1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-15 16:39:40 +00:00

Simplified render timing by separating rendering from the openxr swapchain, and instead blitting and submitting separately

This commit is contained in:
Mads Buvik Sandvei 2020-01-26 20:06:47 +01:00
parent 51125d4f3e
commit 951879240c
17 changed files with 449 additions and 547 deletions

View file

@ -731,12 +731,12 @@ void OMW::Engine::go()
mXRViewer->addChild(root);
mViewer->setSceneData(mXRViewer);
#ifndef _NDEBUG
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::HEAD, MWVR::TrackedSpace::STAGE));
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::HEAD, MWVR::TrackedSpace::VIEW));
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::LEFT_HAND, MWVR::TrackedSpace::STAGE));
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::LEFT_HAND, MWVR::TrackedSpace::VIEW));
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::RIGHT_HAND, MWVR::TrackedSpace::STAGE));
mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::RIGHT_HAND, MWVR::TrackedSpace::VIEW));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::HEAD, MWVR::TrackedSpace::STAGE));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::HEAD, MWVR::TrackedSpace::VIEW));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::LEFT_HAND, MWVR::TrackedSpace::STAGE));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::LEFT_HAND, MWVR::TrackedSpace::VIEW));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::RIGHT_HAND, MWVR::TrackedSpace::STAGE));
//mXR->addPoseUpdateCallback(new MWVR::PoseLogger(MWVR::TrackedLimb::RIGHT_HAND, MWVR::TrackedSpace::VIEW));
#endif
#endif

View file

@ -515,7 +515,7 @@ namespace MWVR
OpenXRInputManagerImpl::updateHandTracking()
{
for (auto hand : { LEFT_HAND, RIGHT_HAND }) {
CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().mFrameState.predictedDisplayTime, &mHandSpaceLocation[hand]));
CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().predictedDisplayTime(OpenXRFrameIndexer::instance().updateIndex()), &mHandSpaceLocation[hand]));
}
}

View file

@ -67,10 +67,10 @@ namespace MWVR
return impl().waitFrame();
}
void OpenXRManager::beginFrame(long long frameIndex)
void OpenXRManager::beginFrame()
{
if (realized())
return impl().beginFrame(frameIndex);
return impl().beginFrame();
}
void OpenXRManager::endFrame()
@ -85,12 +85,6 @@ namespace MWVR
return impl().updateControls();
}
void OpenXRManager::updatePoses()
{
if (realized())
return impl().updatePoses();
}
void
OpenXRManager::realize(
osg::GraphicsContext* gc)
@ -111,14 +105,6 @@ namespace MWVR
}
}
void OpenXRManager::addPoseUpdateCallback(
osg::ref_ptr<PoseUpdateCallback> cb)
{
if (realized())
return impl().addPoseUpdateCallback(cb);
}
int OpenXRManager::eyes()
{
if (realized())
@ -146,50 +132,58 @@ namespace MWVR
}
void OpenXRManager::viewerBarrier()
//#ifndef _NDEBUG
// void PoseLogger::operator()(MWVR::Pose pose)
// {
// const char* limb = nullptr;
// const char* space = nullptr;
// switch (mLimb)
// {
// case TrackedLimb::HEAD:
// limb = "HEAD"; break;
// case TrackedLimb::LEFT_HAND:
// limb = "LEFT_HAND"; break;
// case TrackedLimb::RIGHT_HAND:
// limb = "RIGHT_HAND"; break;
// }
// switch (mSpace)
// {
// case TrackedSpace::STAGE:
// space = "STAGE"; break;
// case TrackedSpace::VIEW:
// space = "VIEW"; break;
// }
//
// //TODO: Use a different output to avoid spamming the debug log when enabled
// Log(Debug::Verbose) << space << "." << limb << ": " << pose;
// }
//#endif
static OpenXRFrameIndexer g_OpenXRFrameIndexer;
OpenXRFrameIndexer& OpenXRFrameIndexer::instance()
{
if (realized())
return impl().viewerBarrier();
return g_OpenXRFrameIndexer;
}
void OpenXRManager::registerToBarrier()
int64_t OpenXRFrameIndexer::advanceUpdateIndex()
{
if (realized())
return impl().registerToBarrier();
std::unique_lock<std::mutex> lock(mMutex);
mUpdateIndex++;
Log(Debug::Verbose) << "Advancing update index to " << mUpdateIndex;
assert(mUpdateIndex > mRenderIndex);
return mUpdateIndex;
}
void OpenXRManager::unregisterFromBarrier()
int64_t OpenXRFrameIndexer::advanceRenderIndex()
{
if (realized())
return impl().unregisterFromBarrier();
std::unique_lock<std::mutex> lock(mMutex);
mRenderIndex++;
Log(Debug::Verbose) << "Advancing frame index to " << mRenderIndex;
if (!(mUpdateIndex >= mRenderIndex))
mUpdateIndex = mRenderIndex - 1;
return mRenderIndex;
}
#ifndef _NDEBUG
void PoseLogger::operator()(MWVR::Pose pose)
{
const char* limb = nullptr;
const char* space = nullptr;
switch (mLimb)
{
case TrackedLimb::HEAD:
limb = "HEAD"; break;
case TrackedLimb::LEFT_HAND:
limb = "LEFT_HAND"; break;
case TrackedLimb::RIGHT_HAND:
limb = "RIGHT_HAND"; break;
}
switch (mSpace)
{
case TrackedSpace::STAGE:
space = "STAGE"; break;
case TrackedSpace::VIEW:
space = "VIEW"; break;
}
//TODO: Use a different output to avoid spamming the debug log when enabled
Log(Debug::Verbose) << space << "." << limb << ": " << pose;
}
#endif
}
std::ostream& operator <<(

View file

@ -6,6 +6,7 @@
#include <memory>
#include <mutex>
#include <components/debug/debuglog.hpp>
#include <components/sdlutil/sdlgraphicswindow.hpp>
#include <components/settings/settings.hpp>
#include <osg/Camera>
@ -17,6 +18,23 @@ struct XrSwapchainSubImage;
namespace MWVR
{
struct Timer
{
Timer(std::string name) : mName(name)
{
mBegin = std::chrono::steady_clock::now();
}
~Timer()
{
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::duration<double>>(end - mBegin);
Log(Debug::Verbose) << mName << "Elapsed: " << elapsed.count() << "s";
}
std::chrono::steady_clock::time_point mBegin;
std::string mName;
};
//! Represents the pose of a limb in VR space.
struct Pose
{
@ -43,25 +61,32 @@ namespace MWVR
VIEW //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation.
};
struct OpenXRFrameIndexer
{
static OpenXRFrameIndexer& instance();
OpenXRFrameIndexer() = default;
~OpenXRFrameIndexer() = default;
int64_t advanceUpdateIndex();
int64_t renderIndex() { return mRenderIndex; }
int64_t advanceRenderIndex();
int64_t updateIndex() { return mUpdateIndex; }
std::mutex mMutex{};
int64_t mUpdateIndex{ -1 };
int64_t mRenderIndex{ -1 };
};
// Use the pimpl pattern to avoid cluttering the namespace with openxr dependencies.
class OpenXRManagerImpl;
class OpenXRManager : public osg::Referenced
{
public:
class PoseUpdateCallback: public osg::Referenced
{
public:
PoseUpdateCallback(TrackedLimb limb, TrackedSpace space)
: mLimb(limb), mSpace(space){}
virtual void operator()(MWVR::Pose pose) = 0;
TrackedLimb mLimb;
TrackedSpace mSpace;
};
public:
class RealizeOperation : public osg::GraphicsOperation
{
@ -96,24 +121,14 @@ namespace MWVR
void handleEvents();
void waitFrame();
void beginFrame(long long frameIndex);
void beginFrame();
void endFrame();
void updateControls();
void updatePoses();
void realize(osg::GraphicsContext* gc);
void addPoseUpdateCallback(osg::ref_ptr<PoseUpdateCallback> cb);
int eyes();
//! A barrier used internally to ensure all views have released their frames before endFrame can complete.
void viewerBarrier();
//! Increments the target viewer counter of the barrier
void registerToBarrier();
//! Decrements the target viewer counter of the barrier
void unregisterFromBarrier();
OpenXRManagerImpl& impl() { return *mPrivate; }
private:
@ -121,16 +136,6 @@ namespace MWVR
std::mutex mMutex;
using lock_guard = std::lock_guard<std::mutex>;
};
#ifndef _NDEBUG
class PoseLogger : public OpenXRManager::PoseUpdateCallback
{
public:
PoseLogger(TrackedLimb limb, TrackedSpace space)
: OpenXRManager::PoseUpdateCallback(limb, space) {};
void operator()(MWVR::Pose pose) override;
};
#endif
}
std::ostream& operator <<(std::ostream& os, const MWVR::Pose& pose);

View file

@ -99,7 +99,6 @@ namespace MWVR
XrSessionCreateInfo createInfo{ XR_TYPE_SESSION_CREATE_INFO };
createInfo.next = &mGraphicsBinding;
createInfo.systemId = mSystemId;
createInfo.createFlags;
CHECK_XRCMD(xrCreateSession(mInstance, &createInfo, &mSession));
assert(mSession);
}
@ -117,13 +116,6 @@ namespace MWVR
CHECK_XRCMD(xrCreateReferenceSpace(mSession, &createInfo, &mReferenceSpaceStage));
}
{ // Set up layers
//mLayer.space = mReferenceSpaceStage;
//mLayer.viewCount = (uint32_t)mProjectionLayerViews.size();
//mLayer.views = mProjectionLayerViews.data();
}
{ // Read and log graphics properties for the swapchain
xrGetSystemProperties(mInstance, mSystemId, &mSystemProperties);
@ -142,32 +134,32 @@ namespace MWVR
uint32_t viewCount = 0;
CHECK_XRCMD(xrEnumerateViewConfigurationViews(mInstance, mSystemId, mViewConfigType, 2, &viewCount, mConfigViews.data()));
// OpenXR gives me crazy bananas high resolutions. Likely an oculus bug.
mConfigViews[0].recommendedImageRectHeight = 1200;
mConfigViews[1].recommendedImageRectHeight = 1200;
mConfigViews[0].recommendedImageRectWidth = 1080;
mConfigViews[1].recommendedImageRectWidth = 1080;
if (viewCount != 2)
{
std::stringstream ss;
ss << "xrEnumerateViewConfigurationViews returned " << viewCount << " views";
Log(Debug::Verbose) << ss.str();
}
// TODO: This, including the projection layer views, should be moved to openxrviewer
//for (unsigned i = 0; i < 2; i++)
//{
// mEyes[i].reset(new OpenXRView(this, mConfigViews[i]));
// mProjectionLayerViews[i].subImage.swapchain = mEyes[i]->mSwapchain;
// mProjectionLayerViews[i].subImage.imageRect.offset = { 0, 0 };
// mProjectionLayerViews[i].subImage.imageRect.extent = { mEyes[i]->mWidth, mEyes[i]->mHeight };
//}
}
}
inline XrResult CheckXrResult(XrResult res, const char* originator, const char* sourceLocation) {
if (XR_FAILED(res)) {
Log(Debug::Error) << sourceLocation << ": OpenXR[" << to_string(res) << "]: " << originator;
std::stringstream ss;
ss << sourceLocation << ": OpenXR[" << to_string(res) << "]: " << originator;
Log(Debug::Error) << ss.str();
throw std::runtime_error(ss.str().c_str());
}
else
{
Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << to_string(res) << "][" << std::this_thread::get_id() << "][" << wglGetCurrentDC() << "][" << wglGetCurrentContext() << "]: " << originator;
// Log(Debug::Verbose) << sourceLocation << ": OpenXR[" << to_string(res) << "][" << std::this_thread::get_id() << "][" << wglGetCurrentDC() << "][" << wglGetCurrentContext() << "]: " << originator;
}
return res;
@ -265,123 +257,59 @@ namespace MWVR
void
OpenXRManagerImpl::waitFrame()
{
Timer timer("waitFrame()");
// In some implementations xrWaitFrame might not return immediately when it should.
// So i let it wait in a separate thread. xrBeginFrame() should wait on xrWaitFrame()
// and xrWaitFrame() doesn't happen again until xrEndFrame() so synchronization is not necessary.
std::thread([this]() {
static std::mutex waitFrameMutex;
std::unique_lock<std::mutex> lock(waitFrameMutex);
if (!mSessionRunning)
return;
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
mFrameState = frameState;
mTimeKeeper.progressToNextFrame(frameState);
}).detach();
}
void
OpenXRManagerImpl::beginFrame(long long frameIndex)
OpenXRManagerImpl::beginFrame()
{
Log(Debug::Verbose) << "frameIndex = " << frameIndex;
if (!mSessionRunning)
return;
std::unique_lock<std::mutex> lock(mFrameStatusMutex);
// We need to wait for the frame to become idle or ready
// (There is no guarantee osg won't get us here before endFrame() returns)
while (mFrameStatus == OPENXR_FRAME_STATUS_ENDING || mFrameIndex < frameIndex)
mFrameStatusSignal.wait(lock);
if (mFrameStatus == OPENXR_FRAME_STATUS_IDLE)
{
Log(Debug::Verbose) << "beginFrame()";
handleEvents();
waitFrame();
Timer timer("beginFrame");
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
updateControls();
updatePoses();
mFrameStatus = OPENXR_FRAME_STATUS_READY;
}
assert(mFrameStatus == OPENXR_FRAME_STATUS_READY);
}
void OpenXRManagerImpl::viewerBarrier()
{
std::unique_lock<std::mutex> lock(mBarrierMutex);
mBarrier++;
Log(Debug::Verbose) << "mBarrier=" << mBarrier << ", tid=" << std::this_thread::get_id();
if (mBarrier == mNBarrier)
{
std::unique_lock<std::mutex> lock(mFrameStatusMutex);
mFrameStatus = OPENXR_FRAME_STATUS_ENDING;
mFrameStatusSignal.notify_all();
//mBarrierSignal.notify_all();
mBarrier = 0;
}
//else
//{
// mBarrierSignal.wait(lock, [this]() { return mBarrier == mNBarrier; });
//}
}
void OpenXRManagerImpl::registerToBarrier()
{
std::unique_lock<std::mutex> lock(mBarrierMutex);
mNBarrier++;
}
void OpenXRManagerImpl::unregisterFromBarrier()
{
std::unique_lock<std::mutex> lock(mBarrierMutex);
mNBarrier--;
assert(mNBarrier >= 0);
}
void
OpenXRManagerImpl::endFrame()
{
Timer timer("endFrame()");
if (!mSessionRunning)
return;
std::unique_lock<std::mutex> lock(mFrameStatusMutex);
while(mFrameStatus != OPENXR_FRAME_STATUS_ENDING)
mFrameStatusSignal.wait(lock);
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
frameEndInfo.displayTime = mFrameState.predictedDisplayTime;
frameEndInfo.displayTime = mTimeKeeper.predictedDisplayTime(OpenXRFrameIndexer::instance().renderIndex());
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
//frameEndInfo.layerCount = (uint32_t)1;
//frameEndInfo.layers = &mLayer_p;
frameEndInfo.layerCount = mLayerStack.layerCount();
frameEndInfo.layers = mLayerStack.layerHeaders();
CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
mFrameStatus = OPENXR_FRAME_STATUS_IDLE;
mFrameIndex++;
mFrameStatusSignal.notify_all();
}
std::array<XrView, 2> OpenXRManagerImpl::getStageViews()
std::array<XrView, 2>
OpenXRManagerImpl::getPredictedViews(
int64_t frameIndex,
TrackedSpace space)
{
// Get the pose of each eye in the "stage" reference space.
// TODO: I likely won't ever use this, since it is only useful if the game world and the
// stage space have matching orientation, which is extremely unlikely. Instead we have
// to keep track of the head pose separately and move our world position based on progressive
// changes.
// In more detail:
// When we apply yaw to the game world to allow free rotation of the character, the orientation
// of the stage space and our world space deviates which breaks free movement.
//
// If we align the orientations by yawing the head pose, that yaw will happen around the origin
// of the stage space rather than the character, which will not be comfortable (or make any sense) to the player.
//
// If we align the orientations by yawing the view poses, the yaw will happen around the character
// as intended, but physically walking will move the player in the wrong direction.
//
// The solution that solves both problems is to yaw the view pose *and* progressively track changes in head pose
// in the stage space and yaw that change before adding it to our separately tracked pose.
std::array<XrView, 2> views{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
XrViewState viewState{ XR_TYPE_VIEW_STATE };
@ -389,34 +317,22 @@ namespace MWVR
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = mViewConfigType;
viewLocateInfo.displayTime = mFrameState.predictedDisplayTime;
viewLocateInfo.displayTime = mTimeKeeper.predictedDisplayTime(frameIndex);
switch (space)
{
case TrackedSpace::STAGE:
viewLocateInfo.space = mReferenceSpaceStage;
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data()));
return views;
}
std::array<XrView, 2> OpenXRManagerImpl::getHmdViews()
{
// Eye poses relative to the HMD rarely change. But they might
// if the user reconfigures his or her hmd during runtime, so we
// re-read this every frame anyway.
std::array<XrView, 2> views{ {{XR_TYPE_VIEW}, {XR_TYPE_VIEW}} };
XrViewState viewState{ XR_TYPE_VIEW_STATE };
uint32_t viewCount = 2;
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = mViewConfigType;
viewLocateInfo.displayTime = mFrameState.predictedDisplayTime;
break;
case TrackedSpace::VIEW:
viewLocateInfo.space = mReferenceSpaceView;
break;
}
CHECK_XRCMD(xrLocateViews(mSession, &viewLocateInfo, &viewState, viewCount, &viewCount, views.data()));
return views;
}
MWVR::Pose OpenXRManagerImpl::getLimbPose(TrackedLimb limb, TrackedSpace space)
MWVR::Pose OpenXRManagerImpl::getPredictedLimbPose(int64_t frameIndex, TrackedLimb limb, TrackedSpace space)
{
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
@ -444,7 +360,7 @@ namespace MWVR
referenceSpace = mReferenceSpaceView;
break;
}
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, mFrameState.predictedDisplayTime, &location));
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, mTimeKeeper.predictedDisplayTime(frameIndex), &location));
if (!(velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT))
Log(Debug::Warning) << "Unable to acquire linear velocity";
return MWVR::Pose{
@ -503,12 +419,13 @@ namespace MWVR
switch (newState)
{
case XR_SESSION_STATE_READY:
case XR_SESSION_STATE_IDLE:
//case XR_SESSION_STATE_IDLE:
{
XrSessionBeginInfo beginInfo{ XR_TYPE_SESSION_BEGIN_INFO };
beginInfo.primaryViewConfigurationType = mViewConfigType;
CHECK_XRCMD(xrBeginSession(mSession, &beginInfo));
mSessionRunning = true;
waitFrame();
break;
}
case XR_SESSION_STATE_STOPPING:
@ -522,41 +439,12 @@ namespace MWVR
}
}
void OpenXRManagerImpl::updatePoses()
XrTime OpenXRManagerImpl::predictedDisplayTime(int64_t frameIndex)
{
//auto oldHeadTrackedPose = mHeadTrackedPose;
//auto oldLefthandPose = mLeftHandTrackedPose;
//auto oldRightHandPose = mRightHandTrackedPose;
//mHeadTrackedPose = getLimbPose(TrackedLimb::HEAD);
//mLeftHandTrackedPose = getLimbPose(TrackedLimb::LEFT_HAND);
//mRightHandTrackedPose = getLimbPose(TrackedLimb::RIGHT_HAND);
//for (auto& cb : mPoseUpdateCallbacks)
//{
// switch (cb->mLimb)
// {
// case TrackedLimb::HEAD:
// (*cb)(mHeadTrackedPose); break;
// case TrackedLimb::LEFT_HAND:
// (*cb)(mLeftHandTrackedPose); break;
// case TrackedLimb::RIGHT_HAND:
// (*cb)(mRightHandTrackedPose); break;
// }
//}
for (auto& cb : mPoseUpdateCallbacks)
(*cb)(getLimbPose(cb->mLimb, cb->mSpace));
return mTimeKeeper.predictedDisplayTime(frameIndex);
}
void OpenXRManagerImpl::addPoseUpdateCallback(
osg::ref_ptr<PoseUpdateCallback> cb)
{
mPoseUpdateCallbacks.push_back(cb);
}
const XrEventDataBaseHeader*
OpenXRManagerImpl::nextEvent()
{
@ -587,6 +475,62 @@ namespace MWVR
{
return XrPosef{ osg::toXR(pose.orientation), osg::toXR(pose.position) };
}
XrTime OpenXRTimeKeeper::predictedDisplayTime(int64_t frameIndex)
{
std::unique_lock<std::mutex> lock(mMutex);
auto prediction = mPredictedFrameTime;
auto predictedPeriod = mPredictedPeriod;
auto futureFrames = frameIndex - OpenXRFrameIndexer::instance().renderIndex();
prediction += ( 0 + futureFrames) * predictedPeriod;
Log(Debug::Verbose) << "Predicted: displayTime[" << futureFrames << "]=" << prediction;
return prediction;
}
void OpenXRTimeKeeper::progressToNextFrame(XrFrameState frameState)
{
std::unique_lock<std::mutex> lock(mMutex);
OpenXRFrameIndexer::instance().advanceRenderIndex();
//XrDuration realPeriod = frameState.predictedDisplayPeriod;
//if(mFrameState.predictedDisplayTime != 0)
// realPeriod = frameState.predictedDisplayTime - mFrameState.predictedDisplayTime;
auto now = clock::now();
auto nanoseconds_elapsed = std::chrono::duration_cast<nanoseconds>(now - mLastFrame);
XrDuration realPeriod = nanoseconds_elapsed.count();
mFrameState = frameState;
mPredictedFrameTime = mFrameState.predictedDisplayTime;
mPredictedPeriod = mFrameState.predictedDisplayPeriod;
// Real fps is lower than expected fps
// Adjust predictions
// (Really wish OpenXR would handle this!)
if (realPeriod > mFrameState.predictedDisplayPeriod)
{
// predictedDisplayTime refers to the midpoint of the display period
// The upjustment must therefore only be half the magnitude
// mPredictedFrameTime += (realPeriod - mFrameState.predictedDisplayPeriod);
// mPredictedPeriod = realPeriod;
}
seconds elapsed = std::chrono::duration_cast<seconds>(now - mLastFrame);
std::swap(now, mLastFrame);
double fps = 1. / elapsed.count();
mFps = mFps * 0.8 + 0.2 * fps;
Log(Debug::Verbose) << "Render progressed to next frame: elapsed=" << elapsed.count() << ", fps=" << fps << ", ImplementationPeriod=" << mFrameState.predictedDisplayPeriod << " realPeriod=" << realPeriod;
Log(Debug::Verbose) << "Render progressed to next frame: predictedDisplayTime=" << mFrameState.predictedDisplayTime << ", mPredictedFrameTime=" << mPredictedFrameTime << ", mFps=" << mFps;
}
}
namespace osg

View file

@ -22,6 +22,7 @@
#include <map>
#include <iostream>
#include <thread>
#include <chrono>
namespace osg {
Vec3 fromXR(XrVector3f);
@ -43,10 +44,33 @@ namespace MWVR
MWVR::Pose fromXR(XrPosef pose);
XrPosef toXR(MWVR::Pose pose);
struct OpenXRTimeKeeper
{
using seconds = std::chrono::duration<double>;
using nanoseconds = std::chrono::nanoseconds;
using clock = std::chrono::steady_clock;
using time_point = clock::time_point;
OpenXRTimeKeeper() = default;
~OpenXRTimeKeeper() = default;
XrTime predictedDisplayTime(int64_t frameIndex);
void progressToNextFrame(XrFrameState frameState);
private:
XrFrameState mFrameState{ XR_TYPE_FRAME_STATE };
std::mutex mMutex{};
double mFps{ 0. };
time_point mLastFrame = clock::now();
XrTime mPredictedFrameTime{ 0 };
XrDuration mPredictedPeriod{ 0 };
};
struct OpenXRManagerImpl
{
using PoseUpdateCallback = OpenXRManager::PoseUpdateCallback;
OpenXRManagerImpl(void);
~OpenXRManagerImpl(void);
@ -56,17 +80,15 @@ namespace MWVR
const XrEventDataBaseHeader* nextEvent();
void waitFrame();
void beginFrame(long long frameIndex);
void beginFrame();
void endFrame();
std::array<XrView, 2> getStageViews();
std::array<XrView, 2> getHmdViews();
MWVR::Pose getLimbPose(TrackedLimb limb, TrackedSpace space);
std::array<XrView, 2> getPredictedViews(int64_t frameIndex, TrackedSpace mSpace);
MWVR::Pose getPredictedLimbPose(int64_t frameIndex, TrackedLimb limb, TrackedSpace space);
int eyes();
void handleEvents();
void updateControls();
void updatePoses();
void addPoseUpdateCallback(osg::ref_ptr<PoseUpdateCallback> cb);
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
XrTime predictedDisplayTime(int64_t frameIndex);
bool initialized = false;
long long mFrameIndex = 0;
@ -83,38 +105,11 @@ namespace MWVR
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
//XrCompositionLayerProjection mLayer{ XR_TYPE_COMPOSITION_LAYER_PROJECTION };
//XrCompositionLayerBaseHeader const* mLayer_p = reinterpret_cast<XrCompositionLayerBaseHeader*>(&mLayer);
//std::array<XrCompositionLayerProjectionView, 2> mProjectionLayerViews{ { {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW} } };
OpenXRTimeKeeper mTimeKeeper{};
OpenXRLayerStack mLayerStack{};
XrFrameState mFrameState{ XR_TYPE_FRAME_STATE };
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
bool mSessionRunning = false;
//osg::Pose mHeadTrackedPose{};
//osg::Pose mLeftHandTrackedPose{};
//osg::Pose mRightHandTrackedPose{};
std::vector< osg::ref_ptr<PoseUpdateCallback> > mPoseUpdateCallbacks{};
std::mutex mEventMutex{};
enum {
OPENXR_FRAME_STATUS_IDLE, //!< Frame is ready for initialization
OPENXR_FRAME_STATUS_READY, //!< Frame has been initialized and swapchains may be acquired
OPENXR_FRAME_STATUS_ENDING //!< All swapchains have been releazed, frame ready for presentation
} mFrameStatus{ OPENXR_FRAME_STATUS_IDLE };
std::condition_variable mFrameStatusSignal{};
std::mutex mFrameStatusMutex;
int mBarrier{ 0 };
int mNBarrier{ 0 };
std::condition_variable mBarrierSignal{};
std::mutex mBarrierMutex;
void viewerBarrier();
void registerToBarrier();
void unregisterFromBarrier();
};
}

View file

@ -6,7 +6,7 @@ namespace MWVR
{
OpenXRMenu::OpenXRMenu(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, const std::string& title, int width, int height, osg::Vec2 extent_meters)
: OpenXRView(XR)
: OpenXRView(XR, title)
, mTitle(title)
{
setWidth(width);
@ -27,7 +27,7 @@ namespace MWVR
// Orientation needs a norm of 1 to be accepted by OpenXR, so we default it to 0,0,0,1
mLayer->pose.orientation.w = 1.f;
updatePosition();
//updatePosition();
}
OpenXRMenu::~OpenXRMenu()
@ -47,10 +47,12 @@ namespace MWVR
return;
if (!mXR->sessionRunning())
return;
if (OpenXRFrameIndexer::instance().updateIndex() == 0)
return;
// Menus are position one meter in front of the player, facing the player.
// Go via osg since OpenXR doesn't distribute a linear maths library
auto pose = mXR->impl().getLimbPose(TrackedLimb::HEAD, TrackedSpace::STAGE);
auto pose = mXR->impl().getPredictedLimbPose(OpenXRFrameIndexer::instance().updateIndex(), TrackedLimb::HEAD, TrackedSpace::STAGE);
pose.position += pose.orientation * osg::Vec3(0, 0, -1);
mLayer->pose.position = osg::toXR(pose.position);
mLayer->pose.orientation = osg::toXR(-pose.orientation);

View file

@ -26,21 +26,20 @@ namespace MWVR {
OpenXRSwapchainImpl(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, OpenXRSwapchain::Config config);
~OpenXRSwapchainImpl();
osg::ref_ptr<OpenXRTextureBuffer> prepareNextSwapchainImage();
void releaseSwapchainImage();
void beginFrame(osg::GraphicsContext* gc);
void endFrame(osg::GraphicsContext* gc);
osg::ref_ptr<OpenXRManager> mXR;
XrSwapchain mSwapchain = XR_NULL_HANDLE;
std::vector<XrSwapchainImageOpenGLKHR> mSwapchainImageBuffers{};
std::vector<osg::ref_ptr<OpenXRTextureBuffer> > mTextureBuffers{};
//std::vector<osg::ref_ptr<OpenXRTextureBuffer> > mTextureBuffers{};
XrSwapchainSubImage mSubImage{};
int32_t mWidth = -1;
int32_t mHeight = -1;
int32_t mSamples = -1;
int64_t mSwapchainColorFormat = -1;
OpenXRTextureBuffer* mCurrentBuffer = nullptr;
uint32_t mFBO = 0;
OpenXRTextureBuffer* mRenderBuffer = nullptr;
};
OpenXRSwapchainImpl::OpenXRSwapchainImpl(
@ -99,8 +98,9 @@ namespace MWVR {
mSwapchainImageBuffers.resize(imageCount, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR });
CHECK_XRCMD(xrEnumerateSwapchainImages(mSwapchain, imageCount, &imageCount, reinterpret_cast<XrSwapchainImageBaseHeader*>(mSwapchainImageBuffers.data())));
for (const auto& swapchainImage : mSwapchainImageBuffers)
mTextureBuffers.push_back(new OpenXRTextureBuffer(state, swapchainImage.image, mWidth, mHeight, 0));
//for (const auto& swapchainImage : mSwapchainImageBuffers)
// mTextureBuffers.push_back(new OpenXRTextureBuffer(state, swapchainImage.image, mWidth, mHeight, 0));
mRenderBuffer = new OpenXRTextureBuffer(state, mWidth, mHeight, 0);
mSubImage.swapchain = mSwapchain;
mSubImage.imageRect.offset = { 0, 0 };
@ -113,11 +113,18 @@ namespace MWVR {
CHECK_XRCMD(xrDestroySwapchain(mSwapchain));
}
osg::ref_ptr<OpenXRTextureBuffer>
OpenXRSwapchainImpl::prepareNextSwapchainImage()
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
{
mRenderBuffer->beginFrame(gc);
}
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
{
Timer timer("Swapchain::endFrame");
// Blit frame to swapchain
if (!mXR->sessionRunning())
return nullptr;
return;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
uint32_t swapchainImageIndex = 0;
@ -127,32 +134,11 @@ namespace MWVR {
waitInfo.timeout = XR_INFINITE_DURATION;
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo));
return mTextureBuffers[swapchainImageIndex];
}
void
OpenXRSwapchainImpl::releaseSwapchainImage()
{
if (!mXR->sessionRunning())
return;
mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[swapchainImageIndex].image);
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
//mCurrentBuffer = nullptr;
}
void OpenXRSwapchainImpl::beginFrame(osg::GraphicsContext* gc)
{
mCurrentBuffer = prepareNextSwapchainImage();
if (mCurrentBuffer)
mCurrentBuffer->beginFrame(gc);
}
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
{
if (mCurrentBuffer)
mCurrentBuffer->endFrame(gc);
releaseSwapchainImage();
}
OpenXRSwapchain::OpenXRSwapchain(
@ -165,16 +151,6 @@ namespace MWVR {
{
}
osg::ref_ptr<OpenXRTextureBuffer> OpenXRSwapchain::prepareNextSwapchainImage()
{
return impl().prepareNextSwapchainImage();
}
void OpenXRSwapchain::releaseSwapchainImage()
{
impl().releaseSwapchainImage();
}
void OpenXRSwapchain::beginFrame(osg::GraphicsContext* gc)
{
impl().beginFrame(gc);
@ -206,8 +182,8 @@ namespace MWVR {
return impl().mSamples;
}
OpenXRTextureBuffer* OpenXRSwapchain::current()
OpenXRTextureBuffer* OpenXRSwapchain::renderBuffer()
{
return impl().mCurrentBuffer;
return impl().mRenderBuffer;
}
}

View file

@ -26,11 +26,6 @@ namespace MWVR
~OpenXRSwapchain();
public:
//! Get the next color buffer.
//! \return The GL texture ID of the now current swapchain image
osg::ref_ptr<OpenXRTextureBuffer> prepareNextSwapchainImage();
//! Release current color buffer. Do not forget to call this after rendering to the color buffer.
void releaseSwapchainImage();
//! Prepare for render (set FBO)
void beginFrame(osg::GraphicsContext* gc);
//! Finalize render
@ -44,7 +39,7 @@ namespace MWVR
//! Samples of the view surface
int samples();
//! Get the current texture
OpenXRTextureBuffer* current();
OpenXRTextureBuffer* renderBuffer();
//! Get the private implementation
OpenXRSwapchainImpl& impl() { return *mPrivate; }
//! Get the private implementation

View file

@ -15,85 +15,57 @@ namespace MWVR
{
OpenXRTextureBuffer::OpenXRTextureBuffer(
osg::ref_ptr<osg::State> state,
uint32_t XRColorBuffer,
std::size_t width,
std::size_t height,
uint32_t msaaSamples)
: mState(state)
, mWidth(width)
, mHeight(height)
, mXRColorBuffer(XRColorBuffer)
, mMSAASamples(msaaSamples)
, mSamples(msaaSamples)
{
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
glBindTexture(GL_TEXTURE_2D, XRColorBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GLint w = 0;
GLint h = 0;
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_WIDTH, &w);
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_HEIGHT, &h);
gl->glGenFramebuffers(1, &mBlitFBO);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mBlitFBO);
gl->glGenFramebuffers(1, &mFBO);
if (mMSAASamples == 0)
{
glGenTextures(1, &mDepthBuffer);
glBindTexture(GL_TEXTURE_2D, mDepthBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, mWidth, mHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glGenTextures(1, &mColorBuffer);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mXRColorBuffer, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, mDepthBuffer, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create OpenXR framebuffer");
}
if (mSamples == 0)
mTextureTarget = GL_TEXTURE_2D;
else
{
gl->glGenFramebuffers(1, &mMSAAFBO);
mTextureTarget = GL_TEXTURE_2D_MULTISAMPLE;
// Create MSAA color buffer
glGenTextures(1, &mMSAAColorBuffer);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mMSAAColorBuffer);
gl->glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, mMSAASamples, GL_RGBA, mWidth, mHeight, false);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER_ARB);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAX_LEVEL, 0);
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);
// Create MSAA depth buffer
glGenTextures(1, &mMSAADepthBuffer);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mMSAADepthBuffer);
gl->glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, mMSAASamples, GL_DEPTH_COMPONENT, mWidth, mHeight, false);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAX_LEVEL, 0);
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);
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mMSAAFBO);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D_MULTISAMPLE, mMSAAColorBuffer, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D_MULTISAMPLE, mMSAADepthBuffer, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create MSAA framebuffer");
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mXRColorBuffer, 0);
gl->glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, mTextureTarget, mColorBuffer, 0);
gl->glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, mTextureTarget, mDepthBuffer, 0);
if (gl->glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
throw std::runtime_error("Failed to create OpenXR framebuffer");
}
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
@ -117,56 +89,38 @@ namespace MWVR
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mFBO)
gl->glDeleteFramebuffers(1, &mFBO);
if (mMSAAFBO)
gl->glDeleteFramebuffers(1, &mMSAAFBO);
}
else if(mFBO || mMSAAFBO)
else if(mFBO)
// Without access to opengl methods, i'll just let the FBOs leak.
Log(Debug::Warning) << "destroy() called without a State. Leaking FBOs";
Log(Debug::Warning) << "destroy() called without a State. Leaking FBO";
if (mDepthBuffer)
glDeleteTextures(1, &mDepthBuffer);
if (mMSAAColorBuffer)
glDeleteTextures(1, &mMSAAColorBuffer);
if (mMSAADepthBuffer)
glDeleteTextures(1, &mMSAADepthBuffer);
if (mColorBuffer)
glDeleteTextures(1, &mColorBuffer);
mFBO = mMSAAFBO = mDepthBuffer = mMSAAColorBuffer = mMSAADepthBuffer = 0;
mFBO = mDepthBuffer = mColorBuffer;
}
void OpenXRTextureBuffer::beginFrame(osg::GraphicsContext* gc)
{
auto state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mMSAASamples == 0)
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mFBO);
}
else
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, mMSAAFBO);
}
}
void OpenXRTextureBuffer::endFrame(osg::GraphicsContext* gc)
void OpenXRTextureBuffer::endFrame(osg::GraphicsContext* gc, uint32_t blitTarget)
{
auto* state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
if (mMSAASamples == 0)
{
gl->glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
}
else
{
gl->glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, mMSAAFBO);
gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, mFBO);
gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, mBlitFBO);
gl->glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, mFBO);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, blitTarget, 0);
gl->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
gl->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
gl->glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, 0);
}
}
void OpenXRTextureBuffer::blit(osg::GraphicsContext* gc, int x, int y, int w, int h)
{

View file

@ -13,22 +13,23 @@ namespace MWVR
class OpenXRTextureBuffer : public osg::Referenced
{
public:
OpenXRTextureBuffer(osg::ref_ptr<osg::State> state, uint32_t XRColorBuffer, std::size_t width, std::size_t height, uint32_t msaaSamples);
OpenXRTextureBuffer(osg::ref_ptr<osg::State> state, std::size_t width, std::size_t height, uint32_t msaaSamples);
~OpenXRTextureBuffer();
void destroy(osg::State* state);
auto width() const { return mWidth; }
auto height() const { return mHeight; }
auto msaaSamples() const { return mMSAASamples; }
auto msaaSamples() const { return mSamples; }
void beginFrame(osg::GraphicsContext* gc);
void endFrame(osg::GraphicsContext* gc);
void endFrame(osg::GraphicsContext* gc, uint32_t blitTarget);
void writeToJpg(osg::State& state, std::string filename);
uint32_t fbo(void) const { return mFBO; }
//! Blit to region in currently bound draw fbo
void blit(osg::GraphicsContext* gc, int x, int y, int w, int h);
private:
@ -39,18 +40,13 @@ namespace MWVR
std::size_t mWidth = 0;
std::size_t mHeight = 0;
// Swapchain buffer
uint32_t mXRColorBuffer = 0;
// FBO target for swapchain buffer
uint32_t mDepthBuffer = 0;
// Render Target
uint32_t mFBO = 0;
// Render targets for MSAA
uint32_t mMSAASamples = 0;
uint32_t mMSAAFBO = 0;
uint32_t mMSAAColorBuffer = 0;
uint32_t mMSAADepthBuffer = 0;
uint32_t mBlitFBO = 0;
uint32_t mDepthBuffer = 0;
uint32_t mColorBuffer = 0;
uint32_t mSamples = 0;
uint32_t mTextureTarget = 0;
};
}

View file

@ -16,22 +16,22 @@
namespace MWVR {
OpenXRView::OpenXRView(
osg::ref_ptr<OpenXRManager> XR)
osg::ref_ptr<OpenXRManager> XR,
std::string name)
: mXR(XR)
, mSwapchain(nullptr)
, mSwapchainConfig{}
, mName(name)
{
mSwapchainConfig.requestedFormats = {
GL_RGBA8,
GL_RGBA8_SNORM,
};
mXR->registerToBarrier();
}
OpenXRView::~OpenXRView()
{
mXR->unregisterFromBarrier();
}
osg::Camera* OpenXRView::createCamera(int eye, const osg::Vec4& clearColor, osg::GraphicsContext* gc)
@ -48,7 +48,10 @@ namespace MWVR {
camera->setGraphicsContext(gc);
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
camera->setPreDrawCallback(new OpenXRView::PredrawCallback(camera.get(), this));
mPredraw = new OpenXRView::PredrawCallback(camera.get(), this);
camera->setPreDrawCallback(mPredraw);
camera->setFinalDrawCallback(new OpenXRView::PostdrawCallback(camera.get(), this));
return camera.release();
@ -71,18 +74,28 @@ namespace MWVR {
void OpenXRView::prerenderCallback(osg::RenderInfo& renderInfo)
{
Log(Debug::Verbose) << "prerenderCallback";
mXR->beginFrame(mFrameIndex);
Log(Debug::Verbose) << mName << ": prerenderCallback";
if (mSwapchain)
{
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
}
}
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
{
if (mSwapchain)
mSwapchain->endFrame(renderInfo.getState()->getGraphicsContext());
mXR->viewerBarrier();
mFrameIndex++;
// osg will sometimes call this without a corresponding prerender.
Log(Debug::Verbose) << mName << ": postrenderCallback";
Log(Debug::Verbose) << renderInfo.getCurrentCamera()->getName() << ": " << renderInfo.getCurrentCamera()->getPreDrawCallback();
if (renderInfo.getCurrentCamera()->getPreDrawCallback() != mPredraw)
{
// It seems OSG will sometimes overwrite the predraw callback.
// Undocumented behaviour?
renderInfo.getCurrentCamera()->setPreDrawCallback(mPredraw);
Log(Debug::Warning) << ("osg overwrote predraw");
}
//if (mSwapchain)
// mSwapchain->endFrame(renderInfo.getState()->getGraphicsContext());
}
bool OpenXRView::realize(osg::ref_ptr<osg::State> state)

View file

@ -1,6 +1,7 @@
#ifndef OPENXR_VIEW_HPP
#define OPENXR_VIEW_HPP
#include <cassert>
#include "openxrmanager.hpp"
#include "openxrswapchain.hpp"
@ -8,6 +9,7 @@ struct XrSwapchainSubImage;
namespace MWVR
{
class OpenXRView : public osg::Referenced
{
public:
@ -49,7 +51,7 @@ namespace MWVR
};
protected:
OpenXRView(osg::ref_ptr<OpenXRManager> XR);
OpenXRView(osg::ref_ptr<OpenXRManager> XR, std::string name);
virtual ~OpenXRView();
void setWidth(int width);
void setHeight(int height);
@ -66,14 +68,14 @@ namespace MWVR
OpenXRSwapchain& swapchain(void) { return *mSwapchain; }
//! Create the view surface
bool realize(osg::ref_ptr<osg::State> state);
//! Current frame being rendered
long long frameIndex() { return mFrameIndex; };
protected:
osg::ref_ptr<OpenXRManager> mXR;
std::unique_ptr<OpenXRSwapchain> mSwapchain;
OpenXRSwapchain::Config mSwapchainConfig;
long long mFrameIndex{ 0 };
std::string mName{};
bool mRendering{ false };
osg::ref_ptr<OpenXRView::PredrawCallback> mPredraw{ nullptr };
};
}

View file

@ -18,20 +18,6 @@ namespace MWVR
, mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW})
{
mViewer->setRealizeOperation(mRealizeOperation);
//auto* mainContext = mViewer->getCamera()->getGraphicsContext();
//assert(mainContext);
//auto* mainTraits = mainContext->getTraits();
//osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits(*mainTraits);
//traits->sharedContext = mainContext;
//mViewerGW = new SDLUtil::GraphicsWindowSDL2(traits);
//if (!mViewerGW->valid()) throw std::runtime_error("Failed to create GraphicsContext");
//mLeftGW = new SDLUtil::GraphicsWindowSDL2(traits);
//if (!mLeftGW->valid()) throw std::runtime_error("Failed to create GraphicsContext");
//mRightGW = new SDLUtil::GraphicsWindowSDL2(traits);
//if (!mRightGW->valid()) throw std::runtime_error("Failed to create GraphicsContext");
}
OpenXRViewer::~OpenXRViewer(void)
@ -50,7 +36,7 @@ namespace MWVR
const XrCompositionLayerBaseHeader*
OpenXRViewer::layer()
{
auto stageViews = mXR->impl().getStageViews();
auto stageViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().mRenderIndex, TrackedSpace::STAGE);
mCompositionLayerProjectionViews[0].pose = stageViews[0].pose;
mCompositionLayerProjectionViews[1].pose = stageViews[1].pose;
mCompositionLayerProjectionViews[0].fov = stageViews[0].fov;
@ -61,6 +47,8 @@ namespace MWVR
void OpenXRViewer::traversals()
{
Log(Debug::Verbose) << "Pre-Update";
mXR->handleEvents();
OpenXRFrameIndexer::instance().advanceUpdateIndex();
mViewer->updateTraversal();
Log(Debug::Verbose) << "Post-Update";
Log(Debug::Verbose) << "Pre-Rendering";
@ -89,12 +77,14 @@ namespace MWVR
// Use the main camera to render any GUI to the OpenXR GUI quad's swapchain.
// (When swapping the window buffer we'll blit the mirror texture to it instead.)
mMainCamera->setCullMask(MWRender::Mask_GUI);
mMainCamera->getGraphicsContext()->setSwapCallback(new OpenXRViewer::SwapBuffersCallback(this));
osg::Vec4 clearColor = mMainCamera->getClearColor();
mViews[OpenXRWorldView::LEFT_VIEW] = new OpenXRWorldView(mXR, context->getState(), mMetersPerUnit, OpenXRWorldView::LEFT_VIEW);
mViews[OpenXRWorldView::RIGHT_VIEW] = new OpenXRWorldView(mXR, context->getState(), mMetersPerUnit, OpenXRWorldView::RIGHT_VIEW);
if (!mXR->realized())
mXR->realize(context);
mViews[OpenXRWorldView::LEFT_VIEW] = new OpenXRWorldView(mXR, "LeftEye", context->getState(), mMetersPerUnit, OpenXRWorldView::LEFT_VIEW);
mViews[OpenXRWorldView::RIGHT_VIEW] = new OpenXRWorldView(mXR, "RightEye", context->getState(), mMetersPerUnit, OpenXRWorldView::RIGHT_VIEW);
mLeftCamera = mViews[OpenXRWorldView::LEFT_VIEW]->createCamera(OpenXRWorldView::LEFT_VIEW, clearColor, context);
mRightCamera = mViews[OpenXRWorldView::RIGHT_VIEW]->createCamera(OpenXRWorldView::RIGHT_VIEW, clearColor, context);
@ -112,8 +102,8 @@ namespace MWVR
mViewer->addSlave(mLeftCamera, mViews[OpenXRWorldView::LEFT_VIEW]->projectionMatrix(), mViews[OpenXRWorldView::LEFT_VIEW]->viewMatrix(), true);
mViewer->addSlave(mRightCamera, mViews[OpenXRWorldView::RIGHT_VIEW]->projectionMatrix(), mViews[OpenXRWorldView::RIGHT_VIEW]->viewMatrix(), true);
mViewer->getSlave(OpenXRWorldView::LEFT_VIEW)._updateSlaveCallback = new UpdateSlaveCallback(mXR, mViews[OpenXRWorldView::LEFT_VIEW], context);
mViewer->getSlave(OpenXRWorldView::RIGHT_VIEW)._updateSlaveCallback = new UpdateSlaveCallback(mXR, mViews[OpenXRWorldView::RIGHT_VIEW], context);
mViewer->getSlave(OpenXRWorldView::LEFT_VIEW)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, mViews[OpenXRWorldView::LEFT_VIEW], context);
mViewer->getSlave(OpenXRWorldView::RIGHT_VIEW)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, mViews[OpenXRWorldView::RIGHT_VIEW], context);
mViewer->setLightingMode(osg::View::SKY_LIGHT);
mViewer->setReleaseContextAtEndOfFrameHint(false);
@ -147,18 +137,24 @@ namespace MWVR
mMirrorTextureSwapchain.reset(new OpenXRSwapchain(mXR, context->getState(), config));
mXRMenu.reset(new OpenXRMenu(mXR, context->getState(), "MainMenu", config.width, config.height, osg::Vec2(1.f, 1.f)));
mMainCamera->setPreDrawCallback(new OpenXRView::PredrawCallback(mMainCamera, mXRMenu.get()));
mMainCamera->setFinalDrawCallback(new OpenXRView::PostdrawCallback(mMainCamera, mXRMenu.get()));
mMenuCamera = mXRMenu->createCamera(2, clearColor, context);
mMenuCamera->setCullMask(MWRender::Mask_GUI);
mMenuCamera->setName("MenuCamera");
mViewer->addSlave(mMenuCamera, true);
//mMenuCamera->setPreDrawCallback(new OpenXRView::PredrawCallback(mMenuCamera, mXRMenu.get()));
//mMenuCamera->setFinalDrawCallback(new OpenXRView::PostdrawCallback(mMenuCamera, mXRMenu.get()));
mXR->impl().mLayerStack.setLayer(OpenXRLayerStack::MENU_VIEW_LAYER, mXRMenu.get());
mMainCamera->getGraphicsContext()->setSwapCallback(new OpenXRViewer::SwapBuffersCallback(this));
mMainCamera->setGraphicsContext(nullptr);
mConfigured = true;
}
void OpenXRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc) const
{
mMirrorTextureSwapchain->beginFrame(gc);
mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().current()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
mViews[OpenXRWorldView::RIGHT_VIEW]->swapchain().current()->blit(gc, mMirrorTextureSwapchain->width() / 2, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
mViews[OpenXRWorldView::RIGHT_VIEW]->swapchain().renderBuffer()->blit(gc, mMirrorTextureSwapchain->width() / 2, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
//mXRMenu->swapchain().current()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
}
@ -172,45 +168,30 @@ namespace MWVR
void OpenXRViewer::swapBuffers(osg::GraphicsContext* gc)
{
Timer timer("swapBuffers");
Log(Debug::Verbose) << "SwapBuffers()";
std::unique_lock<std::mutex> lock(mMutex);
if (!mConfigured)
return;
auto* state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false);
blitEyesToMirrorTexture(gc);
mXR->beginFrame();
//blitEyesToMirrorTexture(gc);
mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().endFrame(gc);
mViews[OpenXRWorldView::RIGHT_VIEW]->swapchain().endFrame(gc);
mXRMenu->swapchain().endFrame(gc);
mXR->endFrame();
gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
mXR->waitFrame();
//gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
mMirrorTextureSwapchain->current()->blit(gc, 0, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
//mMirrorTextureSwapchain->renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
mMirrorTextureSwapchain->releaseSwapchainImage();
//mMirrorTextureSwapchain->endFrame(gc);
gc->swapBuffersImplementation();
}
void
OpenXRViewer::UpdateSlaveCallback::updateSlave(
osg::View& view,
osg::View::Slave& slave)
{
mXR->handleEvents();
if (!mXR->sessionRunning())
return;
auto* camera = slave._camera.get();
auto name = camera->getName();
Log(Debug::Verbose) << "Updating camera " << name;
mXR->beginFrame(mView->frameIndex());
auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
auto projMatrix = mView->projectionMatrix();
camera->setViewMatrix(viewMatrix);
camera->setProjectionMatrix(projMatrix);
}
void

View file

@ -31,20 +31,6 @@ namespace MWVR
osg::ref_ptr<OpenXRViewer> mViewer;
};
class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{
public:
UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRWorldView> view, osg::GraphicsContext* gc)
: mXR(XR), mView(view), mGC(gc)
{}
void updateSlave(osg::View& view, osg::View::Slave& slave) override;
private:
osg::ref_ptr<OpenXRManager> mXR;
osg::ref_ptr<OpenXRWorldView> mView;
osg::ref_ptr<osg::GraphicsContext> mGC;
};
class SwapBuffersCallback : public osg::GraphicsContext::SwapCallback
{
public:
@ -90,6 +76,7 @@ namespace MWVR
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mRightGW;
osg::Camera* mMainCamera = nullptr;
osg::Camera* mMenuCamera = nullptr;
osg::Camera* mLeftCamera = nullptr;
osg::Camera* mRightCamera = nullptr;

View file

@ -75,9 +75,10 @@ namespace MWVR
return osg::Matrix(matrix);
}
osg::Matrix OpenXRWorldView::projectionMatrix()
{
auto hmdViews = mXR->impl().getHmdViews();
auto hmdViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::VIEW);
float near = Settings::Manager::getFloat("near clip", "Camera");
float far = Settings::Manager::getFloat("viewing distance", "Camera") * mMetersPerUnit;
@ -88,11 +89,11 @@ namespace MWVR
osg::Matrix OpenXRWorldView::viewMatrix()
{
osg::Matrix viewMatrix;
auto hmdViews = mXR->impl().getHmdViews();
auto hmdViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::VIEW);
auto pose = hmdViews[mView].pose;
osg::Vec3 position = osg::fromXR(pose.position);
auto stageViews = mXR->impl().getStageViews();
auto stageViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::STAGE);
auto stagePose = stageViews[mView].pose;
// Comfort shortcut.
@ -115,8 +116,8 @@ namespace MWVR
}
OpenXRWorldView::OpenXRWorldView(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view)
: OpenXRView(XR)
osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view)
: OpenXRView(XR, name)
, mMetersPerUnit(metersPerUnit)
, mView(view)
{
@ -145,4 +146,44 @@ namespace MWVR
renderer->setCameraRequiresSetUp(false);
}
}
void OpenXRWorldView::prerenderCallback(osg::RenderInfo& renderInfo)
{
OpenXRView::prerenderCallback(renderInfo);
auto* view = renderInfo.getView();
auto* camera = renderInfo.getCurrentCamera();
auto name = camera->getName();
Log(Debug::Verbose) << "Updating camera " << name;
auto viewMatrix = view->getCamera()->getViewMatrix() * this->viewMatrix();
auto projectionMatrix = this->projectionMatrix();
camera->setViewMatrix(viewMatrix);
camera->setProjectionMatrix(projectionMatrix);
}
void
OpenXRWorldView::UpdateSlaveCallback::updateSlave(
osg::View& view,
osg::View::Slave& slave)
{
mXR->handleEvents();
if (!mXR->sessionRunning())
return;
auto* camera = slave._camera.get();
auto name = camera->getName();
Log(Debug::Verbose) << "Updating slave " << name;
//auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
//auto projMatrix = mView->projectionMatrix();
//camera->setViewMatrix(viewMatrix);
//camera->setProjectionMatrix(projMatrix);
slave.updateSlaveImplementation(view);
}
}

View file

@ -15,10 +15,27 @@ namespace MWVR
SUBVIEW_MAX = RIGHT_VIEW, //!< Used to size subview arrays. Not a valid input.
};
class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{
public:
OpenXRWorldView(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view);
UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRWorldView> view, osg::GraphicsContext* gc)
: mXR(XR), mView(view), mGC(gc)
{}
void updateSlave(osg::View& view, osg::View::Slave& slave) override;
private:
osg::ref_ptr<OpenXRManager> mXR;
osg::ref_ptr<OpenXRWorldView> mView;
osg::ref_ptr<osg::GraphicsContext> mGC;
};
public:
OpenXRWorldView(osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view);
~OpenXRWorldView();
//! Prepare for render (update matrices)
void prerenderCallback(osg::RenderInfo& renderInfo) override;
//! Projection offset for this view
osg::Matrix projectionMatrix();
//! View offset for this view