mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-02-15 08:09:40 +00:00
Predictions work perfectly with this. Committing that and clean up later.
This commit is contained in:
parent
fea964a6f3
commit
14039e5e25
20 changed files with 778 additions and 493 deletions
|
@ -124,6 +124,8 @@ if(BUILD_VR_OPENXR)
|
|||
mwvr/openxrmanagerimpl.cpp
|
||||
mwvr/openxrmenu.hpp
|
||||
mwvr/openxrmenu.cpp
|
||||
mwvr/openxrsession.hpp
|
||||
mwvr/openxrsession.cpp
|
||||
mwvr/openxrswapchain.hpp
|
||||
mwvr/openxrswapchain.cpp
|
||||
mwvr/openxrtexture.hpp
|
||||
|
|
|
@ -514,9 +514,10 @@ namespace MWVR
|
|||
void
|
||||
OpenXRInputManagerImpl::updateHandTracking()
|
||||
{
|
||||
for (auto hand : { LEFT_HAND, RIGHT_HAND }) {
|
||||
CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().predictedDisplayTime(OpenXRFrameIndexer::instance().updateIndex()), &mHandSpaceLocation[hand]));
|
||||
}
|
||||
// TODO
|
||||
//for (auto hand : { LEFT_HAND, RIGHT_HAND }) {
|
||||
// CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().predictedDisplayTime(OpenXRFrameIndexer::instance().updateIndex()), &mHandSpaceLocation[hand]));
|
||||
//}
|
||||
}
|
||||
|
||||
XrPath
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace MWVR {
|
|||
|
||||
void OpenXRLayerStack::setLayer(Layer layer, OpenXRLayer* layerObj)
|
||||
{
|
||||
mLayers[layer] = nullptr;
|
||||
mLayers[layer] = layerObj->layer();
|
||||
mLayerObjects[layer] = layerObj;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace MWVR
|
|||
virtual ~OpenXRLayer(void) = default;
|
||||
|
||||
virtual const XrCompositionLayerBaseHeader* layer() = 0;
|
||||
virtual void swapBuffers(osg::GraphicsContext* gc) = 0;
|
||||
};
|
||||
|
||||
class OpenXRLayerStack
|
||||
|
@ -32,9 +33,11 @@ namespace MWVR
|
|||
OpenXRLayerStack() = default;
|
||||
~OpenXRLayerStack() = default;
|
||||
|
||||
|
||||
void setLayer(Layer layer, OpenXRLayer* layerObj);
|
||||
int layerCount();
|
||||
const XrCompositionLayerBaseHeader** layerHeaders();
|
||||
LayerObjectStack& layerObjects() { return mLayerObjects; };
|
||||
|
||||
private:
|
||||
LayerObjectStack mLayerObjects;
|
||||
|
|
|
@ -16,11 +16,93 @@
|
|||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
|
||||
static std::vector<Timer::MeasurementContext> stats;
|
||||
static std::mutex statsMutex;
|
||||
static std::thread statsThread;
|
||||
static bool statsThreadRunning = false;
|
||||
|
||||
static void statsThreadRun()
|
||||
{
|
||||
return;
|
||||
while (statsThreadRunning)
|
||||
{
|
||||
std::stringstream ss;
|
||||
for (auto& context : stats)
|
||||
{
|
||||
for (auto& measurement : *context.second)
|
||||
{
|
||||
double ms = static_cast<double>(measurement.second) / 1000000.;
|
||||
Log(Debug::Verbose) << context.first << "." << measurement.first << ": " << ms << "ms";
|
||||
}
|
||||
}
|
||||
|
||||
//Log(Debug::Verbose) << ss.str();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Timer::Timer(const char* name) : mName(name)
|
||||
{
|
||||
mLastCheckpoint = mBegin = std::chrono::steady_clock::now();
|
||||
|
||||
std::unique_lock<std::mutex> lock(statsMutex);
|
||||
for (auto& m : stats)
|
||||
{
|
||||
if (m.first == mName)
|
||||
mContext = m.second;
|
||||
}
|
||||
|
||||
if (mContext == nullptr)
|
||||
{
|
||||
mContext = new Measures();
|
||||
mContext->reserve(32);
|
||||
stats.emplace_back(MeasurementContext(mName, mContext));
|
||||
}
|
||||
|
||||
if (!statsThreadRunning)
|
||||
{
|
||||
statsThreadRunning = true;
|
||||
statsThread = std::thread([] { statsThreadRun(); });
|
||||
}
|
||||
}
|
||||
Timer::~Timer()
|
||||
{
|
||||
//statsThreadRunning = false;
|
||||
checkpoint("~");
|
||||
}
|
||||
|
||||
void Timer::checkpoint(const char* name)
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(now - mLastCheckpoint);
|
||||
mLastCheckpoint = now;
|
||||
|
||||
Measure* measure = nullptr;
|
||||
for (auto& m : *mContext)
|
||||
{
|
||||
if (m.first == name)
|
||||
{
|
||||
measure = &m;
|
||||
}
|
||||
}
|
||||
if (!measure)
|
||||
{
|
||||
mContext->push_back(Measure(name, elapsed.count()));
|
||||
}
|
||||
else {
|
||||
measure->second = measure->second * 0.95 + elapsed.count() * 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
OpenXRManager::OpenXRManager()
|
||||
: mPrivate(nullptr)
|
||||
|
@ -34,7 +116,7 @@ namespace MWVR
|
|||
|
||||
}
|
||||
|
||||
bool
|
||||
bool
|
||||
OpenXRManager::realized()
|
||||
{
|
||||
return !!mPrivate;
|
||||
|
@ -73,10 +155,10 @@ namespace MWVR
|
|||
return impl().beginFrame();
|
||||
}
|
||||
|
||||
void OpenXRManager::endFrame()
|
||||
void OpenXRManager::endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack)
|
||||
{
|
||||
if (realized())
|
||||
return impl().endFrame();
|
||||
return impl().endFrame(displayTime, layerStack);
|
||||
}
|
||||
|
||||
void OpenXRManager::updateControls()
|
||||
|
@ -96,11 +178,11 @@ namespace MWVR
|
|||
try {
|
||||
mPrivate = std::make_shared<OpenXRManagerImpl>();
|
||||
}
|
||||
catch (std::exception& e)
|
||||
catch (std::exception & e)
|
||||
{
|
||||
Log(Debug::Error) << "Exception thrown by OpenXR: " << e.what();
|
||||
osg::ref_ptr<osg::State> state = gc->getState();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,71 +201,18 @@ namespace MWVR
|
|||
mXR->realize(gc);
|
||||
}
|
||||
|
||||
bool
|
||||
bool
|
||||
OpenXRManager::RealizeOperation::realized()
|
||||
{
|
||||
return mXR->realized();
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
OpenXRManager::CleanupOperation::operator()(
|
||||
osg::GraphicsContext* gc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//#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()
|
||||
{
|
||||
return g_OpenXRFrameIndexer;
|
||||
}
|
||||
|
||||
int64_t OpenXRFrameIndexer::advanceUpdateIndex()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mUpdateIndex++;
|
||||
Log(Debug::Verbose) << "Advancing update index to " << mUpdateIndex;
|
||||
assert(mUpdateIndex > mRenderIndex);
|
||||
return mUpdateIndex;
|
||||
}
|
||||
|
||||
int64_t OpenXRFrameIndexer::advanceRenderIndex()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mRenderIndex++;
|
||||
Log(Debug::Verbose) << "Advancing frame index to " << mRenderIndex;
|
||||
if (!(mUpdateIndex >= mRenderIndex))
|
||||
mUpdateIndex = mRenderIndex - 1;
|
||||
return mRenderIndex;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator <<(
|
||||
|
|
|
@ -20,30 +20,29 @@ 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";
|
||||
}
|
||||
using Measure = std::pair < const char*, int64_t >;
|
||||
using Measures = std::vector < Measure >;
|
||||
using MeasurementContext = std::pair < const char*, Measures* >;
|
||||
|
||||
Timer(const char* name);
|
||||
~Timer();
|
||||
void checkpoint(const char* name);
|
||||
|
||||
std::chrono::steady_clock::time_point mBegin;
|
||||
std::string mName;
|
||||
std::chrono::steady_clock::time_point mLastCheckpoint;
|
||||
const char* mName = nullptr;
|
||||
Measures* mContext = nullptr;
|
||||
};
|
||||
|
||||
//! Represents the pose of a limb in VR space.
|
||||
struct Pose
|
||||
{
|
||||
//! Position in VR space
|
||||
osg::Vec3 position;
|
||||
osg::Vec3 position{ 0,0,0 };
|
||||
//! Orientation in VR space.
|
||||
osg::Quat orientation;
|
||||
osg::Quat orientation{ 0,0,0,1 };
|
||||
//! Speed of movement in VR space, expressed in meters per second
|
||||
osg::Vec3 velocity;
|
||||
osg::Vec3 velocity{ 0,0,0 };
|
||||
};
|
||||
|
||||
//! Describes what limb to track.
|
||||
|
@ -57,31 +56,16 @@ namespace MWVR
|
|||
//! Describes what space to track the limb in
|
||||
enum class TrackedSpace
|
||||
{
|
||||
STAGE, //!< Track limb in the VR stage space. Meaning a space with a floor level origin and fixed horizontal orientation.
|
||||
VIEW //!< Track limb in the VR view space. Meaning a space with the head as origin and orientation.
|
||||
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.
|
||||
};
|
||||
|
||||
struct OpenXRFrameIndexer
|
||||
enum class Chirality
|
||||
{
|
||||
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 };
|
||||
LEFT_HAND = 0,
|
||||
RIGHT_HAND = 1
|
||||
};
|
||||
|
||||
|
||||
// Use the pimpl pattern to avoid cluttering the namespace with openxr dependencies.
|
||||
class OpenXRManagerImpl;
|
||||
|
||||
|
@ -122,7 +106,7 @@ namespace MWVR
|
|||
void handleEvents();
|
||||
void waitFrame();
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
void endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack);
|
||||
void updateControls();
|
||||
|
||||
void realize(osg::GraphicsContext* gc);
|
||||
|
|
|
@ -254,60 +254,81 @@ namespace MWVR
|
|||
Log(Debug::Verbose) << ss.str();
|
||||
}
|
||||
|
||||
XrFrameState OpenXRManagerImpl::frameState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mFrameStateMutex);
|
||||
return mFrameState;
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::waitFrame()
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::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);
|
||||
static std::mutex waitFrameMutex;
|
||||
|
||||
if (!mSessionRunning)
|
||||
return;
|
||||
if (!mSessionRunning)
|
||||
return;
|
||||
|
||||
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
|
||||
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
|
||||
XrFrameWaitInfo frameWaitInfo{ XR_TYPE_FRAME_WAIT_INFO };
|
||||
XrFrameState frameState{ XR_TYPE_FRAME_STATE };
|
||||
|
||||
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
|
||||
|
||||
|
||||
mTimeKeeper.progressToNextFrame(frameState);
|
||||
|
||||
//}).detach();
|
||||
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
|
||||
|
||||
std::unique_lock<std::mutex> lock(mFrameStateMutex);
|
||||
mFrameState = frameState;
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::WaitFrame END";
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::beginFrame()
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::BeginFrame";
|
||||
Timer timer("beginFrame");
|
||||
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
|
||||
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::BeginFrame END";
|
||||
}
|
||||
|
||||
void
|
||||
OpenXRManagerImpl::endFrame()
|
||||
OpenXRManagerImpl::endFrame(int64_t displayTime, OpenXRLayerStack* layerStack)
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::EndFrame";
|
||||
Timer timer("endFrame()");
|
||||
if (!mSessionRunning)
|
||||
return;
|
||||
|
||||
|
||||
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
|
||||
frameEndInfo.displayTime = mTimeKeeper.predictedDisplayTime(OpenXRFrameIndexer::instance().renderIndex());
|
||||
frameEndInfo.displayTime = displayTime;
|
||||
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
|
||||
frameEndInfo.layerCount = mLayerStack.layerCount();
|
||||
frameEndInfo.layers = mLayerStack.layerHeaders();
|
||||
if (layerStack)
|
||||
{
|
||||
frameEndInfo.layerCount = layerStack->layerCount();
|
||||
frameEndInfo.layers = layerStack->layerHeaders();
|
||||
}
|
||||
else {
|
||||
frameEndInfo.layerCount = 0;
|
||||
frameEndInfo.layers = nullptr;
|
||||
}
|
||||
//Log(Debug::Verbose) << "LayerStack<" << frameEndInfo.layerCount << ", " << frameEndInfo.layers << ">";
|
||||
|
||||
static std::chrono::steady_clock::time_point last = std::chrono::steady_clock::now();
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = now -last;
|
||||
last = now;
|
||||
|
||||
Log(Debug::Verbose) << "endFrame(): period: " << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
|
||||
|
||||
CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::EndFrame END";
|
||||
}
|
||||
|
||||
std::array<XrView, 2>
|
||||
OpenXRManagerImpl::getPredictedViews(
|
||||
int64_t frameIndex,
|
||||
int64_t predictedDisplayTime,
|
||||
TrackedSpace space)
|
||||
{
|
||||
|
||||
|
@ -317,7 +338,7 @@ namespace MWVR
|
|||
|
||||
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
|
||||
viewLocateInfo.viewConfigurationType = mViewConfigType;
|
||||
viewLocateInfo.displayTime = mTimeKeeper.predictedDisplayTime(frameIndex);
|
||||
viewLocateInfo.displayTime = predictedDisplayTime;
|
||||
switch (space)
|
||||
{
|
||||
case TrackedSpace::STAGE:
|
||||
|
@ -332,7 +353,7 @@ namespace MWVR
|
|||
return views;
|
||||
}
|
||||
|
||||
MWVR::Pose OpenXRManagerImpl::getPredictedLimbPose(int64_t frameIndex, TrackedLimb limb, TrackedSpace space)
|
||||
MWVR::Pose OpenXRManagerImpl::getPredictedLimbPose(int64_t predictedDisplayTime, TrackedLimb limb, TrackedSpace space)
|
||||
{
|
||||
XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
|
||||
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
|
||||
|
@ -360,9 +381,9 @@ namespace MWVR
|
|||
referenceSpace = mReferenceSpaceView;
|
||||
break;
|
||||
}
|
||||
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";
|
||||
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, predictedDisplayTime, &location));
|
||||
//if (!(velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT))
|
||||
// Log(Debug::Warning) << "Unable to acquire linear velocity";
|
||||
return MWVR::Pose{
|
||||
osg::fromXR(location.pose.position),
|
||||
osg::fromXR(location.pose.orientation),
|
||||
|
@ -439,12 +460,6 @@ namespace MWVR
|
|||
}
|
||||
}
|
||||
|
||||
XrTime OpenXRManagerImpl::predictedDisplayTime(int64_t frameIndex)
|
||||
{
|
||||
|
||||
return mTimeKeeper.predictedDisplayTime(frameIndex);
|
||||
}
|
||||
|
||||
const XrEventDataBaseHeader*
|
||||
OpenXRManagerImpl::nextEvent()
|
||||
{
|
||||
|
@ -476,65 +491,65 @@ 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);
|
||||
//XrTime OpenXRTimeKeeper::predictedDisplayTime(int64_t frameIndex)
|
||||
//{
|
||||
// std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
//auto prediction = mPredictedFrameTime;
|
||||
//auto predictedPeriod = mPredictedPeriod;
|
||||
// //auto prediction = mPredictedFrameTime;
|
||||
// //auto predictedPeriod = mPredictedPeriod;
|
||||
|
||||
//auto futureFrames = frameIndex - OpenXRFrameIndexer::instance().renderIndex();
|
||||
// //auto futureFrames = frameIndex - OpenXRFrameIndexer::instance().renderIndex();
|
||||
|
||||
//prediction += ( 0 + futureFrames) * predictedPeriod;
|
||||
// //prediction += ( 0 + futureFrames) * predictedPeriod;
|
||||
|
||||
//Log(Debug::Verbose) << "Predicted: displayTime[" << futureFrames << "]=" << prediction;
|
||||
// //Log(Debug::Verbose) << "Predicted: displayTime[" << futureFrames << "]=" << prediction;
|
||||
|
||||
//return prediction;
|
||||
// //return prediction;
|
||||
|
||||
//return mFrameState.predictedDisplayTime;
|
||||
// //return mFrameState.predictedDisplayTime;
|
||||
|
||||
return mPredictedFrameTime;
|
||||
}
|
||||
// return mPredictedFrameTime;
|
||||
//}
|
||||
|
||||
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;
|
||||
//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;
|
||||
// auto now = clock::now();
|
||||
// auto nanoseconds_elapsed = std::chrono::duration_cast<nanoseconds>(now - mLastFrameTimePoint);
|
||||
// XrDuration realPeriod = nanoseconds_elapsed.count();
|
||||
// mFrameState = frameState;
|
||||
|
||||
mPredictedFrameTime = mFrameState.predictedDisplayTime;
|
||||
mPredictedPeriod = mFrameState.predictedDisplayPeriod;
|
||||
// mPredictedFrameTime = mFrameState.predictedDisplayTime + mFrameState.predictedDisplayPeriod * 4;
|
||||
// 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) / 2;
|
||||
mPredictedPeriod = realPeriod;
|
||||
}
|
||||
// // 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) / 2;
|
||||
// // 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;
|
||||
// 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;
|
||||
}
|
||||
// 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
|
||||
|
|
|
@ -44,31 +44,6 @@ 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
|
||||
{
|
||||
OpenXRManagerImpl(void);
|
||||
|
@ -81,14 +56,14 @@ namespace MWVR
|
|||
const XrEventDataBaseHeader* nextEvent();
|
||||
void waitFrame();
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
std::array<XrView, 2> getPredictedViews(int64_t frameIndex, TrackedSpace mSpace);
|
||||
MWVR::Pose getPredictedLimbPose(int64_t frameIndex, TrackedLimb limb, TrackedSpace space);
|
||||
void endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack);
|
||||
std::array<XrView, 2> getPredictedViews(int64_t predictedDisplayTime, TrackedSpace mSpace);
|
||||
MWVR::Pose getPredictedLimbPose(int64_t predictedDisplayTime, TrackedLimb limb, TrackedSpace space);
|
||||
int eyes();
|
||||
void handleEvents();
|
||||
void updateControls();
|
||||
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
|
||||
XrTime predictedDisplayTime(int64_t frameIndex);
|
||||
XrFrameState frameState();
|
||||
|
||||
bool initialized = false;
|
||||
long long mFrameIndex = 0;
|
||||
|
@ -105,10 +80,10 @@ namespace MWVR
|
|||
XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
|
||||
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
|
||||
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
|
||||
OpenXRTimeKeeper mTimeKeeper{};
|
||||
OpenXRLayerStack mLayerStack{};
|
||||
XrFrameState mFrameState{};
|
||||
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
|
||||
bool mSessionRunning = false;
|
||||
std::mutex mFrameStateMutex{};
|
||||
std::mutex mEventMutex{};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,27 +5,16 @@
|
|||
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, title)
|
||||
OpenXRMenu::OpenXRMenu(
|
||||
osg::ref_ptr<OpenXRManager> XR,
|
||||
OpenXRSwapchain::Config config,
|
||||
osg::ref_ptr<osg::State> state,
|
||||
const std::string& title,
|
||||
osg::Vec2 extent_meters)
|
||||
: OpenXRView(XR, title, config, state)
|
||||
, mTitle(title)
|
||||
, mExtent(extent_meters)
|
||||
{
|
||||
setWidth(width);
|
||||
setHeight(height);
|
||||
setSamples(1);
|
||||
if (!realize(state))
|
||||
throw std::runtime_error(std::string("Failed to create swapchain for menu \"") + title + "\"");
|
||||
mLayer.reset(new XrCompositionLayerQuad);
|
||||
mLayer->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||||
mLayer->next = nullptr;
|
||||
mLayer->layerFlags = 0;
|
||||
mLayer->space = XR->impl().mReferenceSpaceStage;
|
||||
mLayer->eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
mLayer->subImage = swapchain().subImage();
|
||||
mLayer->size.width = extent_meters.x();
|
||||
mLayer->size.height = extent_meters.y();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
@ -38,6 +27,14 @@ namespace MWVR
|
|||
OpenXRMenu::layer()
|
||||
{
|
||||
updatePosition();
|
||||
//Log(Debug::Verbose) << "MenuPose: " << mPose;
|
||||
|
||||
if (mLayer)
|
||||
{
|
||||
mLayer->pose.position = osg::toXR(mPose.position);
|
||||
mLayer->pose.orientation = osg::toXR(mPose.orientation);
|
||||
}
|
||||
|
||||
return reinterpret_cast<XrCompositionLayerBaseHeader*>(mLayer.get());
|
||||
}
|
||||
|
||||
|
@ -47,22 +44,36 @@ 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().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);
|
||||
mPose = predictedPose();
|
||||
mPose.position += mPose.orientation * osg::Vec3(0, 0, -1);
|
||||
mPose.orientation = -mPose.orientation;
|
||||
|
||||
Log(Debug::Verbose) << "Menu pose updated to: " << mPose;
|
||||
mPositionNeedsUpdate = false;
|
||||
|
||||
Log(Debug::Verbose) << "Menu pose updated to: " << pose;
|
||||
}
|
||||
|
||||
void OpenXRMenu::postrenderCallback(osg::RenderInfo& info)
|
||||
void OpenXRMenu::swapBuffers(
|
||||
osg::GraphicsContext* gc)
|
||||
{
|
||||
OpenXRView::postrenderCallback(info);
|
||||
OpenXRView::swapBuffers(gc);
|
||||
|
||||
if (!mLayer)
|
||||
{
|
||||
mLayer.reset(new XrCompositionLayerQuad);
|
||||
mLayer->type = XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||||
mLayer->next = nullptr;
|
||||
mLayer->layerFlags = 0;
|
||||
mLayer->space = mXR->impl().mReferenceSpaceStage;
|
||||
mLayer->eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
mLayer->subImage = swapchain().subImage();
|
||||
mLayer->size.width = mExtent.x();
|
||||
mLayer->size.height = mExtent.y();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,20 @@ namespace MWVR
|
|||
{
|
||||
|
||||
public:
|
||||
OpenXRMenu(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<osg::State> state, const std::string& title, int width, int height, osg::Vec2 extent_meters);
|
||||
OpenXRMenu(osg::ref_ptr<OpenXRManager> XR, OpenXRSwapchain::Config config, osg::ref_ptr<osg::State> state, const std::string& title, osg::Vec2 extent_meters);
|
||||
~OpenXRMenu();
|
||||
const XrCompositionLayerBaseHeader* layer() override;
|
||||
const std::string& title() const { return mTitle; }
|
||||
void updatePosition();
|
||||
void postrenderCallback(osg::RenderInfo& renderInfo) override;
|
||||
|
||||
void swapBuffers(osg::GraphicsContext* gc) override;
|
||||
|
||||
protected:
|
||||
bool mPositionNeedsUpdate{ true };
|
||||
std::unique_ptr<XrCompositionLayerQuad> mLayer = nullptr;
|
||||
std::string mTitle;
|
||||
Pose mPose{ {}, {0,0,0,1}, {} };
|
||||
osg::Vec2 mExtent;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
166
apps/openmw/mwvr/openxrsession.cpp
Normal file
166
apps/openmw/mwvr/openxrsession.cpp
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "openxrmanager.hpp"
|
||||
#include "openxrmanagerimpl.hpp"
|
||||
#include "openxrswapchain.hpp"
|
||||
#include "../mwinput/inputmanagerimp.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <openxr/openxr_platform.h>
|
||||
#include <openxr/openxr_platform_defines.h>
|
||||
#include <openxr/openxr_reflection.h>
|
||||
|
||||
#include <osg/Camera>
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include "openxrsession.hpp"
|
||||
#include <time.h>
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
OpenXRSession::OpenXRSession(
|
||||
osg::ref_ptr<OpenXRManager> XR)
|
||||
: mXR(XR)
|
||||
, mFrameEndTimePoint(clock::now())
|
||||
, mFrameBeginTimePoint(mFrameEndTimePoint)
|
||||
{
|
||||
mXRThread = std::thread([this] {run(); });
|
||||
}
|
||||
|
||||
OpenXRSession::~OpenXRSession()
|
||||
{
|
||||
mShouldQuit = true;
|
||||
}
|
||||
|
||||
void OpenXRSession::setLayer(
|
||||
OpenXRLayerStack::Layer layerType,
|
||||
OpenXRLayer* layer)
|
||||
{
|
||||
mLayerStack.setLayer(layerType, layer);
|
||||
}
|
||||
|
||||
void OpenXRSession::swapBuffers(osg::GraphicsContext* gc)
|
||||
{
|
||||
Log(Debug::Verbose) << "OpenXRSession::swapBuffers";
|
||||
Timer timer("OpenXRSession::SwapBuffers");
|
||||
static int wat = 0;
|
||||
if (!mXR->sessionRunning())
|
||||
return;
|
||||
if (!mShouldRenderLayers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if (wat++ % 100 != 0)
|
||||
// return;
|
||||
|
||||
|
||||
//std::unique_lock<std::mutex> renderLock(mRenderMutex);
|
||||
//timer.checkpoint("Mutex");
|
||||
for (auto layer : mLayerStack.layerObjects())
|
||||
layer->swapBuffers(gc);
|
||||
mFrameEndTimePoint = clock::now();
|
||||
auto nanoseconds_elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(mFrameEndTimePoint - mFrameBeginTimePoint);
|
||||
auto milliseconds_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(nanoseconds_elapsed);
|
||||
mRenderTimes.push_back(nanoseconds_elapsed.count());
|
||||
Log(Debug::Verbose) << "Render time: " << milliseconds_elapsed.count() << "ms";
|
||||
//mShouldRenderLayers = true;
|
||||
timer.checkpoint("Rendered");
|
||||
|
||||
mXR->endFrame(predictedDisplayTime(), &mLayerStack);
|
||||
Log(Debug::Verbose) << "mFrameEndTimePoint: " << mFrameEndTimePoint.time_since_epoch().count() / 1000000;
|
||||
Log(Debug::Verbose) << "mFrameBeginTimePoint: " << mFrameBeginTimePoint.time_since_epoch().count() / 1000000;
|
||||
Log(Debug::Verbose) << "mPredictedDisplayTimeRealTime: " << mPredictedDisplayTimeRealTime.time_since_epoch().count() / 1000000;
|
||||
Log(Debug::Verbose) << "mPredictedTime: " << predictedDisplayTime() / 1000000;
|
||||
Log(Debug::Verbose) << "mXrDisplayTime: " << mXR->impl().frameState().predictedDisplayTime / 1000000;
|
||||
//mShouldRenderLayers = false;
|
||||
mFrameBeginTimePoint = clock::now();
|
||||
|
||||
//mRenderTimes.push_front(std::chrono::duration_cast<std::chrono::nanoseconds>(mFrameEndTimePoint - mFrameBeginTimePoint).count());
|
||||
//if(mRenderTimes.size() > 33) mRenderTimes.pop_back();
|
||||
|
||||
//if (mPredictedPeriod == 0)
|
||||
//{
|
||||
// mPredictedPeriod = milliseconds_elapsed;
|
||||
// mPredictedPeriods = 1;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// double periodDevianceMagnitude = static_cast<double>(std::max(milliseconds_elapsed, mPredictedPeriod)) / static_cast<double>(std::min(milliseconds_elapsed, mPredictedPeriod));
|
||||
// double periodStability = std::pow(periodDevianceMagnitude, -2.);
|
||||
// mPredictedPeriod = (1. - periodStability) * mPredictedPeriod + (periodStability)*milliseconds_elapsed;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
void OpenXRSession::run()
|
||||
{
|
||||
}
|
||||
|
||||
void OpenXRSession::waitFrame()
|
||||
{
|
||||
// For now it seems we must just accept crap performance from the rendering loop
|
||||
// Since Oculus' implementation of waitFrame() does not even attempt to reflect real
|
||||
// render time and just incurs a huge useless delay.
|
||||
|
||||
Log(Debug::Verbose) << "OpenXRSesssion::beginframe";
|
||||
Timer timer("OpenXRSession::beginFrame");
|
||||
mXR->waitFrame();
|
||||
timer.checkpoint("waitFrame");
|
||||
predictNext(0);
|
||||
mShouldRenderLayers = true;
|
||||
|
||||
}
|
||||
|
||||
void OpenXRSession::predictNext(int extraPeriods)
|
||||
{
|
||||
int64_t sum = 0;
|
||||
while (mRenderTimes.size() > 10)
|
||||
mRenderTimes.pop_front();
|
||||
for (unsigned i = 0; i < mRenderTimes.size(); i++)
|
||||
{
|
||||
sum += mRenderTimes[i] / mXR->impl().frameState().predictedDisplayPeriod;
|
||||
}
|
||||
int64_t average = sum / std::max((int64_t)mRenderTimes.size(), (int64_t)1);
|
||||
|
||||
mPredictedPeriods = std::max(average, (int64_t)0) + extraPeriods;
|
||||
|
||||
Log(Debug::Verbose) << "Periods: " << mPredictedPeriods;
|
||||
|
||||
int64_t future = (mPredictedPeriods)*mXR->impl().frameState().predictedDisplayPeriod;
|
||||
|
||||
mPredictedDisplayTime = mXR->impl().frameState().predictedDisplayTime + future;
|
||||
mPredictedDisplayTimeRealTime = mFrameBeginTimePoint + nanoseconds(future + mXR->impl().frameState().predictedDisplayPeriod);
|
||||
|
||||
// Update pose predictions
|
||||
mPredictedPoses.head[(int)TrackedSpace::STAGE] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::STAGE);
|
||||
mPredictedPoses.head[(int)TrackedSpace::VIEW] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::HEAD, TrackedSpace::VIEW);
|
||||
mPredictedPoses.hands[(int)Chirality::LEFT_HAND][(int)TrackedSpace::STAGE] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::LEFT_HAND, TrackedSpace::STAGE);
|
||||
mPredictedPoses.hands[(int)Chirality::LEFT_HAND][(int)TrackedSpace::VIEW] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::LEFT_HAND, TrackedSpace::VIEW);
|
||||
mPredictedPoses.hands[(int)Chirality::RIGHT_HAND][(int)TrackedSpace::STAGE] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::RIGHT_HAND, TrackedSpace::STAGE);
|
||||
mPredictedPoses.hands[(int)Chirality::RIGHT_HAND][(int)TrackedSpace::VIEW] = mXR->impl().getPredictedLimbPose(mPredictedDisplayTime, TrackedLimb::RIGHT_HAND, TrackedSpace::VIEW);
|
||||
auto stageViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::STAGE);
|
||||
auto hmdViews = mXR->impl().getPredictedViews(mPredictedDisplayTime, TrackedSpace::VIEW);
|
||||
mPredictedPoses.eye[(int)Chirality::LEFT_HAND][(int)TrackedSpace::STAGE] = fromXR(stageViews[(int)Chirality::LEFT_HAND].pose);
|
||||
mPredictedPoses.eye[(int)Chirality::LEFT_HAND][(int)TrackedSpace::VIEW] = fromXR(hmdViews[(int)Chirality::LEFT_HAND].pose);
|
||||
mPredictedPoses.eye[(int)Chirality::RIGHT_HAND][(int)TrackedSpace::STAGE] = fromXR(stageViews[(int)Chirality::RIGHT_HAND].pose);
|
||||
mPredictedPoses.eye[(int)Chirality::RIGHT_HAND][(int)TrackedSpace::VIEW] = fromXR(hmdViews[(int)Chirality::RIGHT_HAND].pose);
|
||||
|
||||
//std::unique_lock<std::mutex> lock(mSyncMutex);
|
||||
//mSync.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator <<(
|
||||
std::ostream& os,
|
||||
const MWVR::Pose& pose)
|
||||
{
|
||||
os << "position=" << pose.position << " orientation=" << pose.orientation << " velocity=" << pose.velocity;
|
||||
return os;
|
||||
}
|
||||
|
79
apps/openmw/mwvr/openxrsession.hpp
Normal file
79
apps/openmw/mwvr/openxrsession.hpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#ifndef MWVR_OPENRXSESSION_H
|
||||
#define MWVR_OPENRXSESSION_H
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/sdlutil/sdlgraphicswindow.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrlayer.hpp"
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
|
||||
using PoseSet = std::array<Pose, 2>;
|
||||
|
||||
struct PoseSets
|
||||
{
|
||||
PoseSet eye[2]{};
|
||||
PoseSet hands[2]{};
|
||||
PoseSet head{};
|
||||
};
|
||||
|
||||
class OpenXRSession
|
||||
{
|
||||
using seconds = std::chrono::duration<double>;
|
||||
using nanoseconds = std::chrono::nanoseconds;
|
||||
using clock = std::chrono::steady_clock;
|
||||
using time_point = clock::time_point;
|
||||
|
||||
public:
|
||||
OpenXRSession(osg::ref_ptr<OpenXRManager> XR);
|
||||
~OpenXRSession();
|
||||
|
||||
void setLayer(OpenXRLayerStack::Layer layerType, OpenXRLayer* layer);
|
||||
void swapBuffers(osg::GraphicsContext* gc);
|
||||
void run();
|
||||
|
||||
PoseSets& predictedPoses() { return mPredictedPoses; };
|
||||
|
||||
//! Call before rendering to update predictions
|
||||
void waitFrame();
|
||||
|
||||
//! Most recent prediction for display time of next frame.
|
||||
int64_t predictedDisplayTime() { return mPredictedDisplayTime; };
|
||||
|
||||
//! Update predictions
|
||||
void predictNext(int extraPeriods);
|
||||
|
||||
OpenXRLayerStack mLayerStack{};
|
||||
osg::ref_ptr<OpenXRManager> mXR;
|
||||
std::thread mXRThread;
|
||||
bool mShouldQuit = false;
|
||||
|
||||
PoseSets mPredictedPoses{};
|
||||
|
||||
int64_t mPredictedDisplayTime{ 0 };
|
||||
time_point mPredictedDisplayTimeRealTime{ nanoseconds(0) };
|
||||
std::deque<int64_t> mRenderTimes;
|
||||
int64_t mPredictedPeriods{ 0 };
|
||||
double mFps{ 0. };
|
||||
time_point mFrameEndTimePoint;
|
||||
time_point mFrameBeginTimePoint;
|
||||
|
||||
bool mNeedSync = true;
|
||||
std::condition_variable mSync{};
|
||||
std::mutex mSyncMutex{};
|
||||
|
||||
bool mShouldRenderLayers = false;
|
||||
std::condition_variable mRenderSignal{};
|
||||
std::mutex mRenderMutex{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -27,7 +27,7 @@ namespace MWVR {
|
|||
~OpenXRSwapchainImpl();
|
||||
|
||||
void beginFrame(osg::GraphicsContext* gc);
|
||||
void endFrame(osg::GraphicsContext* gc);
|
||||
int endFrame(osg::GraphicsContext* gc);
|
||||
|
||||
osg::ref_ptr<OpenXRManager> mXR;
|
||||
XrSwapchain mSwapchain = XR_NULL_HANDLE;
|
||||
|
@ -118,13 +118,16 @@ namespace MWVR {
|
|||
mRenderBuffer->beginFrame(gc);
|
||||
}
|
||||
|
||||
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
|
||||
int swapCount = 0;
|
||||
|
||||
int OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
|
||||
{
|
||||
Timer timer("Swapchain::endFrame");
|
||||
// Blit frame to swapchain
|
||||
|
||||
if (!mXR->sessionRunning())
|
||||
return;
|
||||
return -1;
|
||||
|
||||
|
||||
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||
uint32_t swapchainImageIndex = 0;
|
||||
|
@ -134,11 +137,23 @@ namespace MWVR {
|
|||
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo));
|
||||
|
||||
// Oculus bug: Either the swapchain image index is off by 1 or xrEndFrame() choses the wrong swapchain image.
|
||||
//mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[0].image);
|
||||
//mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[1].image);
|
||||
//mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[2].image);
|
||||
// mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[(swapchainImageIndex + 1) % 3].image);
|
||||
mRenderBuffer->endFrame(gc, mSwapchainImageBuffers[swapchainImageIndex].image);
|
||||
|
||||
//Log(Debug::Verbose) << "swapchainImageIndex: " << swapchainImageIndex;
|
||||
//Log(Debug::Verbose) << "swapchainImage: " << mSwapchainImageBuffers[swapchainImageIndex].image;
|
||||
static int asdf = 0;
|
||||
//Log(Debug::Verbose) << "OpenXRSwapchainImpl ENDFRAME[ " << (asdf++ / 3) << "]";
|
||||
|
||||
swapCount = asdf / 3;
|
||||
|
||||
XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
||||
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
|
||||
|
||||
return swapchainImageIndex;
|
||||
}
|
||||
|
||||
OpenXRSwapchain::OpenXRSwapchain(
|
||||
|
@ -156,9 +171,9 @@ namespace MWVR {
|
|||
impl().beginFrame(gc);
|
||||
}
|
||||
|
||||
void OpenXRSwapchain::endFrame(osg::GraphicsContext* gc)
|
||||
int OpenXRSwapchain::endFrame(osg::GraphicsContext* gc)
|
||||
{
|
||||
impl().endFrame(gc);
|
||||
return impl().endFrame(gc);
|
||||
}
|
||||
|
||||
const XrSwapchainSubImage&
|
||||
|
|
|
@ -10,12 +10,13 @@ namespace MWVR
|
|||
{
|
||||
class OpenXRSwapchainImpl;
|
||||
|
||||
extern int swapCount;
|
||||
|
||||
class OpenXRSwapchain
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
std::vector<int64_t> requestedFormats{};
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
int samples = -1;
|
||||
|
@ -29,7 +30,7 @@ namespace MWVR
|
|||
//! Prepare for render (set FBO)
|
||||
void beginFrame(osg::GraphicsContext* gc);
|
||||
//! Finalize render
|
||||
void endFrame(osg::GraphicsContext* gc);
|
||||
int endFrame(osg::GraphicsContext* gc);
|
||||
//! Get the view surface
|
||||
const XrSwapchainSubImage& subImage(void) const;
|
||||
//! Width of the view surface
|
||||
|
|
|
@ -17,17 +17,15 @@ namespace MWVR {
|
|||
|
||||
OpenXRView::OpenXRView(
|
||||
osg::ref_ptr<OpenXRManager> XR,
|
||||
std::string name)
|
||||
std::string name,
|
||||
OpenXRSwapchain::Config config,
|
||||
osg::ref_ptr<osg::State> state)
|
||||
: mXR(XR)
|
||||
, mSwapchain(nullptr)
|
||||
, mSwapchainConfig{}
|
||||
, mSwapchainConfig{ config }
|
||||
, mSwapchain(new OpenXRSwapchain(mXR, state, mSwapchainConfig))
|
||||
, mName(name)
|
||||
, mTimer(mName.c_str())
|
||||
{
|
||||
mSwapchainConfig.requestedFormats = {
|
||||
GL_RGBA8,
|
||||
GL_RGBA8_SNORM,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
OpenXRView::~OpenXRView()
|
||||
|
@ -49,78 +47,35 @@ namespace MWVR {
|
|||
|
||||
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
|
||||
|
||||
mPredraw = new OpenXRView::PredrawCallback(camera.get(), this);
|
||||
|
||||
camera->setPreDrawCallback(mPredraw);
|
||||
camera->setFinalDrawCallback(new OpenXRView::PostdrawCallback(camera.get(), this));
|
||||
|
||||
return camera.release();
|
||||
}
|
||||
|
||||
void OpenXRView::setWidth(int width)
|
||||
{
|
||||
mSwapchainConfig.width = width;
|
||||
}
|
||||
|
||||
void OpenXRView::setHeight(int height)
|
||||
{
|
||||
mSwapchainConfig.height = height;
|
||||
}
|
||||
|
||||
void OpenXRView::setSamples(int samples)
|
||||
{
|
||||
mSwapchainConfig.samples = samples;
|
||||
}
|
||||
|
||||
void OpenXRView::prerenderCallback(osg::RenderInfo& renderInfo)
|
||||
{
|
||||
|
||||
if(mName == "LeftEye")
|
||||
mXR->waitFrame();
|
||||
|
||||
Log(Debug::Verbose) << mName << ": prerenderCallback";
|
||||
if (mSwapchain)
|
||||
{
|
||||
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
|
||||
}
|
||||
mTimer.checkpoint("Prerender");
|
||||
}
|
||||
|
||||
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
|
||||
{
|
||||
// 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());
|
||||
|
||||
mTimer.checkpoint("Postrender");
|
||||
}
|
||||
|
||||
bool OpenXRView::realize(osg::ref_ptr<osg::State> state)
|
||||
void OpenXRView::swapBuffers(osg::GraphicsContext* gc)
|
||||
{
|
||||
try {
|
||||
mSwapchain.reset(new OpenXRSwapchain(mXR, state, mSwapchainConfig));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
return !!mSwapchain;
|
||||
swapchain().endFrame(gc);
|
||||
}
|
||||
|
||||
void OpenXRView::PredrawCallback::operator()(osg::RenderInfo& info) const
|
||||
{
|
||||
mView->prerenderCallback(info);
|
||||
}
|
||||
|
||||
void OpenXRView::PostdrawCallback::operator()(osg::RenderInfo& info) const
|
||||
{
|
||||
mView->postrenderCallback(info);
|
||||
}
|
||||
void OpenXRView::setPredictedPose(const Pose& pose)
|
||||
{
|
||||
mPredictedPose = pose;
|
||||
//Log(Debug::Verbose) << mName << " predicted pose updated to " << pose;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,36 +13,6 @@ namespace MWVR
|
|||
class OpenXRView : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
class PredrawCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
PredrawCallback(osg::Camera* camera, OpenXRView* view)
|
||||
: mCamera(camera)
|
||||
, mView(view)
|
||||
{}
|
||||
|
||||
void operator()(osg::RenderInfo& info) const override;
|
||||
|
||||
private:
|
||||
|
||||
osg::observer_ptr<osg::Camera> mCamera;
|
||||
OpenXRView* mView;
|
||||
};
|
||||
class PostdrawCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
PostdrawCallback(osg::Camera* camera, OpenXRView* view)
|
||||
: mCamera(camera)
|
||||
, mView(view)
|
||||
{}
|
||||
|
||||
void operator()(osg::RenderInfo& info) const override;
|
||||
|
||||
private:
|
||||
|
||||
osg::observer_ptr<osg::Camera> mCamera;
|
||||
OpenXRView* mView;
|
||||
};
|
||||
|
||||
class InitialDrawCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
|
@ -51,11 +21,8 @@ namespace MWVR
|
|||
};
|
||||
|
||||
protected:
|
||||
OpenXRView(osg::ref_ptr<OpenXRManager> XR, std::string name);
|
||||
OpenXRView(osg::ref_ptr<OpenXRManager> XR, std::string name, OpenXRSwapchain::Config config, osg::ref_ptr<osg::State> state);
|
||||
virtual ~OpenXRView();
|
||||
void setWidth(int width);
|
||||
void setHeight(int height);
|
||||
void setSamples(int samples);
|
||||
|
||||
public:
|
||||
//! Prepare for render (set FBO)
|
||||
|
@ -66,16 +33,21 @@ namespace MWVR
|
|||
osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
|
||||
//! Get the view surface
|
||||
OpenXRSwapchain& swapchain(void) { return *mSwapchain; }
|
||||
//! Create the view surface
|
||||
bool realize(osg::ref_ptr<osg::State> state);
|
||||
|
||||
protected:
|
||||
void swapBuffers(osg::GraphicsContext* gc);
|
||||
|
||||
void setPredictedPose(const Pose& pose);
|
||||
Pose& predictedPose() { return mPredictedPose; };
|
||||
|
||||
public:
|
||||
osg::ref_ptr<OpenXRManager> mXR;
|
||||
std::unique_ptr<OpenXRSwapchain> mSwapchain;
|
||||
OpenXRSwapchain::Config mSwapchainConfig;
|
||||
std::unique_ptr<OpenXRSwapchain> mSwapchain;
|
||||
std::string mName{};
|
||||
bool mRendering{ false };
|
||||
osg::ref_ptr<OpenXRView::PredrawCallback> mPredraw{ nullptr };
|
||||
Timer mTimer;
|
||||
|
||||
Pose mPredictedPose{ {}, {0,0,0,1}, {} };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,13 @@ namespace MWVR
|
|||
, mMetersPerUnit(metersPerUnit)
|
||||
, mConfigured(false)
|
||||
, mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW})
|
||||
, mXRSession(nullptr)
|
||||
, mPreDraw(new PredrawCallback(this))
|
||||
, mPostDraw(new PostdrawCallback(this))
|
||||
{
|
||||
mViewer->setRealizeOperation(mRealizeOperation);
|
||||
mCompositionLayerProjectionViews[0].pose.orientation.w = 1;
|
||||
mCompositionLayerProjectionViews[1].pose.orientation.w = 1;
|
||||
}
|
||||
|
||||
OpenXRViewer::~OpenXRViewer(void)
|
||||
|
@ -36,24 +41,18 @@ namespace MWVR
|
|||
const XrCompositionLayerBaseHeader*
|
||||
OpenXRViewer::layer()
|
||||
{
|
||||
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;
|
||||
mCompositionLayerProjectionViews[1].fov = stageViews[1].fov;
|
||||
return reinterpret_cast<XrCompositionLayerBaseHeader*>(mLayer.get());
|
||||
}
|
||||
|
||||
void OpenXRViewer::traversals()
|
||||
{
|
||||
Log(Debug::Verbose) << "Pre-Update";
|
||||
//Log(Debug::Verbose) << "Pre-Update";
|
||||
mXR->handleEvents();
|
||||
OpenXRFrameIndexer::instance().advanceUpdateIndex();
|
||||
mViewer->updateTraversal();
|
||||
Log(Debug::Verbose) << "Post-Update";
|
||||
Log(Debug::Verbose) << "Pre-Rendering";
|
||||
//Log(Debug::Verbose) << "Post-Update";
|
||||
//Log(Debug::Verbose) << "Pre-Rendering";
|
||||
mViewer->renderingTraversals();
|
||||
Log(Debug::Verbose) << "Post-Rendering";
|
||||
//Log(Debug::Verbose) << "Post-Rendering";
|
||||
}
|
||||
|
||||
void OpenXRViewer::realize(osg::GraphicsContext* context)
|
||||
|
@ -70,91 +69,102 @@ namespace MWVR
|
|||
return;
|
||||
}
|
||||
|
||||
mMainCamera = mViewer->getCamera();
|
||||
mMainCamera->setName("Main");
|
||||
mMainCamera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
|
||||
|
||||
|
||||
auto mainCamera = mCameras["MainCamera"] = mViewer->getCamera();
|
||||
mainCamera->setName("Main");
|
||||
mainCamera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback());
|
||||
|
||||
// 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);
|
||||
mainCamera->setCullMask(MWRender::Mask_GUI);
|
||||
|
||||
osg::Vec4 clearColor = mMainCamera->getClearColor();
|
||||
osg::Vec4 clearColor = mainCamera->getClearColor();
|
||||
|
||||
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);
|
||||
OpenXRSwapchain::Config leftConfig;
|
||||
leftConfig.width = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectWidth;
|
||||
leftConfig.height = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedImageRectHeight;
|
||||
leftConfig.samples = mXR->impl().mConfigViews[(int)Chirality::LEFT_HAND].recommendedSwapchainSampleCount;
|
||||
OpenXRSwapchain::Config rightConfig;
|
||||
rightConfig.width = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectWidth;
|
||||
rightConfig.height = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedImageRectHeight;
|
||||
rightConfig.samples = mXR->impl().mConfigViews[(int)Chirality::RIGHT_HAND].recommendedSwapchainSampleCount;
|
||||
|
||||
auto leftView = new OpenXRWorldView(mXR, "LeftEye", context->getState(), leftConfig, mMetersPerUnit);
|
||||
auto rightView = new OpenXRWorldView(mXR, "RightEye", context->getState(), rightConfig, mMetersPerUnit);
|
||||
|
||||
mViews["LeftEye"] = leftView;
|
||||
mViews["RightEye"] = rightView;
|
||||
|
||||
auto leftCamera = mCameras["LeftEye"] = leftView->createCamera(0, clearColor, context);
|
||||
auto rightCamera = mCameras["RightEye"] = rightView->createCamera(1, clearColor, context);
|
||||
|
||||
leftCamera->setPreDrawCallback(mPreDraw);
|
||||
rightCamera->setPreDrawCallback(mPreDraw);
|
||||
|
||||
leftCamera->setPostDrawCallback(mPostDraw);
|
||||
rightCamera->setPostDrawCallback(mPostDraw);
|
||||
|
||||
mLeftCamera = mViews[OpenXRWorldView::LEFT_VIEW]->createCamera(OpenXRWorldView::LEFT_VIEW, clearColor, context);
|
||||
mRightCamera = mViews[OpenXRWorldView::RIGHT_VIEW]->createCamera(OpenXRWorldView::RIGHT_VIEW, clearColor, context);
|
||||
// Stereo cameras should only draw the scene (AR layers should later add minimap, health, etc.)
|
||||
leftCamera->setCullMask(~MWRender::Mask_GUI);
|
||||
rightCamera->setCullMask(~MWRender::Mask_GUI);
|
||||
|
||||
//mLeftCamera->setGraphicsContext(mLeftGW);
|
||||
//mRightCamera->setGraphicsContext(mRightGW);
|
||||
leftCamera->setName("LeftEye");
|
||||
rightCamera->setName("RightEye");
|
||||
|
||||
mLeftCamera->setCullMask(~MWRender::Mask_GUI);
|
||||
mRightCamera->setCullMask(~MWRender::Mask_GUI);
|
||||
mViewer->addSlave(leftCamera, leftView->projectionMatrix(), leftView->viewMatrix(), true);
|
||||
mViewer->addSlave(rightCamera, rightView->projectionMatrix(), rightView->viewMatrix(), true);
|
||||
|
||||
mLeftCamera->setName("LeftEye");
|
||||
mRightCamera->setName("RightEye");
|
||||
|
||||
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 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);
|
||||
|
||||
// Rendering main camera is a waste of time with VR enabled
|
||||
//camera->setGraphicsContext(nullptr);
|
||||
mXRInput.reset(new OpenXRInputManager(mXR));
|
||||
|
||||
mCompositionLayerProjectionViews[0].subImage = leftView->swapchain().subImage();
|
||||
mCompositionLayerProjectionViews[1].subImage = rightView->swapchain().subImage();
|
||||
|
||||
mLayer.reset(new XrCompositionLayerProjection);
|
||||
mLayer->type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
|
||||
mLayer->space = mXR->impl().mReferenceSpaceStage;
|
||||
mLayer->viewCount = 2;
|
||||
mLayer->views = mCompositionLayerProjectionViews.data();
|
||||
mCompositionLayerProjectionViews[OpenXRWorldView::LEFT_VIEW].subImage = mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().subImage();
|
||||
mCompositionLayerProjectionViews[OpenXRWorldView::RIGHT_VIEW].subImage = mViews[OpenXRWorldView::RIGHT_VIEW]->swapchain().subImage();
|
||||
|
||||
mXR->impl().mLayerStack.setLayer(OpenXRLayerStack::WORLD_VIEW_LAYER, this);
|
||||
|
||||
OpenXRSwapchain::Config config;
|
||||
config.requestedFormats = {
|
||||
GL_RGBA8,
|
||||
GL_RGBA8_SNORM,
|
||||
};
|
||||
config.width = mMainCamera->getViewport()->width();
|
||||
config.height = mMainCamera->getViewport()->height();
|
||||
config.width = mainCamera->getViewport()->width();
|
||||
config.height = mainCamera->getViewport()->height();
|
||||
config.samples = 1;
|
||||
|
||||
// Mirror texture doesn't have to be an OpenXR swapchain.
|
||||
// It's just convenient.
|
||||
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)));
|
||||
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());
|
||||
auto menuView = new OpenXRMenu(mXR, config, context->getState(), "MainMenu", osg::Vec2(1.f, 1.f));
|
||||
mViews["MenuView"] = menuView;
|
||||
|
||||
mMainCamera->getGraphicsContext()->setSwapCallback(new OpenXRViewer::SwapBuffersCallback(this));
|
||||
mMainCamera->setGraphicsContext(nullptr);
|
||||
auto menuCamera = mCameras["MenuView"] = menuView->createCamera(2, clearColor, context);
|
||||
menuCamera->setCullMask(MWRender::Mask_GUI);
|
||||
menuCamera->setName("MenuView");
|
||||
menuCamera->setPreDrawCallback(mPreDraw);
|
||||
menuCamera->setPostDrawCallback(mPostDraw);
|
||||
|
||||
mViewer->addSlave(menuCamera, true);
|
||||
|
||||
mXRSession.reset(new OpenXRSession(mXR));
|
||||
mViewer->getSlave(0)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, mXRSession.get(), leftView, context);
|
||||
mViewer->getSlave(1)._updateSlaveCallback = new OpenXRWorldView::UpdateSlaveCallback(mXR, mXRSession.get(), rightView, context);
|
||||
|
||||
mainCamera->getGraphicsContext()->setSwapCallback(new OpenXRViewer::SwapBuffersCallback(this));
|
||||
mainCamera->setGraphicsContext(nullptr);
|
||||
mXRSession->setLayer(OpenXRLayerStack::WORLD_VIEW_LAYER, this);
|
||||
mXRSession->setLayer(OpenXRLayerStack::MENU_VIEW_LAYER, dynamic_cast<OpenXRLayer*>(mViews["MenuView"].get()));
|
||||
mConfigured = true;
|
||||
|
||||
}
|
||||
|
||||
void OpenXRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc) const
|
||||
void OpenXRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc)
|
||||
{
|
||||
mMirrorTextureSwapchain->beginFrame(gc);
|
||||
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());
|
||||
mViews["LeftEye"]->swapchain().renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
|
||||
mViews["RightEye"]->swapchain().renderBuffer()->blit(gc, mMirrorTextureSwapchain->width() / 2, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
|
||||
|
||||
//mXRMenu->swapchain().current()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
|
||||
}
|
||||
|
@ -163,34 +173,46 @@ namespace MWVR
|
|||
OpenXRViewer::SwapBuffersCallback::swapBuffersImplementation(
|
||||
osg::GraphicsContext* gc)
|
||||
{
|
||||
mViewer->swapBuffers(gc);
|
||||
mViewer->mXRSession->swapBuffers(gc);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
////// NEW SYSTEM
|
||||
Timer timer("OpenXRViewer::SwapBuffers");
|
||||
mViews["LeftEye"]->swapBuffers(gc);
|
||||
mViews["RightEye"]->swapBuffers(gc);
|
||||
timer.checkpoint("Views");
|
||||
|
||||
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);
|
||||
|
||||
//mMirrorTextureSwapchain->renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height());
|
||||
auto eyePoses = mXRSession->predictedPoses().eye;
|
||||
auto leftEyePose = toXR(mViews["LeftEye"]->predictedPose());
|
||||
auto rightEyePose = toXR(mViews["RightEye"]->predictedPose());
|
||||
mCompositionLayerProjectionViews[0].pose = leftEyePose;
|
||||
mCompositionLayerProjectionViews[1].pose = rightEyePose;
|
||||
timer.checkpoint("Poses");
|
||||
|
||||
//mMirrorTextureSwapchain->endFrame(gc);
|
||||
gc->swapBuffersImplementation();
|
||||
// TODO: Keep track of these in the session too.
|
||||
auto stageViews = mXR->impl().getPredictedViews(mXRSession->predictedDisplayTime(), TrackedSpace::STAGE);
|
||||
mCompositionLayerProjectionViews[0].fov = stageViews[0].fov;
|
||||
mCompositionLayerProjectionViews[1].fov = stageViews[1].fov;
|
||||
timer.checkpoint("Fovs");
|
||||
|
||||
|
||||
if (!mLayer)
|
||||
{
|
||||
mLayer.reset(new XrCompositionLayerProjection);
|
||||
mLayer->type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
|
||||
mLayer->space = mXR->impl().mReferenceSpaceStage;
|
||||
mLayer->viewCount = 2;
|
||||
mLayer->views = mCompositionLayerProjectionViews.data();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -206,4 +228,37 @@ namespace MWVR
|
|||
{
|
||||
return mViewer->realized();
|
||||
}
|
||||
|
||||
void OpenXRViewer::preDrawCallback(osg::RenderInfo& info)
|
||||
{
|
||||
auto* camera = info.getCurrentCamera();
|
||||
auto name = camera->getName();
|
||||
auto& view = mViews[name];
|
||||
|
||||
if (name == "LeftEye")
|
||||
{
|
||||
mXR->beginFrame();
|
||||
auto& poses = mXRSession->predictedPoses();
|
||||
auto menuPose = poses.head[(int)TrackedSpace::STAGE];
|
||||
mViews["MenuView"]->setPredictedPose(menuPose);
|
||||
}
|
||||
|
||||
view->prerenderCallback(info);
|
||||
}
|
||||
|
||||
void OpenXRViewer::postDrawCallback(osg::RenderInfo& info)
|
||||
{
|
||||
auto* camera = info.getCurrentCamera();
|
||||
auto name = camera->getName();
|
||||
auto& view = mViews[name];
|
||||
|
||||
view->postrenderCallback(info);
|
||||
|
||||
// OSG will sometimes overwrite the predraw callback.
|
||||
if (camera->getPreDrawCallback() != mPreDraw)
|
||||
{
|
||||
camera->setPreDrawCallback(mPreDraw);
|
||||
Log(Debug::Warning) << ("osg overwrote predraw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <osg/Group>
|
||||
#include <osg/Camera>
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include "openxrmanager.hpp"
|
||||
#include "openxrsession.hpp"
|
||||
#include "openxrlayer.hpp"
|
||||
#include "openxrworldview.hpp"
|
||||
#include "openxrmenu.hpp"
|
||||
|
@ -41,6 +43,34 @@ namespace MWVR
|
|||
OpenXRViewer* mViewer;
|
||||
};
|
||||
|
||||
class PredrawCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
PredrawCallback(OpenXRViewer* viewer)
|
||||
: mViewer(viewer)
|
||||
{}
|
||||
|
||||
void operator()(osg::RenderInfo& info) const override { mViewer->preDrawCallback(info); };
|
||||
|
||||
private:
|
||||
|
||||
OpenXRViewer* mViewer;
|
||||
};
|
||||
|
||||
class PostdrawCallback : public osg::Camera::DrawCallback
|
||||
{
|
||||
public:
|
||||
PostdrawCallback(OpenXRViewer* viewer)
|
||||
: mViewer(viewer)
|
||||
{}
|
||||
|
||||
void operator()(osg::RenderInfo& info) const override { mViewer->postDrawCallback(info); };
|
||||
|
||||
private:
|
||||
|
||||
OpenXRViewer* mViewer;
|
||||
};
|
||||
|
||||
public:
|
||||
OpenXRViewer(
|
||||
osg::ref_ptr<OpenXRManager> XR,
|
||||
|
@ -54,31 +84,28 @@ namespace MWVR
|
|||
const XrCompositionLayerBaseHeader* layer() override;
|
||||
|
||||
void traversals();
|
||||
void blitEyesToMirrorTexture(osg::GraphicsContext* gc) const;
|
||||
void swapBuffers(osg::GraphicsContext* gc) ;
|
||||
void preDrawCallback(osg::RenderInfo& info);
|
||||
void postDrawCallback(osg::RenderInfo& info);
|
||||
void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
|
||||
void swapBuffers(osg::GraphicsContext* gc) override;
|
||||
void realize(osg::GraphicsContext* gc);
|
||||
|
||||
bool realized() { return mConfigured; }
|
||||
|
||||
protected:
|
||||
public:
|
||||
|
||||
std::unique_ptr<XrCompositionLayerProjection> mLayer = nullptr;
|
||||
std::vector<XrCompositionLayerProjectionView> mCompositionLayerProjectionViews;
|
||||
osg::observer_ptr<OpenXRManager> mXR = nullptr;
|
||||
osg::ref_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr;
|
||||
osg::observer_ptr<osgViewer::Viewer> mViewer = nullptr;
|
||||
std::unique_ptr<MWVR::OpenXRInputManager> mXRInput = nullptr;
|
||||
std::unique_ptr<MWVR::OpenXRMenu> mXRMenu = nullptr;
|
||||
std::array<osg::ref_ptr<OpenXRWorldView>, 2> mViews{};
|
||||
std::unique_ptr<OpenXRInputManager> mXRInput = nullptr;
|
||||
std::unique_ptr<OpenXRSession> mXRSession = nullptr;
|
||||
std::map<std::string, osg::ref_ptr<OpenXRView> > mViews{};
|
||||
std::map<std::string, osg::ref_ptr<osg::Camera> > mCameras{};
|
||||
|
||||
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mViewerGW;
|
||||
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mLeftGW;
|
||||
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mRightGW;
|
||||
|
||||
osg::Camera* mMainCamera = nullptr;
|
||||
osg::Camera* mMenuCamera = nullptr;
|
||||
osg::Camera* mLeftCamera = nullptr;
|
||||
osg::Camera* mRightCamera = nullptr;
|
||||
PredrawCallback* mPreDraw{ nullptr };
|
||||
PostdrawCallback* mPostDraw{ nullptr };
|
||||
|
||||
std::unique_ptr<OpenXRSwapchain> mMirrorTextureSwapchain = nullptr;
|
||||
|
||||
|
|
|
@ -78,57 +78,45 @@ namespace MWVR
|
|||
|
||||
osg::Matrix OpenXRWorldView::projectionMatrix()
|
||||
{
|
||||
auto hmdViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::VIEW);
|
||||
// TODO: Get this from the session predictions instead
|
||||
auto hmdViews = mXR->impl().getPredictedViews(mXR->impl().frameState().predictedDisplayTime, TrackedSpace::VIEW);
|
||||
|
||||
float near = Settings::Manager::getFloat("near clip", "Camera");
|
||||
float far = Settings::Manager::getFloat("viewing distance", "Camera") * mMetersPerUnit;
|
||||
//return perspectiveFovMatrix()
|
||||
return perspectiveFovMatrix(near, far, hmdViews[mView].fov);
|
||||
if(mName == "LeftEye")
|
||||
return perspectiveFovMatrix(near, far, hmdViews[0].fov);
|
||||
return perspectiveFovMatrix(near, far, hmdViews[1].fov);
|
||||
}
|
||||
|
||||
osg::Matrix OpenXRWorldView::viewMatrix()
|
||||
{
|
||||
osg::Matrix viewMatrix;
|
||||
auto hmdViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::VIEW);
|
||||
auto pose = hmdViews[mView].pose;
|
||||
osg::Vec3 position = osg::fromXR(pose.position);
|
||||
auto pose = predictedPose();
|
||||
osg::Vec3 position = pose.position;
|
||||
|
||||
auto stageViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::STAGE);
|
||||
auto stagePose = stageViews[mView].pose;
|
||||
|
||||
// Comfort shortcut.
|
||||
// TODO: STAGE movement should affect in-game movement but not like this.
|
||||
// This method should only be using HEAD view.
|
||||
// But for comfort i'm keeping this until such movement has been implemented.
|
||||
#if 1
|
||||
position = -osg::fromXR(stagePose.position);
|
||||
position.y() += 0.9144 * 2.;
|
||||
// Comfort shortcut.
|
||||
// TODO: Head pose should affect more than just the view !
|
||||
position.y() -= 0.9144 * 2.;
|
||||
#endif
|
||||
|
||||
// invert orientation (conjugate of Quaternion) and position to apply to the view matrix as offset
|
||||
viewMatrix.setTrans(position * mMetersPerUnit);
|
||||
viewMatrix.postMultRotate(osg::fromXR(stagePose.orientation).conj());
|
||||
|
||||
// Scale to world units
|
||||
//viewMatrix.postMultScale(osg::Vec3d(1.f / mMetersPerUnit, 1.f / mMetersPerUnit, 1.f / mMetersPerUnit));
|
||||
// invert orientation (co jugate of Quaternion) and position to apply to the view matrix as offset
|
||||
osg::Matrix viewMatrix;
|
||||
viewMatrix.setTrans(-position * mMetersPerUnit);
|
||||
viewMatrix.postMultRotate(pose.orientation.conj());
|
||||
|
||||
return viewMatrix;
|
||||
}
|
||||
|
||||
OpenXRWorldView::OpenXRWorldView(
|
||||
osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view)
|
||||
: OpenXRView(XR, name)
|
||||
osg::ref_ptr<OpenXRManager> XR,
|
||||
std::string name,
|
||||
osg::ref_ptr<osg::State> state,
|
||||
OpenXRSwapchain::Config config,
|
||||
float metersPerUnit )
|
||||
: OpenXRView(XR, name, config, state)
|
||||
, mMetersPerUnit(metersPerUnit)
|
||||
, mView(view)
|
||||
{
|
||||
auto config = mXR->impl().mConfigViews[view];
|
||||
|
||||
setWidth(config.recommendedImageRectWidth);
|
||||
setHeight(config.recommendedImageRectHeight);
|
||||
setSamples(config.recommendedSwapchainSampleCount);
|
||||
|
||||
realize(state);
|
||||
// XR->setViewSubImage(view, mSwapchain->subImage());
|
||||
}
|
||||
|
||||
OpenXRWorldView::~OpenXRWorldView()
|
||||
|
@ -154,13 +142,8 @@ namespace MWVR
|
|||
auto* camera = renderInfo.getCurrentCamera();
|
||||
auto name = camera->getName();
|
||||
|
||||
Log(Debug::Verbose) << "Updating camera " << name;
|
||||
//Log(Debug::Verbose) << "Updating camera " << name;
|
||||
|
||||
auto viewMatrix = view->getCamera()->getViewMatrix() * this->viewMatrix();
|
||||
auto projectionMatrix = this->projectionMatrix();
|
||||
|
||||
camera->setViewMatrix(viewMatrix);
|
||||
camera->setProjectionMatrix(projectionMatrix);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -168,21 +151,38 @@ namespace MWVR
|
|||
osg::View& view,
|
||||
osg::View::Slave& slave)
|
||||
{
|
||||
mXR->handleEvents();
|
||||
if (!mXR->sessionRunning())
|
||||
return;
|
||||
|
||||
|
||||
mView->mTimer.checkpoint("UpdateSlave");
|
||||
auto* camera = slave._camera.get();
|
||||
auto name = camera->getName();
|
||||
|
||||
Log(Debug::Verbose) << "Updating slave " << name;
|
||||
Log(Debug::Verbose) << name << ": slave update";
|
||||
|
||||
//auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
|
||||
//auto projMatrix = mView->projectionMatrix();
|
||||
auto& poses = mSession->predictedPoses();
|
||||
|
||||
//camera->setViewMatrix(viewMatrix);
|
||||
//camera->setProjectionMatrix(projMatrix);
|
||||
if (name == "LeftEye")
|
||||
{
|
||||
mXR->handleEvents();
|
||||
mSession->waitFrame();
|
||||
auto leftEyePose = poses.eye[(int)Chirality::LEFT_HAND][(int)TrackedSpace::STAGE];
|
||||
mView->setPredictedPose(leftEyePose);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rightEyePose = poses.eye[(int)Chirality::RIGHT_HAND][(int)TrackedSpace::STAGE];
|
||||
mView->setPredictedPose(rightEyePose);
|
||||
}
|
||||
if (!mXR->sessionRunning())
|
||||
return;
|
||||
|
||||
// TODO: This is where controls should update
|
||||
|
||||
|
||||
|
||||
auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix();
|
||||
auto projectionMatrix = mView->projectionMatrix();
|
||||
|
||||
camera->setViewMatrix(viewMatrix);
|
||||
camera->setProjectionMatrix(projectionMatrix);
|
||||
|
||||
slave.updateSlaveImplementation(view);
|
||||
}
|
||||
|
|
|
@ -2,36 +2,31 @@
|
|||
#define OPENXRWORLDVIEW_HPP
|
||||
|
||||
#include "openxrview.hpp"
|
||||
#include "openxrsession.hpp"
|
||||
|
||||
namespace MWVR
|
||||
{
|
||||
class OpenXRWorldView : public OpenXRView
|
||||
{
|
||||
public:
|
||||
enum SubView
|
||||
{
|
||||
LEFT_VIEW = 0,
|
||||
RIGHT_VIEW = 1,
|
||||
SUBVIEW_MAX = RIGHT_VIEW, //!< Used to size subview arrays. Not a valid input.
|
||||
};
|
||||
|
||||
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)
|
||||
UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, OpenXRSession* session, osg::ref_ptr<OpenXRWorldView> view, osg::GraphicsContext* gc)
|
||||
: mXR(XR), mSession(session), mView(view), mGC(gc)
|
||||
{}
|
||||
|
||||
void updateSlave(osg::View& view, osg::View::Slave& slave) override;
|
||||
|
||||
private:
|
||||
osg::ref_ptr<OpenXRManager> mXR;
|
||||
OpenXRSession* mSession;
|
||||
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(osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, OpenXRSwapchain::Config config, float metersPerUnit);
|
||||
~OpenXRWorldView();
|
||||
|
||||
//! Prepare for render (update matrices)
|
||||
|
@ -40,11 +35,8 @@ namespace MWVR
|
|||
osg::Matrix projectionMatrix();
|
||||
//! View offset for this view
|
||||
osg::Matrix viewMatrix();
|
||||
//! Which view this is
|
||||
SubView view() { return mView; }
|
||||
|
||||
float mMetersPerUnit = 1.f;
|
||||
SubView mView;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue