From 9bd676f5be48d8e9abf67eca294620ee30d2b454 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 7 Jun 2020 20:02:03 +0200 Subject: [PATCH] Some bugfixes, more experimenting with timing of frame sync calls. --- apps/openmw/engine.cpp | 11 +- apps/openmw/mwgui/windowmanagerimp.cpp | 10 +- apps/openmw/mwrender/animation.cpp | 8 +- apps/openmw/mwvr/openxrinputmanager.cpp | 17 +- apps/openmw/mwvr/vranimation.cpp | 5 +- apps/openmw/mwvr/vrgui.cpp | 60 ++++--- apps/openmw/mwvr/vrgui.hpp | 1 + apps/openmw/mwvr/vrsession.cpp | 216 +++++++++++++++--------- apps/openmw/mwvr/vrsession.hpp | 4 + apps/openmw/mwworld/worldimp.cpp | 2 + files/mygui/openmw_layers_vr.xml | 1 + 11 files changed, 214 insertions(+), 121 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 797ab6197..0946f9a85 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -564,6 +564,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mVFS.get(), mUseSound)); + +#ifdef USE_OPENXR + mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer)); + //mViewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded); +#endif + if (!mSkipMenu) { const std::string& logo = Fallback::Map::getString("Movies_Company_Logo"); @@ -729,11 +735,6 @@ void OMW::Engine::go() osg::ref_ptr resourceshandler = new Resource::StatsHandler; mViewer->addEventHandler(resourceshandler); -#ifdef USE_OPENXR - mXrEnvironment.setGUIManager(new MWVR::VRGUIManager(mViewer)); - //mViewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded); -#endif - // Start the game if (!mSaveGameFile.empty()) { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 7d5969e47..0533f8f71 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -288,13 +288,13 @@ namespace MWGui MyGUI::PointerManager::getInstance().setVisible(false); mVideoBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, - MyGUI::Align::Default, "InputBlocker"); + MyGUI::Align::Default, "VideoPlayer"); mVideoBackground->setImageTexture("black"); mVideoBackground->setVisible(false); mVideoBackground->setNeedMouseFocus(true); mVideoBackground->setNeedKeyFocus(true); - mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); + mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default, "VideoPlayer"); mVideoWidget->setNeedMouseFocus(true); mVideoWidget->setNeedKeyFocus(true); mVideoWidget->setVFS(resourceSystem->getVFS()); @@ -1969,6 +1969,7 @@ namespace MWGui void WindowManager::playVideo(const std::string &name, bool allowSkipping) { + auto* vrGuiManager = MWVR::Environment::get().getGUIManager(); mVideoEnabled = true; mVideoWidget->playVideo("video\\" + name); @@ -1990,6 +1991,9 @@ namespace MWGui mVideoBackground->setVisible(true); + vrGuiManager->updateTracking(mViewer->getCamera()); + vrGuiManager->insertLayer(mVideoBackground->getLayer()->getName()); + bool cursorWasVisible = mCursorVisible; setCursorVisible(false); @@ -2019,6 +2023,7 @@ namespace MWGui mViewer->eventTraversal(); mViewer->updateTraversal(); + //vrGuiManager->updateTracking(mViewer->getCamera()); mViewer->renderingTraversals(); } // at the time this function is called we are in the middle of a frame, @@ -2039,6 +2044,7 @@ namespace MWGui // Restore normal rendering updateVisible(); + vrGuiManager->removeLayer(mVideoBackground->getLayer()->getName()); mVideoBackground->setVisible(false); mVideoEnabled = false; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 17b934ef1..ca95bba76 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -907,10 +907,10 @@ namespace MWRender const bool isPlayer = (mPtr == MWMechanics::getPlayer()); - //if (isPlayer) - //{ - // Log(Debug::Verbose) << "groupname=" << groupname << ", start=" << start << ", stop=" << stop << ", accumRoot=" << mAccumRoot->getName(); - //} + if (isPlayer) + { + Log(Debug::Debug) << "groupname=" << groupname << ", start=" << start << ", stop=" << stop << ", accumRoot=" << mAccumRoot->getName(); + } AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) diff --git a/apps/openmw/mwvr/openxrinputmanager.cpp b/apps/openmw/mwvr/openxrinputmanager.cpp index 9852ba8c4..0cab69364 100644 --- a/apps/openmw/mwvr/openxrinputmanager.cpp +++ b/apps/openmw/mwvr/openxrinputmanager.cpp @@ -57,9 +57,9 @@ namespace MWVR { -// TODO: Make part of settings (is there already a setting like this?) -//! Delay before a long-press action is activated -static std::chrono::milliseconds gActionTime{ 1000 }; +//! Delay before a long-press action is activated (and regular press is discarded) +//! TODO: Make this configurable? +static std::chrono::milliseconds gActionTime{ 666 }; //! Magnitude above which an axis action is considered active static float gAxisEpsilon{ 0.01f }; @@ -568,7 +568,7 @@ OpenXRInput::OpenXRInput() , mMoveLeftRight(std::move(createMWAction(MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right", { }))) , mJournal(std::move(createMWAction(MWInput::A_Journal, "journal_book", "Journal Book", { }))) , mQuickSave(std::move(createMWAction(MWInput::A_QuickSave, "quick_save", "Quick Save", { }))) - , mRest(std::move(createMWAction(MWInput::A_Rest, "rest", "Rest", { }))) + , mRest(std::move(createMWAction(MWInput::A_Rest, "rest", "Rest", { }))) , mActivateTouch(std::move(createMWAction(A_ActivateTouch, "activate_touched", "Activate Touch", { RIGHT_HAND }))) , mAlwaysRun(std::move(createMWAction(MWInput::A_AlwaysRun, "always_run", "Always Run", { }))) , mAutoMove(std::move(createMWAction(MWInput::A_AutoMove, "auto_move", "Auto Move", { }))) @@ -1027,9 +1027,16 @@ private: MWInput::InputManager::update(dt, disableControls, disableEvents); + // Start next frame phase + auto* session = Environment::get().getSession(); + if (session) + session->beginPhase(VRSession::FramePhase::Update); + // The rest of this code assumes the game is running - if (MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + { return; + } bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); diff --git a/apps/openmw/mwvr/vranimation.cpp b/apps/openmw/mwvr/vranimation.cpp index 6cab594e9..e86eccae9 100644 --- a/apps/openmw/mwvr/vranimation.cpp +++ b/apps/openmw/mwvr/vranimation.cpp @@ -274,10 +274,11 @@ void HandController::operator()(osg::Node* node, osg::NodeVisitor* nv) osg::Quat rotate{ 0,0,0,1 }; auto* world = MWBase::Environment::get().getWorld(); auto windowManager = MWBase::Environment::get().getWindowManager(); + auto animation = MWVR::Environment::get().getPlayerAnimation(); auto weaponType = world->getActiveWeaponType(); // Morrowind models do not hold most weapons at a natural angle, so i rotate the hand // to more natural angles on weapons to allow more comfortable combat. - if (!windowManager->isGuiMode()) + if (!windowManager->isGuiMode() && !animation->isPointingForward()) { switch (weaponType) @@ -407,10 +408,8 @@ void WeaponPointerController::operator()(osg::Node* node, osg::NodeVisitor* nv) } else { - // Hide the pointer matrixTransform->setMatrix( osg::Matrix::scale(1.f, 64.f, 1.f) - //osg::Matrix::scale(0.f, 0.f, 0.f) ); } diff --git a/apps/openmw/mwvr/vrgui.cpp b/apps/openmw/mwvr/vrgui.cpp index b14a2bd12..0747e4c23 100644 --- a/apps/openmw/mwvr/vrgui.cpp +++ b/apps/openmw/mwvr/vrgui.cpp @@ -23,6 +23,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwgui/windowbase.hpp" +#include "../mwbase/statemanager.hpp" #include #include @@ -408,20 +409,11 @@ VRGUIManager::VRGUIManager( osg::ref_ptr viewer) : mOsgViewer(viewer) { - mGUIGeometriesRoot->setName("XR GUI Geometry Root"); - mGUICamerasRoot->setName("XR GUI Cameras Root"); + mGUIGeometriesRoot->setName("VR GUI Geometry Root"); + mGUICamerasRoot->setName("VR GUI Cameras Root"); auto* root = viewer->getSceneData(); - - SceneUtil::FindByNameVisitor findSceneVisitor("Scene Root"); - root->accept(findSceneVisitor); - if(!findSceneVisitor.mFoundNode) - { - Log(Debug::Error) << "Scene Root doesn't exist"; - return; - } - - findSceneVisitor.mFoundNode->addChild(mGUIGeometriesRoot); root->asGroup()->addChild(mGUICamerasRoot); + root->asGroup()->addChild(mGUIGeometriesRoot); } @@ -447,10 +439,12 @@ static const LayerConfig createDefaultConfig(int priority, bool background = tru }; } LayerConfig gDefaultConfig = createDefaultConfig(1); +LayerConfig gVideoPlayerConfig = createDefaultConfig(1, true, SizingMode::Fixed); +LayerConfig gLoadingScreenConfig = createDefaultConfig(1, true, SizingMode::Fixed); LayerConfig gJournalBooksConfig = createDefaultConfig(2, false, SizingMode::Fixed); LayerConfig gDefaultWindowsConfig = createDefaultConfig(3, true); LayerConfig gMessageBoxConfig = createDefaultConfig(6, false, SizingMode::Auto);; -LayerConfig gNotificationConfig = createDefaultConfig(7, false, SizingMode::Fixed);; +LayerConfig gNotificationConfig = createDefaultConfig(7, false, SizingMode::Fixed); static const float sSideBySideRadius = 1.f; static const float sSideBySideAzimuthInterval = -osg::PI_4; @@ -527,6 +521,8 @@ static std::map gLayerConfigs = {"MessageBox", gMessageBoxConfig}, {"Windows", gDefaultWindowsConfig}, {"Notification", gNotificationConfig}, + {"VideoPlayer", gVideoPlayerConfig}, + {"LoadingScreen", gLoadingScreenConfig}, }; static std::set layerBlacklist = @@ -673,21 +669,41 @@ void VRGUIManager::setVisible(MWGui::Layout* widget, bool visible) void VRGUIManager::updateTracking(void) { - // Get head pose by reading the camera view matrix to place the GUI in the world. - osg::Vec3 eye{}; - osg::Vec3 center{}; - osg::Vec3 up{}; - Pose headPose{}; auto* world = MWBase::Environment::get().getWorld(); if (!world) return; auto* camera = world->getRenderingManager().getCamera()->getOsgCamera(); if (!camera) return; - camera->getViewMatrixAsLookAt(eye, center, up); - headPose.position = eye; - headPose.orientation = camera->getViewMatrix().getRotate(); - headPose.orientation = headPose.orientation.inverse(); + updateTracking(camera); +} + +void VRGUIManager::updateTracking(osg::Camera* camera) +{ + // Get head pose by reading the camera view matrix to place the GUI in the world. + osg::Vec3 eye{}; + osg::Vec3 center{}; + osg::Vec3 up{}; + Pose headPose{}; + + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + { + // If App is not running, tracking will not be propagated to camera. + // So we adopt stage space. + auto pose = MWVR::Environment::get().getSession()->predictedPoses(MWVR::VRSession::FramePhase::Update).head; + osg::Vec3 position = pose.position * Environment::get().unitsPerMeter(); + osg::Quat orientation = pose.orientation; + headPose.position = position; + headPose.orientation = orientation; + } + else + { + auto viewMatrix = camera->getViewMatrix(); + viewMatrix.getLookAt(eye, center, up); + headPose.position = eye; + headPose.orientation = viewMatrix.getRotate(); + headPose.orientation = headPose.orientation.inverse(); + } mHeadPose = headPose; diff --git a/apps/openmw/mwvr/vrgui.hpp b/apps/openmw/mwvr/vrgui.hpp index 4160b1fcb..667be0242 100644 --- a/apps/openmw/mwvr/vrgui.hpp +++ b/apps/openmw/mwvr/vrgui.hpp @@ -137,6 +137,7 @@ namespace MWVR void removeWidget(MWGui::Layout* widget); void updateTracking(void); + void updateTracking(osg::Camera* camera); bool updateFocus(); diff --git a/apps/openmw/mwvr/vrsession.cpp b/apps/openmw/mwvr/vrsession.cpp index 3e5f0c0ee..b3db6aaf1 100644 --- a/apps/openmw/mwvr/vrsession.cpp +++ b/apps/openmw/mwvr/vrsession.cpp @@ -5,6 +5,7 @@ #include "openxrswapchain.hpp" #include "../mwinput/inputmanagerimp.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/statemanager.hpp" #include #include @@ -26,6 +27,14 @@ #include #include +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + namespace MWVR { VRSession::VRSession() @@ -47,7 +56,23 @@ osg::Matrix VRSession::projectionMatrix(FramePhase phase, Side side) osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side) { - MWVR::Pose pose = predictedPoses(phase).view[(int)side].pose; + MWVR::Pose pose{}; + pose = predictedPoses(phase).view[(int)side].pose; + + + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + { + pose = predictedPoses(phase).eye[(int)side]; + osg::Vec3 position = pose.position * Environment::get().unitsPerMeter(); + osg::Quat orientation = pose.orientation; + osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0); + osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1); + osg::Matrix viewMatrix; + viewMatrix.makeLookAt(position, position + forward, up); + + return viewMatrix; + } + osg::Vec3 position = pose.position * Environment::get().unitsPerMeter(); osg::Quat orientation = pose.orientation; @@ -106,72 +131,92 @@ void VRSession::swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer) layer.views = compositionLayerProjectionViews.data(); auto* layerStack = reinterpret_cast(&layer); - Log(Debug::Debug) << getFrame(FramePhase::Swap)->mFrameNo << ": EndFrame"; + Log(Debug::Debug) << getFrame(FramePhase::Swap)->mFrameNo << ": EndFrame " <endFrame(getFrame(FramePhase::Swap)->mPredictedDisplayTime, 1, &layerStack); } - std::unique_lock lock(mMutex); - auto xrPredictionChange = xr->impl().frameState().predictedDisplayTime - mLastPredictedDisplayTime; - mLastPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime; - mLastPredictedDisplayPeriod = xr->impl().frameState().predictedDisplayPeriod; - auto now = std::chrono::steady_clock::now(); - mLastFrameInterval = std::chrono::duration_cast(now - mLastRenderedFrameTimestamp); - mLastRenderedFrameTimestamp = now; - mLastRenderedFrame = getFrame(FramePhase::Swap)->mFrameNo; - - //Log(Debug::Verbose) << getFrame(FramePhase::Swap)->mFrameNo << ": xrPrediction=" << xr->impl().frameState().predictedDisplayTime << ", ourPrediction=" << getFrame(FramePhase::Swap)->mPredictedDisplayTime << ", miss=" << miss << "ms"; - Log(Debug::Debug) << "xrPredictionChange=" << (xrPredictionChange / 1000000) << "ms"; - auto seconds = std::chrono::duration_cast>(now - mStart).count(); - static int sBaseFrames = 0; - if (seconds > 10.f) { - Log(Debug::Verbose) << "Fps: " << (static_cast(mLastRenderedFrame - sBaseFrames) / seconds); - mStart = now; - sBaseFrames = mLastRenderedFrame; - } + std::unique_lock lock(mMutex); + + // Some of these values are useless until the prediction time bug is resolved by oculus. + //auto xrPredictionChange = xr->impl().frameState().predictedDisplayTime - mLastPredictedDisplayTime; + mLastPredictedDisplayTime = xr->impl().frameState().predictedDisplayTime; + mLastPredictedDisplayPeriod = xr->impl().frameState().predictedDisplayPeriod; + auto now = std::chrono::steady_clock::now(); + mLastFrameInterval = std::chrono::duration_cast(now - mLastRenderedFrameTimestamp); + mLastRenderedFrameTimestamp = now; + mLastRenderedFrame = getFrame(FramePhase::Swap)->mFrameNo; + + //Log(Debug::Debug) << getFrame(FramePhase::Swap)->mFrameNo << ": xrPrediction=" << xr->impl().frameState().predictedDisplayTime << ", ourPrediction=" << getFrame(FramePhase::Swap)->mPredictedDisplayTime << ", miss=" << miss << "ms"; + //Log(Debug::Debug) << "xrPredictionChange=" << (xrPredictionChange / 1000000) << "ms"; + //Log(Debug::Debug) << "xrPredictionPeriod=" << (mLastPredictedDisplayPeriod / 1000000) << "ms"; + // Just a quick averaging fps over some time rather than just the instantaneous. + auto seconds = std::chrono::duration_cast>(now - mStart).count(); + static int sBaseFrames = 0; + if (seconds > 10.f) + { + Log(Debug::Debug) << "Fps: " << (static_cast(mLastRenderedFrame - sBaseFrames) / seconds); + mStart = now; + sBaseFrames = mLastRenderedFrame; + } - getFrame(FramePhase::Swap) = nullptr; + getFrame(FramePhase::Swap) = nullptr; + mFramesInFlight--; + } mCondition.notify_one(); } void VRSession::beginPhase(FramePhase phase) { Timer timer("VRSession::advanceFrame"); - Log(Debug::Debug) << "beginPhase(" << ((int)phase) << ")"; + Log(Debug::Debug) << "beginPhase(" << ((int)phase) << ") " << std::this_thread::get_id(); - if (phase != FramePhase::Update) + if (getFrame(phase)) + { + Log(Debug::Warning) << "advanceFramePhase called with a frame alreay in the target phase"; + return; + } + + + if (phase == FramePhase::Update) + { + prepareFrame(); + } + else { std::unique_lock lock(mMutex); FramePhase previousPhase = static_cast((int)phase - 1); - - if (getFrame(phase)) - throw std::logic_error("advanceFramePhase called with a frame alreay in the target phase"); if (!getFrame(previousPhase)) - throw std::logic_error("advanceFramePhase called without a frame in the predraw phase"); + throw std::logic_error("beginPhase called without a frame"); getFrame(phase) = std::move(getFrame(previousPhase)); } - else - prepareFrame(); - if (phase == FramePhase::Cull && getFrame(phase)->mShouldRender) + + // TODO: Invokation should depend on earliest render rather than necessarily phase. + // Specifically. Without shadows this is fine because nothing is being rendered + // during cull or earlier. + // Thought: Add an Shadowmapping phase and invoke it from the shadow code + // But with shadows rendering occurs during cull and we must do frame sync before those calls. + // If you want to pay the FPS toll and play with shadows, change FramePhase::Draw to FramePhase::Cull or enjoy your eyes getting torn apart by jitters. + if (phase == FramePhase::Draw && getFrame(phase)->mShouldRender) + doFrameSync(); +} + +void VRSession::doFrameSync() +{ { - auto* xr = Environment::get().getManager(); - // Since i am forced to do xr->beginFrame() before cull instead of draw - // i have to explicitly wait for xr->endFrame() to finish to avoid an - // out-of-order error from openxr. - // If i don't wait, we might hit beginFrame() before the previous frame - // reaches endFrame() in which case openxr will cancel the previous frame - // and we will get an out-of-order error due to two back-to-back calls to endFrame() - while (getFrame(phase)->mFrameNo != (mLastRenderedFrame + 1)) + std::unique_lock lock(mMutex); + while (mLastRenderedFrame != mFrames - 1) { - std::unique_lock lock(mMutex); mCondition.wait(lock); } - Log(Debug::Debug) << getFrame(phase)->mFrameNo << ": WaitFrame"; - xr->waitFrame(); - Log(Debug::Debug) << getFrame(phase)->mFrameNo << ": BeginFrame"; - xr->beginFrame(); } + + auto* xr = Environment::get().getManager(); + Log(Debug::Debug) << mFrames << ": WaitFrame " << std::this_thread::get_id(); + xr->waitFrame(); + Log(Debug::Debug) << mFrames << ": BeginFrame " << std::this_thread::get_id(); + xr->beginFrame(); } std::unique_ptr& VRSession::getFrame(FramePhase phase) @@ -183,56 +228,65 @@ std::unique_ptr& VRSession::getFrame(FramePhase phase) void VRSession::prepareFrame() { + std::unique_lock lock(mMutex); + mFrames++; assert(!mPredrawFrame); Timer timer("VRSession::startFrame"); auto* xr = Environment::get().getManager(); xr->handleEvents(); + - // Until OpenXR allows us to get a prediction without waiting - // we make our own (bad) prediction and call xrWaitFrame when it is more convenient auto frameState = xr->impl().frameState(); - long long predictedDisplayTime = 0; - mFrames++; - if (mLastPredictedDisplayTime == 0) +// auto predictedDisplayTime = frameState.predictedDisplayTime; +// if (predictedDisplayTime == 0) +// { +// // First time, need to invent a frame time since openxr won't help us without calling waitframe. +// predictedDisplayTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); +// } +// else +// { +// // Predict display time based on real framerate +// float intervalsf = static_cast(mLastFrameInterval.count()) / static_cast(mLastPredictedDisplayPeriod); +// int intervals = std::max((int)std::roundf(intervalsf), 1); +// predictedDisplayTime = mLastPredictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * mLastPredictedDisplayPeriod; +// } +// TODO: +//////////////////////// OCULUS BUG + //////////////////// Oculus will suddenly start monotonically increasing their predicted display time by precisely 1 second + //////////////////// regardless of real time passed, causing predictions to go crazy due to the time difference. + //////////////////// Therefore, for the time being, i ignore oculus' predicted display time altogether. + long long predictedDisplayTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + if (mFrames > 1) { - // First time, need to invent a frame time since openxr won't help us without calling waitframe. - predictedDisplayTime = std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); - } - else if (mFrames > mLastRenderedFrame) - { - //predictedDisplayTime = mLastPredictedDisplayTime + mLastFrameInterval.count() * (mFrames - mLastRenderedFrame); - float intervalsf = static_cast(mLastFrameInterval.count()) / static_cast(mLastPredictedDisplayPeriod); -#ifdef max -#undef max -#endif - int intervals = std::max((int)std::roundf(intervalsf), 1); - predictedDisplayTime = mLastPredictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * mLastPredictedDisplayPeriod; + float intervalsf = static_cast(mLastFrameInterval.count()) / static_cast(frameState.predictedDisplayPeriod); + int intervals = std::max((int)std::roundf(intervalsf), 1); + predictedDisplayTime = predictedDisplayTime + intervals * (mFrames - mLastRenderedFrame) * frameState.predictedDisplayPeriod; } + + PoseSet predictedPoses{}; - if (isRunning()) + + xr->impl().enablePredictions(); + predictedPoses.head = xr->impl().getPredictedLimbPose(predictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE) * mPlayerScale; + auto hmdViews = xr->impl().getPredictedViews(predictedDisplayTime, TrackedSpace::VIEW); + predictedPoses.view[(int)Side::LEFT_HAND].pose = fromXR(hmdViews[(int)Side::LEFT_HAND].pose) * mPlayerScale; + predictedPoses.view[(int)Side::RIGHT_HAND].pose = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose) * mPlayerScale; + predictedPoses.view[(int)Side::LEFT_HAND].fov = fromXR(hmdViews[(int)Side::LEFT_HAND].fov); + predictedPoses.view[(int)Side::RIGHT_HAND].fov = fromXR(hmdViews[(int)Side::RIGHT_HAND].fov); + auto stageViews = xr->impl().getPredictedViews(predictedDisplayTime, TrackedSpace::STAGE); + predictedPoses.eye[(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose) * mPlayerScale; + predictedPoses.eye[(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose) * mPlayerScale; + + auto* input = Environment::get().getInputManager(); + if (input) { - xr->impl().enablePredictions(); - predictedPoses.head = xr->impl().getPredictedLimbPose(predictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE) * mPlayerScale; - auto hmdViews = xr->impl().getPredictedViews(predictedDisplayTime, TrackedSpace::VIEW); - predictedPoses.view[(int)Side::LEFT_HAND].pose = fromXR(hmdViews[(int)Side::LEFT_HAND].pose) * mPlayerScale; - predictedPoses.view[(int)Side::RIGHT_HAND].pose = fromXR(hmdViews[(int)Side::RIGHT_HAND].pose) * mPlayerScale; - predictedPoses.view[(int)Side::LEFT_HAND].fov = fromXR(hmdViews[(int)Side::LEFT_HAND].fov); - predictedPoses.view[(int)Side::RIGHT_HAND].fov = fromXR(hmdViews[(int)Side::RIGHT_HAND].fov); - auto stageViews = xr->impl().getPredictedViews(predictedDisplayTime, TrackedSpace::STAGE); - predictedPoses.eye[(int)Side::LEFT_HAND] = fromXR(stageViews[(int)Side::LEFT_HAND].pose) * mPlayerScale; - predictedPoses.eye[(int)Side::RIGHT_HAND] = fromXR(stageViews[(int)Side::RIGHT_HAND].pose) * mPlayerScale; - - auto* input = Environment::get().getInputManager(); - if (input) - { - predictedPoses.hands[(int)Side::LEFT_HAND] = input->getHandPose(predictedDisplayTime, TrackedSpace::STAGE, Side::LEFT_HAND) * mPlayerScale; - predictedPoses.hands[(int)Side::RIGHT_HAND] = input->getHandPose(predictedDisplayTime, TrackedSpace::STAGE, Side::RIGHT_HAND) * mPlayerScale; - } - xr->impl().disablePredictions(); + predictedPoses.hands[(int)Side::LEFT_HAND] = input->getHandPose(predictedDisplayTime, TrackedSpace::STAGE, Side::LEFT_HAND) * mPlayerScale; + predictedPoses.hands[(int)Side::RIGHT_HAND] = input->getHandPose(predictedDisplayTime, TrackedSpace::STAGE, Side::RIGHT_HAND) * mPlayerScale; } + xr->impl().disablePredictions(); auto& frame = getFrame(FramePhase::Update); frame.reset(new VRFrame); @@ -240,6 +294,7 @@ void VRSession::prepareFrame() frame->mFrameNo = mFrames; frame->mPredictedPoses = predictedPoses; frame->mShouldRender = isRunning(); + mFramesInFlight++; } const PoseSet& VRSession::predictedPoses(FramePhase phase) @@ -298,8 +353,9 @@ void VRSession::movementAngles(float& yaw, float& pitch) // TODO: This strictly speaking violates the rule of not making predictions outside of prepareFrame() // I should either add VIEW hands to the predicted pose set, or compute this using STAGE poses // It would likely suffice to compute euler angles for STAGE head and hand and return the difference? + if (!getFrame(FramePhase::Update)) + beginPhase(FramePhase::Update); auto lhandquat = input->getHandPose(getFrame(FramePhase::Update)->mPredictedDisplayTime, TrackedSpace::VIEW, Side::LEFT_HAND).orientation; - //predictedPoses(FramePhase::Predraw).hands[(int)TrackedSpace::VIEW][(int)MWVR::Side::LEFT_HAND].orientation; float roll = 0.f; getEulerAngles(lhandquat, yaw, pitch, roll); diff --git a/apps/openmw/mwvr/vrsession.hpp b/apps/openmw/mwvr/vrsession.hpp index d8217717a..322a852b4 100644 --- a/apps/openmw/mwvr/vrsession.hpp +++ b/apps/openmw/mwvr/vrsession.hpp @@ -54,6 +54,9 @@ public: //! Starts a new frame void prepareFrame(); + //! Synchronize with openxr + void doFrameSync(); + //! Angles to be used for overriding movement direction void movementAngles(float& yaw, float& pitch); @@ -68,6 +71,7 @@ public: osg::Matrix viewMatrix(FramePhase phase, Side side); osg::Matrix projectionMatrix(FramePhase phase, Side side); + int mFramesInFlight{ 0 }; std::array, (int)FramePhase::NumPhases> mFrame{ nullptr }; std::mutex mMutex{}; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 92b266c48..57c1e22a3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -607,6 +607,7 @@ namespace MWWorld void World::useDeathCamera() { +#ifndef USE_OPENXR if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) { mRendering->getCamera()->togglePreviewMode(false); @@ -614,6 +615,7 @@ namespace MWWorld } if(mRendering->getCamera()->isFirstPerson()) mRendering->getCamera()->toggleViewMode(true); +#endif } MWWorld::Player& World::getPlayer() diff --git a/files/mygui/openmw_layers_vr.xml b/files/mygui/openmw_layers_vr.xml index 4deecd67b..bbcd2ef94 100644 --- a/files/mygui/openmw_layers_vr.xml +++ b/files/mygui/openmw_layers_vr.xml @@ -25,5 +25,6 @@ + \ No newline at end of file