diff --git a/apps/openmw/mwvr/openxraction.cpp b/apps/openmw/mwvr/openxraction.cpp index d71e1f6db..cc878a492 100644 --- a/apps/openmw/mwvr/openxraction.cpp +++ b/apps/openmw/mwvr/openxraction.cpp @@ -32,7 +32,7 @@ bool OpenXRAction::getFloat(XrPath subactionPath, float& value) getInfo.subactionPath = subactionPath; XrActionStateFloat xrValue{ XR_TYPE_ACTION_STATE_FLOAT }; - CHECK_XRCMD(xrGetActionStateFloat(xr->impl().mSession, &getInfo, &xrValue)); + CHECK_XRCMD(xrGetActionStateFloat(xr->impl().xrSession(), &getInfo, &xrValue)); if (xrValue.isActive) value = xrValue.currentState; @@ -47,7 +47,7 @@ bool OpenXRAction::getBool(XrPath subactionPath, bool& value) getInfo.subactionPath = subactionPath; XrActionStateBoolean xrValue{ XR_TYPE_ACTION_STATE_BOOLEAN }; - CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().mSession, &getInfo, &xrValue)); + CHECK_XRCMD(xrGetActionStateBoolean(xr->impl().xrSession(), &getInfo, &xrValue)); if (xrValue.isActive) value = xrValue.currentState; @@ -63,7 +63,7 @@ bool OpenXRAction::getPoseIsActive(XrPath subactionPath) getInfo.subactionPath = subactionPath; XrActionStatePose xrValue{ XR_TYPE_ACTION_STATE_POSE }; - CHECK_XRCMD(xrGetActionStatePose(xr->impl().mSession, &getInfo, &xrValue)); + CHECK_XRCMD(xrGetActionStatePose(xr->impl().xrSession(), &getInfo, &xrValue)); return xrValue.isActive; } @@ -81,7 +81,7 @@ bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude) XrHapticActionInfo hapticActionInfo{ XR_TYPE_HAPTIC_ACTION_INFO }; hapticActionInfo.action = mAction; hapticActionInfo.subactionPath = subactionPath; - CHECK_XRCMD(xrApplyHapticFeedback(xr->impl().mSession, &hapticActionInfo, (XrHapticBaseHeader*)&vibration)); + CHECK_XRCMD(xrApplyHapticFeedback(xr->impl().xrSession(), &hapticActionInfo, (XrHapticBaseHeader*)&vibration)); return true; } } diff --git a/apps/openmw/mwvr/openxrinput.cpp b/apps/openmw/mwvr/openxrinput.cpp index 66e456377..9fcfa5305 100644 --- a/apps/openmw/mwvr/openxrinput.cpp +++ b/apps/openmw/mwvr/openxrinput.cpp @@ -103,7 +103,7 @@ OpenXRInput::OpenXRInput(const std::vector& suggestedBindings XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO }; attachInfo.countActionSets = 1; attachInfo.actionSets = &mActionSet; - CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().mSession, &attachInfo)); + CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo)); } }; @@ -144,7 +144,7 @@ OpenXRInput::createActionSet() strcpy_s(createInfo.actionSetName, "gameplay"); strcpy_s(createInfo.localizedActionSetName, "Gameplay"); createInfo.priority = 0; - CHECK_XRCMD(xrCreateActionSet(xr->impl().mInstance, &createInfo, &actionSet)); + CHECK_XRCMD(xrCreateActionSet(xr->impl().xrInstance(), &createInfo, &actionSet)); return actionSet; } @@ -153,7 +153,7 @@ void OpenXRInput::suggestBindings(const SuggestedBindings& mwSuggestedBindings) auto* xr = Environment::get().getManager(); XrPath oculusTouchInteractionProfilePath; CHECK_XRCMD( - xrStringToPath(xr->impl().mInstance, mwSuggestedBindings.controllerPath.c_str(), &oculusTouchInteractionProfilePath)); + xrStringToPath(xr->impl().xrInstance(), mwSuggestedBindings.controllerPath.c_str(), &oculusTouchInteractionProfilePath)); std::vector suggestedBindings = { @@ -178,7 +178,7 @@ void OpenXRInput::suggestBindings(const SuggestedBindings& mwSuggestedBindings) xrSuggestedBindings.interactionProfile = oculusTouchInteractionProfilePath; xrSuggestedBindings.suggestedBindings = suggestedBindings.data(); xrSuggestedBindings.countSuggestedBindings = (uint32_t)suggestedBindings.size(); - CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().mInstance, &xrSuggestedBindings)); + CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrSuggestedBindings)); } void @@ -192,8 +192,8 @@ OpenXRInput::generateControllerActionPaths( std::string left = std::string("/user/hand/left") + controllerAction; std::string right = std::string("/user/hand/right") + controllerAction; - CHECK_XRCMD(xrStringToPath(xr->impl().mInstance, left.c_str(), &actionPaths[(int)Side::LEFT_SIDE])); - CHECK_XRCMD(xrStringToPath(xr->impl().mInstance, right.c_str(), &actionPaths[(int)Side::RIGHT_SIDE])); + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), left.c_str(), &actionPaths[(int)Side::LEFT_SIDE])); + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), right.c_str(), &actionPaths[(int)Side::RIGHT_SIDE])); mPathMap[actionPath] = actionPaths; } @@ -221,7 +221,7 @@ void OpenXRInput::updateControls() { auto* xr = Environment::get().getManager(); - if (!xr->impl().mSessionRunning) + if (!xr->impl().xrSessionRunning()) return; @@ -229,7 +229,7 @@ OpenXRInput::updateControls() XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO }; syncInfo.countActiveActionSets = 1; syncInfo.activeActionSets = &activeActionSet; - CHECK_XRCMD(xrSyncActions(xr->impl().mSession, &syncInfo)); + CHECK_XRCMD(xrSyncActions(xr->impl().xrSession(), &syncInfo)); // Note on update order: @@ -246,7 +246,7 @@ XrPath OpenXRInput::generateXrPath(const std::string& path) { auto* xr = Environment::get().getManager(); XrPath xrpath = 0; - CHECK_XRCMD(xrStringToPath(xr->impl().mInstance, path.c_str(), &xrpath)); + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), path.c_str(), &xrpath)); return xrpath; } diff --git a/apps/openmw/mwvr/openxrmanager.cpp b/apps/openmw/mwvr/openxrmanager.cpp index d014ec51d..bd98292db 100644 --- a/apps/openmw/mwvr/openxrmanager.cpp +++ b/apps/openmw/mwvr/openxrmanager.cpp @@ -40,10 +40,10 @@ namespace MWVR return !!mPrivate; } - bool OpenXRManager::sessionRunning() + bool OpenXRManager::xrSessionRunning() { if (realized()) - return impl().mSessionRunning; + return impl().xrSessionRunning(); return false; } @@ -71,12 +71,6 @@ namespace MWVR return impl().endFrame(displayTime, layerCount, layerStack); } - void OpenXRManager::updateControls() - { - if (realized()) - return impl().updateControls(); - } - void OpenXRManager::realize( osg::GraphicsContext* gc) @@ -97,13 +91,6 @@ namespace MWVR } } - int OpenXRManager::eyes() - { - if (realized()) - return impl().eyes(); - return 0; - } - void OpenXRManager::enablePredictions() { return impl().enablePredictions(); @@ -114,21 +101,31 @@ namespace MWVR return impl().disablePredictions(); } - std::array OpenXRManager::getPredictedViews(int64_t predictedDisplayTime, TrackedSpace space) + std::array OpenXRManager::getPredictedViews(int64_t predictedDisplayTime, ReferenceSpace space) { return impl().getPredictedViews(predictedDisplayTime, space); } - MWVR::Pose OpenXRManager::getPredictedHeadPose(int64_t predictedDisplayTime, TrackedSpace 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 OpenXRManager::getRecommendedSwapchainConfig() const + { + return impl().getRecommendedSwapchainConfig(); + } + void OpenXRManager::RealizeOperation::operator()( osg::GraphicsContext* gc) diff --git a/apps/openmw/mwvr/openxrmanager.hpp b/apps/openmw/mwvr/openxrmanager.hpp index b58091872..ea6c954bb 100644 --- a/apps/openmw/mwvr/openxrmanager.hpp +++ b/apps/openmw/mwvr/openxrmanager.hpp @@ -53,26 +53,47 @@ namespace MWVR bool realized(); - long long frameIndex(); - bool sessionRunning(); - - void handleEvents(); + //! Forward call to xrWaitFrame() void waitFrame(); - void beginFrame(); - void endFrame(int64_t displayTime, int layerCount, const std::array& layerStack); - void updateControls(); + //! Forward call to xrBeginFrame() + void beginFrame(); + + //! Forward call to xrEndFrame() + void endFrame(int64_t displayTime, int layerCount, const std::array& layerStack); + + //! Whether the openxr session is currently in a running state + bool xrSessionRunning(); + + //! Process all openxr events + void handleEvents(); + + //! Instantiate implementation void realize(osg::GraphicsContext* gc); - int eyes(); - + //! Enable pose predictions. Exist to police that predictions are never made out of turn. void enablePredictions(); + + //! Disable pose predictions. void disablePredictions(); - std::array getPredictedViews(int64_t predictedDisplayTime, TrackedSpace space); - MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, TrackedSpace space); + + //! 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 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 getRecommendedSwapchainConfig() const; + OpenXRManagerImpl& impl() { return *mPrivate; } + const OpenXRManagerImpl& impl() const { return *mPrivate; } private: std::shared_ptr mPrivate; diff --git a/apps/openmw/mwvr/openxrmanagerimpl.cpp b/apps/openmw/mwvr/openxrmanagerimpl.cpp index 1c0d51832..9f0687f39 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.cpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.cpp @@ -1,5 +1,5 @@ #include "openxrmanagerimpl.hpp" -#include "openxrswapchain.hpp" +#include "openxrswapchainimpl.hpp" #include "vrtexture.hpp" #include @@ -173,6 +173,11 @@ namespace MWVR return res; } + std::string XrResultString(XrResult res) + { + return to_string(res); + } + OpenXRManagerImpl::~OpenXRManagerImpl() { @@ -259,12 +264,6 @@ namespace MWVR Log(Debug::Verbose) << ss.str(); } - XrFrameState OpenXRManagerImpl::frameState() - { - std::unique_lock lock(mFrameStateMutex); - return mFrameState; - } - void OpenXRManagerImpl::waitFrame() { @@ -294,7 +293,7 @@ namespace MWVR { XrCompositionLayerProjectionView xrLayer; xrLayer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; - xrLayer.subImage = layer.swapchain->subImage(); + xrLayer.subImage = layer.swapchain->impl().xrSubImage(); xrLayer.pose = toXR(layer.pose); xrLayer.fov = toXR(layer.fov); xrLayer.next = nullptr; @@ -316,7 +315,7 @@ namespace MWVR compositionLayerProjectionViews[(int)Side::RIGHT_SIDE] = toXR(layerStack[(int)Side::RIGHT_SIDE]); XrCompositionLayerProjection layer{}; layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; - layer.space = getReferenceSpace(TrackedSpace::STAGE); + layer.space = getReferenceSpace(ReferenceSpace::STAGE); layer.viewCount = 2; layer.views = compositionLayerProjectionViews.data(); auto* xrLayerStack = reinterpret_cast(&layer); @@ -334,7 +333,7 @@ namespace MWVR std::array OpenXRManagerImpl::getPredictedViews( int64_t predictedDisplayTime, - TrackedSpace space) + ReferenceSpace space) { if (!mPredictionsEnabled) { @@ -350,10 +349,10 @@ namespace MWVR viewLocateInfo.displayTime = predictedDisplayTime; switch (space) { - case TrackedSpace::STAGE: + case ReferenceSpace::STAGE: viewLocateInfo.space = mReferenceSpaceStage; break; - case TrackedSpace::VIEW: + case ReferenceSpace::VIEW: viewLocateInfo.space = mReferenceSpaceView; break; } @@ -369,7 +368,7 @@ namespace MWVR MWVR::Pose OpenXRManagerImpl::getPredictedHeadPose( int64_t predictedDisplayTime, - TrackedSpace space) + ReferenceSpace space) { if (!mPredictionsEnabled) { @@ -382,10 +381,10 @@ namespace MWVR switch (space) { - case TrackedSpace::STAGE: + case ReferenceSpace::STAGE: referenceSpace = mReferenceSpaceStage; break; - case TrackedSpace::VIEW: + case ReferenceSpace::VIEW: referenceSpace = mReferenceSpaceView; break; } @@ -398,16 +397,11 @@ namespace MWVR location.pose.orientation.w = 1; } return MWVR::Pose{ - osg::fromXR(location.pose.position), - osg::fromXR(location.pose.orientation) + fromXR(location.pose.position), + fromXR(location.pose.orientation) }; } - int OpenXRManagerImpl::eyes() - { - return mConfigViews.size(); - } - void OpenXRManagerImpl::handleEvents() { std::unique_lock lock(mEventMutex); @@ -435,9 +429,6 @@ namespace MWVR } } - void OpenXRManagerImpl::updateControls() - { - } void OpenXRManagerImpl::HandleSessionStateChanged( const XrEventDataSessionStateChanged& stateChangedEvent) @@ -495,12 +486,12 @@ namespace MWVR MWVR::Pose fromXR(XrPosef pose) { - return MWVR::Pose{ osg::fromXR(pose.position), osg::fromXR(pose.orientation) }; + return MWVR::Pose{ fromXR(pose.position), fromXR(pose.orientation) }; } XrPosef toXR(MWVR::Pose pose) { - return XrPosef{ osg::toXR(pose.orientation), osg::toXR(pose.position) }; + return XrPosef{ toXR(pose.orientation), toXR(pose.position) }; } MWVR::FieldOfView fromXR(XrFovf fov) @@ -513,12 +504,12 @@ namespace MWVR return XrFovf{ fov.angleLeft, fov.angleRight, fov.angleUp, fov.angleDown }; } - XrSpace OpenXRManagerImpl::getReferenceSpace(TrackedSpace space) + XrSpace OpenXRManagerImpl::getReferenceSpace(ReferenceSpace space) { XrSpace referenceSpace = XR_NULL_HANDLE; - if (space == TrackedSpace::STAGE) + if (space == ReferenceSpace::STAGE) referenceSpace = mReferenceSpaceStage; - if (space == TrackedSpace::VIEW) + if (space == ReferenceSpace::VIEW) referenceSpace = mReferenceSpaceView; return referenceSpace; } @@ -533,32 +524,48 @@ namespace MWVR mPredictionsEnabled = false; } + long long OpenXRManagerImpl::getLastPredictedDisplayTime() + { + return mFrameState.predictedDisplayTime; + } + long long OpenXRManagerImpl::getLastPredictedDisplayPeriod() { return mFrameState.predictedDisplayPeriod; } -} - -namespace osg -{ - - Vec3 fromXR(XrVector3f v) + std::array OpenXRManagerImpl::getRecommendedSwapchainConfig() const { - return Vec3{ v.x, -v.z, v.y }; + std::array config{}; + for (uint32_t i = 0; i < 2; i++) + config[i] = SwapchainConfig{ + mConfigViews[i].recommendedImageRectWidth, + mConfigViews[i].maxImageRectWidth, + mConfigViews[i].recommendedImageRectHeight, + mConfigViews[i].maxImageRectHeight, + mConfigViews[i].recommendedSwapchainSampleCount, + mConfigViews[i].recommendedSwapchainSampleCount, + }; + return config; } - Quat fromXR(XrQuaternionf quat) + osg::Vec3 fromXR(XrVector3f v) { - return Quat{ quat.x, -quat.z, quat.y, quat.w }; + return osg::Vec3{ v.x, -v.z, v.y }; } - XrVector3f toXR(Vec3 v) + 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(Quat quat) + XrQuaternionf toXR(osg::Quat quat) { return XrQuaternionf{ quat.x(), quat.z(), -quat.y(), quat.w() }; } } + diff --git a/apps/openmw/mwvr/openxrmanagerimpl.hpp b/apps/openmw/mwvr/openxrmanagerimpl.hpp index 5a3b5fcae..ccc364e6a 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.hpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.hpp @@ -22,82 +22,82 @@ #include #include -namespace osg { - Vec3 fromXR(XrVector3f); - Quat fromXR(XrQuaternionf quat); - XrVector3f toXR(Vec3 v); - XrQuaternionf toXR(Quat quat); -} - 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); - XrResult CheckXrResult(XrResult res, const char* originator = nullptr, const char* sourceLocation = nullptr); - MWVR::Pose fromXR(XrPosef pose); - XrPosef toXR(MWVR::Pose pose); - MWVR::FieldOfView fromXR(XrFovf fov); - XrFovf toXR(MWVR::FieldOfView fov); +/// Conversion methods from openxr types to osg/mwvr types. Includes managing the differing conventions. +MWVR::Pose fromXR(XrPosef pose); +MWVR::FieldOfView fromXR(XrFovf fov); +osg::Vec3 fromXR(XrVector3f); +osg::Quat fromXR(XrQuaternionf quat); - XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer); +/// Conversion methods from osg/mwvr types to openxr types. Includes managing the differing conventions. +XrPosef toXR(MWVR::Pose pose); +XrFovf toXR(MWVR::FieldOfView fov); +XrVector3f toXR(osg::Vec3 v); +XrQuaternionf toXR(osg::Quat quat); - struct OpenXRManagerImpl - { - OpenXRManagerImpl(void); - ~OpenXRManagerImpl(void); +XrCompositionLayerProjectionView toXR(MWVR::CompositionLayerProjectionView layer); - void LogLayersAndExtensions(); - void LogInstanceInfo(); - void LogReferenceSpaces(); +struct OpenXRManagerImpl +{ + OpenXRManagerImpl(void); + ~OpenXRManagerImpl(void); - const XrEventDataBaseHeader* nextEvent(); - void waitFrame(); - void beginFrame(); - void endFrame(int64_t displayTime, int layerCount, const std::array& layerStack); - std::array getPredictedViews(int64_t predictedDisplayTime, TrackedSpace space); - MWVR::Pose getPredictedHeadPose(int64_t predictedDisplayTime, TrackedSpace space); - int eyes(); - void handleEvents(); - void updateControls(); - void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent); - XrFrameState frameState(); - XrSpace getReferenceSpace(TrackedSpace space); - void enablePredictions(); - void disablePredictions(); - long long getLastPredictedDisplayPeriod(); + void waitFrame(); + void beginFrame(); + void endFrame(int64_t displayTime, int layerCount, const std::array& layerStack); + bool xrSessionRunning() const { return mSessionRunning; } + std::array 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 getRecommendedSwapchainConfig() const; + XrSpace getReferenceSpace(ReferenceSpace space); + XrSession xrSession() const { return mSession; }; + XrInstance xrInstance() const { return mInstance; }; - 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; - XrGraphicsBindingOpenGLWin32KHR mGraphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR }; - XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES }; - std::array mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } }; - XrSpace mReferenceSpaceView = XR_NULL_HANDLE; - XrSpace mReferenceSpaceStage = XR_NULL_HANDLE; - XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER }; - XrFrameState mFrameState{}; - XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN; - bool mSessionRunning = false; - std::mutex mFrameStateMutex{}; - std::mutex mEventMutex{}; +protected: + void LogLayersAndExtensions(); + void LogInstanceInfo(); + void LogReferenceSpaces(); + const XrEventDataBaseHeader* nextEvent(); + void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent); - XrActionSet mActionSet = nullptr; - XrAction mHandPoseAction = nullptr; - XrSpace mHandSpaces[2]{ nullptr, nullptr }; - XrPath mSubactionPaths[2]{ 0, 0 }; - XrPath mPosePath[2]{ 0, 0 }; - }; +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; + XrGraphicsBindingOpenGLWin32KHR mGraphicsBinding{ XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR }; + XrSystemProperties mSystemProperties{ XR_TYPE_SYSTEM_PROPERTIES }; + std::array mConfigViews{ { {XR_TYPE_VIEW_CONFIGURATION_VIEW}, {XR_TYPE_VIEW_CONFIGURATION_VIEW} } }; + XrSpace mReferenceSpaceView = XR_NULL_HANDLE; + XrSpace mReferenceSpaceStage = XR_NULL_HANDLE; + XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER }; + XrFrameState mFrameState{}; + XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN; + bool mSessionRunning = false; + std::mutex mFrameStateMutex{}; + std::mutex mEventMutex{}; +}; } #endif diff --git a/apps/openmw/mwvr/openxrswapchain.cpp b/apps/openmw/mwvr/openxrswapchain.cpp index cbed470c8..be563f348 100644 --- a/apps/openmw/mwvr/openxrswapchain.cpp +++ b/apps/openmw/mwvr/openxrswapchain.cpp @@ -24,7 +24,7 @@ namespace MWVR { SUBVIEW_MAX = RIGHT_VIEW, //!< Used to size subview arrays. Not a valid input. }; - OpenXRSwapchainImpl(osg::ref_ptr state, OpenXRSwapchain::Config config); + OpenXRSwapchainImpl(osg::ref_ptr state, SwapchainConfig config); ~OpenXRSwapchainImpl(); void beginFrame(osg::GraphicsContext* gc); @@ -53,10 +53,10 @@ namespace MWVR { bool mIsAcquired{ false }; }; - OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr state, OpenXRSwapchain::Config config) - : mWidth(config.width) - , mHeight(config.height) - , mSamples(config.samples) + OpenXRSwapchainImpl::OpenXRSwapchainImpl(osg::ref_ptr state, SwapchainConfig config) + : mWidth(config.recommendedWidth) + , mHeight(config.recommendedHeight) + , mSamples(config.recommendedSamples) { if (mWidth <= 0) throw std::invalid_argument("Width must be a positive integer"); @@ -69,9 +69,9 @@ namespace MWVR { // Select a swapchain format. uint32_t swapchainFormatCount; - CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().mSession, 0, &swapchainFormatCount, nullptr)); + CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().xrSession(), 0, &swapchainFormatCount, nullptr)); std::vector swapchainFormats(swapchainFormatCount); - CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().mSession, (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data())); + CHECK_XRCMD(xrEnumerateSwapchainFormats(xr->impl().xrSession(), (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data())); // List of supported color swapchain formats. constexpr int64_t SupportedColorSwapchainFormats[] = { @@ -100,7 +100,7 @@ namespace MWVR { swapchainCreateInfo.faceCount = 1; swapchainCreateInfo.sampleCount = mSamples; swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; - CHECK_XRCMD(xrCreateSwapchain(xr->impl().mSession, &swapchainCreateInfo, &mSwapchain)); + CHECK_XRCMD(xrCreateSwapchain(xr->impl().xrSession(), &swapchainCreateInfo, &mSwapchain)); uint32_t imageCount = 0; CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, 0, &imageCount, nullptr)); @@ -176,7 +176,7 @@ namespace MWVR { CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo)); } - OpenXRSwapchain::OpenXRSwapchain(osg::ref_ptr state, OpenXRSwapchain::Config config) + OpenXRSwapchain::OpenXRSwapchain(osg::ref_ptr state, SwapchainConfig config) : mPrivate(new OpenXRSwapchainImpl(state, config)) { } diff --git a/apps/openmw/mwvr/openxrswapchain.hpp b/apps/openmw/mwvr/openxrswapchain.hpp index 2caec0f7d..73c4b305b 100644 --- a/apps/openmw/mwvr/openxrswapchain.hpp +++ b/apps/openmw/mwvr/openxrswapchain.hpp @@ -21,7 +21,7 @@ namespace MWVR }; public: - OpenXRSwapchain(osg::ref_ptr state, Config config); + OpenXRSwapchain(osg::ref_ptr state, SwapchainConfig config); ~OpenXRSwapchain(); public: diff --git a/apps/openmw/mwvr/vrinput.cpp b/apps/openmw/mwvr/vrinput.cpp index 4178370ac..8e38b21bf 100644 --- a/apps/openmw/mwvr/vrinput.cpp +++ b/apps/openmw/mwvr/vrinput.cpp @@ -31,7 +31,7 @@ PoseAction::PoseAction(std::unique_ptr xrAction) createInfo.action = *mXRAction; createInfo.poseInActionSpace.orientation.w = 1.f; createInfo.subactionPath = XR_NULL_PATH; - CHECK_XRCMD(xrCreateActionSpace(xr->impl().mSession, &createInfo, &mXRSpace)); + CHECK_XRCMD(xrCreateActionSpace(xr->impl().xrSession(), &createInfo, &mXRSpace)); } void PoseAction::update(long long time) @@ -39,7 +39,7 @@ void PoseAction::update(long long time) mPrevious = mValue; auto* xr = Environment::get().getManager(); - XrSpace referenceSpace = xr->impl().getReferenceSpace(TrackedSpace::STAGE); + XrSpace referenceSpace = xr->impl().getReferenceSpace(ReferenceSpace::STAGE); XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION }; XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY }; @@ -51,8 +51,8 @@ void PoseAction::update(long long time) location.pose.orientation.w = 1; mValue = Pose{ - osg::fromXR(location.pose.position), - osg::fromXR(location.pose.orientation) + fromXR(location.pose.position), + fromXR(location.pose.orientation) }; } diff --git a/apps/openmw/mwvr/vrsession.cpp b/apps/openmw/mwvr/vrsession.cpp index 8e5eb6339..fdc0b128f 100644 --- a/apps/openmw/mwvr/vrsession.cpp +++ b/apps/openmw/mwvr/vrsession.cpp @@ -94,7 +94,7 @@ osg::Matrix VRSession::viewMatrix(FramePhase phase, Side side) bool VRSession::isRunning() const { auto* xr = Environment::get().getManager(); - return xr->sessionRunning(); + return xr->xrSessionRunning(); } void VRSession::swapBuffers(osg::GraphicsContext* gc, VRViewer& viewer) @@ -254,13 +254,13 @@ void VRSession::prepareFrame() PoseSet predictedPoses{}; xr->enablePredictions(); - predictedPoses.head = xr->getPredictedHeadPose(predictedDisplayTime, TrackedSpace::STAGE) * mPlayerScale; - auto hmdViews = xr->getPredictedViews(predictedDisplayTime, TrackedSpace::VIEW); + predictedPoses.head = xr->getPredictedHeadPose(predictedDisplayTime, ReferenceSpace::STAGE) * mPlayerScale; + auto hmdViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::VIEW); predictedPoses.view[(int)Side::LEFT_SIDE].pose = hmdViews[(int)Side::LEFT_SIDE].pose * mPlayerScale; predictedPoses.view[(int)Side::RIGHT_SIDE].pose = hmdViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale; predictedPoses.view[(int)Side::LEFT_SIDE].fov = hmdViews[(int)Side::LEFT_SIDE].fov; predictedPoses.view[(int)Side::RIGHT_SIDE].fov = hmdViews[(int)Side::RIGHT_SIDE].fov; - auto stageViews = xr->getPredictedViews(predictedDisplayTime, TrackedSpace::STAGE); + auto stageViews = xr->getPredictedViews(predictedDisplayTime, ReferenceSpace::STAGE); predictedPoses.eye[(int)Side::LEFT_SIDE] = stageViews[(int)Side::LEFT_SIDE].pose * mPlayerScale; predictedPoses.eye[(int)Side::RIGHT_SIDE] = stageViews[(int)Side::RIGHT_SIDE].pose * mPlayerScale; diff --git a/apps/openmw/mwvr/vrsession.hpp b/apps/openmw/mwvr/vrsession.hpp index 0051a5264..e57b45249 100644 --- a/apps/openmw/mwvr/vrsession.hpp +++ b/apps/openmw/mwvr/vrsession.hpp @@ -17,6 +17,8 @@ namespace MWVR extern void getEulerAngles(const osg::Quat& quat, float& yaw, float& pitch, float& roll); +//! Manages VR logic, such as managing frames, predicting their poses, and handling frame synchronization. +//! Should not be confused with the openxr session object. class VRSession { public: diff --git a/apps/openmw/mwvr/vrtexture.cpp b/apps/openmw/mwvr/vrtexture.cpp index 155073a28..88dc99709 100644 --- a/apps/openmw/mwvr/vrtexture.cpp +++ b/apps/openmw/mwvr/vrtexture.cpp @@ -13,52 +13,56 @@ namespace MWVR { - VRTexture::VRTexture( - osg::ref_ptr state, - std::size_t width, - std::size_t height, - uint32_t msaaSamples) + + VRTexture::VRTexture(osg::ref_ptr state, std::size_t width, std::size_t height, uint32_t msaaSamples, uint32_t colorBuffer, uint32_t depthBuffer) : mState(state) , mWidth(width) , mHeight(height) , mSamples(msaaSamples) + , mColorBuffer(colorBuffer) + , mDepthBuffer(depthBuffer) { - auto* gl = osg::GLExtensions::Get(state->getContextID(), false); gl->glGenFramebuffers(1, &mBlitFBO); gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mBlitFBO); gl->glGenFramebuffers(1, &mFBO); - glGenTextures(1, &mDepthBuffer); - glGenTextures(1, &mColorBuffer); - if (mSamples == 0) + if (mSamples <= 1) mTextureTarget = GL_TEXTURE_2D; else mTextureTarget = GL_TEXTURE_2D_MULTISAMPLE; - glBindTexture(mTextureTarget, mColorBuffer); - if (mSamples == 0) - glTexImage2D(mTextureTarget, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_INT, nullptr); - else - gl->glTexImage2DMultisample(mTextureTarget, mSamples, GL_RGBA, mWidth, mHeight, false); - glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB); - glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB); - glTexParameteri(mTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(mTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(mTextureTarget, GL_TEXTURE_MAX_LEVEL, 0); + if (mColorBuffer == 0) + { + glGenTextures(1, &mColorBuffer); + glBindTexture(mTextureTarget, mColorBuffer); + if (mSamples <= 1) + glTexImage2D(mTextureTarget, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_INT, nullptr); + else + gl->glTexImage2DMultisample(mTextureTarget, mSamples, GL_RGBA, mWidth, mHeight, false); + glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB); + glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB); + glTexParameteri(mTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(mTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(mTextureTarget, GL_TEXTURE_MAX_LEVEL, 0); + } - glBindTexture(mTextureTarget, mDepthBuffer); - if (mSamples == 0) - glTexImage2D(mTextureTarget, 0, GL_DEPTH_COMPONENT24, mWidth, mHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); - else - gl->glTexImage2DMultisample(mTextureTarget, mSamples, GL_DEPTH_COMPONENT, mWidth, mHeight, false); - glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(mTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(mTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(mTextureTarget, GL_TEXTURE_MAX_LEVEL, 0); + if (mDepthBuffer == 0) + { + glGenTextures(1, &mDepthBuffer); + glBindTexture(mTextureTarget, mDepthBuffer); + if (mSamples <= 1) + glTexImage2D(mTextureTarget, 0, GL_DEPTH_COMPONENT24, mWidth, mHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + else + gl->glTexImage2DMultisample(mTextureTarget, mSamples, GL_DEPTH_COMPONENT, mWidth, mHeight, false); + glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(mTextureTarget, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(mTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(mTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(mTextureTarget, GL_TEXTURE_MAX_LEVEL, 0); + } gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO); diff --git a/apps/openmw/mwvr/vrtexture.hpp b/apps/openmw/mwvr/vrtexture.hpp index 10697cf5f..7aff59429 100644 --- a/apps/openmw/mwvr/vrtexture.hpp +++ b/apps/openmw/mwvr/vrtexture.hpp @@ -13,7 +13,7 @@ namespace MWVR class VRTexture : public osg::Referenced { public: - VRTexture(osg::ref_ptr state, std::size_t width, std::size_t height, uint32_t msaaSamples); + VRTexture(osg::ref_ptr state, std::size_t width, std::size_t height, uint32_t msaaSamples, uint32_t colorBuffer = 0, uint32_t depthBuffer = 0); ~VRTexture(); void destroy(osg::State* state); diff --git a/apps/openmw/mwvr/vrtypes.cpp b/apps/openmw/mwvr/vrtypes.cpp index 93110b1f5..8fd9afe5e 100644 --- a/apps/openmw/mwvr/vrtypes.cpp +++ b/apps/openmw/mwvr/vrtypes.cpp @@ -172,13 +172,13 @@ std::ostream& operator <<( std::ostream& operator <<( std::ostream& os, - TrackedSpace limb) + ReferenceSpace limb) { switch (limb) { - case TrackedSpace::STAGE: + case ReferenceSpace::STAGE: os << "STAGE"; break; - case TrackedSpace::VIEW: + case ReferenceSpace::VIEW: os << "VIEW"; break; } return os; diff --git a/apps/openmw/mwvr/vrtypes.hpp b/apps/openmw/mwvr/vrtypes.hpp index ca7c8fd48..b4ee7c3a3 100644 --- a/apps/openmw/mwvr/vrtypes.hpp +++ b/apps/openmw/mwvr/vrtypes.hpp @@ -24,7 +24,7 @@ enum class TrackedLimb }; //! Describes what space to track the limb in -enum class TrackedSpace +enum class ReferenceSpace { STAGE = 0, //!< Track limb in the VR stage space. Meaning a space with a floor level origin and fixed horizontal orientation. VIEW = 1 //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation. @@ -58,7 +58,7 @@ struct Pose bool operator==(const Pose& rhs) const; }; -//! Represents the field of view of an eye +//! Fov of a single eye struct FieldOfView { float angleLeft; float angleRight; @@ -97,13 +97,23 @@ struct CompositionLayerProjectionView FieldOfView fov; }; +struct SwapchainConfig +{ + uint32_t recommendedWidth = -1; + uint32_t maxWidth = -1; + uint32_t recommendedHeight = -1; + uint32_t maxHeight = -1; + uint32_t recommendedSamples = -1; + uint32_t maxSamples = -1; +}; + // Serialization methods for VR types. std::ostream& operator <<(std::ostream& os, const Pose& pose); std::ostream& operator <<(std::ostream& os, const FieldOfView& fov); std::ostream& operator <<(std::ostream& os, const View& view); std::ostream& operator <<(std::ostream& os, const PoseSet& poseSet); std::ostream& operator <<(std::ostream& os, TrackedLimb limb); -std::ostream& operator <<(std::ostream& os, TrackedSpace space); +std::ostream& operator <<(std::ostream& os, ReferenceSpace space); std::ostream& operator <<(std::ostream& os, Side side); } diff --git a/apps/openmw/mwvr/vrview.cpp b/apps/openmw/mwvr/vrview.cpp index 8d3a5cb16..3f8ed86f1 100644 --- a/apps/openmw/mwvr/vrview.cpp +++ b/apps/openmw/mwvr/vrview.cpp @@ -26,7 +26,7 @@ namespace MWVR { VRView::VRView( std::string name, - OpenXRSwapchain::Config config, + SwapchainConfig config, osg::ref_ptr state) : mSwapchainConfig{ config } , mSwapchain(new OpenXRSwapchain(state, mSwapchainConfig)) diff --git a/apps/openmw/mwvr/vrview.hpp b/apps/openmw/mwvr/vrview.hpp index 93fce45a7..b5b951757 100644 --- a/apps/openmw/mwvr/vrview.hpp +++ b/apps/openmw/mwvr/vrview.hpp @@ -36,7 +36,7 @@ namespace MWVR }; public: - VRView(std::string name, OpenXRSwapchain::Config config, osg::ref_ptr state); + VRView(std::string name, SwapchainConfig config, osg::ref_ptr state); virtual ~VRView(); public: @@ -52,7 +52,7 @@ namespace MWVR void swapBuffers(osg::GraphicsContext* gc); public: - OpenXRSwapchain::Config mSwapchainConfig; + SwapchainConfig mSwapchainConfig; std::unique_ptr mSwapchain; std::string mName{}; bool mRendering{ false }; diff --git a/apps/openmw/mwvr/vrviewer.cpp b/apps/openmw/mwvr/vrviewer.cpp index c326f1e48..57d1cd323 100644 --- a/apps/openmw/mwvr/vrviewer.cpp +++ b/apps/openmw/mwvr/vrviewer.cpp @@ -67,17 +67,10 @@ namespace MWVR xr->handleEvents(); - OpenXRSwapchain::Config leftConfig; - leftConfig.width = xr->impl().mConfigViews[(int)Side::LEFT_SIDE].recommendedImageRectWidth; - leftConfig.height = xr->impl().mConfigViews[(int)Side::LEFT_SIDE].recommendedImageRectHeight; - leftConfig.samples = xr->impl().mConfigViews[(int)Side::LEFT_SIDE].recommendedSwapchainSampleCount; - OpenXRSwapchain::Config rightConfig; - rightConfig.width = xr->impl().mConfigViews[(int)Side::RIGHT_SIDE].recommendedImageRectWidth; - rightConfig.height = xr->impl().mConfigViews[(int)Side::RIGHT_SIDE].recommendedImageRectHeight; - rightConfig.samples = xr->impl().mConfigViews[(int)Side::RIGHT_SIDE].recommendedSwapchainSampleCount; + auto config = xr->getRecommendedSwapchainConfig(); - auto leftView = new VRView("LeftEye", leftConfig, context->getState()); - auto rightView = new VRView("RightEye", rightConfig, context->getState()); + auto leftView = new VRView("LeftEye", config[(int)Side::LEFT_SIDE], context->getState()); + auto rightView = new VRView("RightEye", config[(int)Side::RIGHT_SIDE], context->getState()); mViews["LeftEye"] = leftView; mViews["RightEye"] = rightView; @@ -118,15 +111,7 @@ namespace MWVR mViewer->setReleaseContextAtEndOfFrameHint(false); - - OpenXRSwapchain::Config config; - config.width = mainCamera->getViewport()->width(); - config.height = mainCamera->getViewport()->height(); - config.samples = 1; - - // Mirror texture doesn't have to be an OpenXR swapchain. - // It was just convenient at the time. - mMirrorTextureSwapchain.reset(new OpenXRSwapchain(context->getState(), config)); + mMirrorTexture.reset(new VRTexture(context->getState(), mainCamera->getViewport()->width(), mainCamera->getViewport()->height(), 0)); mViewer->getSlave(0)._updateSlaveCallback = new VRView::UpdateSlaveCallback(leftView, context); mViewer->getSlave(1)._updateSlaveCallback = new VRView::UpdateSlaveCallback(rightView, context); @@ -151,18 +136,16 @@ namespace MWVR void VRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc) { - int mirror_width = mMirrorTextureSwapchain->width() / 2; - mMirrorTextureSwapchain->beginFrame(gc); - - mViews["RightEye"]->swapchain().renderBuffer()->blit(gc, 0, 0, mirror_width, mMirrorTextureSwapchain->height()); - mViews["LeftEye"]->swapchain().renderBuffer()->blit(gc, mirror_width, 0, 2 * mirror_width, mMirrorTextureSwapchain->height()); - - mMirrorTextureSwapchain->endFrame(gc); - auto* state = gc->getState(); auto* gl = osg::GLExtensions::Get(state->getContextID(), false); + int mirror_width = mMirrorTexture->width() / 2; + mMirrorTexture->beginFrame(gc); + + mViews["RightEye"]->swapchain().renderBuffer()->blit(gc, 0, 0, mirror_width, mMirrorTexture->height()); + mViews["LeftEye"]->swapchain().renderBuffer()->blit(gc, mirror_width, 0, 2 * mirror_width, mMirrorTexture->height()); + gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); - mMirrorTextureSwapchain->renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height()); + mMirrorTexture->blit(gc, 0, 0, mMirrorTexture->width(), mMirrorTexture->height()); } diff --git a/apps/openmw/mwvr/vrviewer.hpp b/apps/openmw/mwvr/vrviewer.hpp index 25cf53304..fa52f499a 100644 --- a/apps/openmw/mwvr/vrviewer.hpp +++ b/apps/openmw/mwvr/vrviewer.hpp @@ -91,8 +91,7 @@ namespace MWVR osg::ref_ptr mPreDraw{ nullptr }; osg::ref_ptr mPostDraw{ nullptr }; osg::GraphicsContext* mMainCameraGC{ nullptr }; - - std::unique_ptr mMirrorTextureSwapchain{ nullptr }; + std::unique_ptr mMirrorTexture{ nullptr }; std::mutex mMutex;