1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-19 17:09:40 +00:00

Predictions work perfectly with this. Committing that and clean up later.

This commit is contained in:
Mads Buvik Sandvei 2020-02-02 13:12:53 +01:00
parent fea964a6f3
commit 14039e5e25
20 changed files with 778 additions and 493 deletions

View file

@ -124,6 +124,8 @@ if(BUILD_VR_OPENXR)
mwvr/openxrmanagerimpl.cpp mwvr/openxrmanagerimpl.cpp
mwvr/openxrmenu.hpp mwvr/openxrmenu.hpp
mwvr/openxrmenu.cpp mwvr/openxrmenu.cpp
mwvr/openxrsession.hpp
mwvr/openxrsession.cpp
mwvr/openxrswapchain.hpp mwvr/openxrswapchain.hpp
mwvr/openxrswapchain.cpp mwvr/openxrswapchain.cpp
mwvr/openxrtexture.hpp mwvr/openxrtexture.hpp

View file

@ -514,9 +514,10 @@ namespace MWVR
void void
OpenXRInputManagerImpl::updateHandTracking() OpenXRInputManagerImpl::updateHandTracking()
{ {
for (auto hand : { LEFT_HAND, RIGHT_HAND }) { // TODO
CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().predictedDisplayTime(OpenXRFrameIndexer::instance().updateIndex()), &mHandSpaceLocation[hand])); //for (auto hand : { LEFT_HAND, RIGHT_HAND }) {
} // CHECK_XRCMD(xrLocateSpace(mHandSpace[hand], mXR->impl().mReferenceSpaceStage, mXR->impl().predictedDisplayTime(OpenXRFrameIndexer::instance().updateIndex()), &mHandSpaceLocation[hand]));
//}
} }
XrPath XrPath

View file

@ -5,7 +5,7 @@ namespace MWVR {
void OpenXRLayerStack::setLayer(Layer layer, OpenXRLayer* layerObj) void OpenXRLayerStack::setLayer(Layer layer, OpenXRLayer* layerObj)
{ {
mLayers[layer] = nullptr; mLayers[layer] = layerObj->layer();
mLayerObjects[layer] = layerObj; mLayerObjects[layer] = layerObj;
} }

View file

@ -16,6 +16,7 @@ namespace MWVR
virtual ~OpenXRLayer(void) = default; virtual ~OpenXRLayer(void) = default;
virtual const XrCompositionLayerBaseHeader* layer() = 0; virtual const XrCompositionLayerBaseHeader* layer() = 0;
virtual void swapBuffers(osg::GraphicsContext* gc) = 0;
}; };
class OpenXRLayerStack class OpenXRLayerStack
@ -32,9 +33,11 @@ namespace MWVR
OpenXRLayerStack() = default; OpenXRLayerStack() = default;
~OpenXRLayerStack() = default; ~OpenXRLayerStack() = default;
void setLayer(Layer layer, OpenXRLayer* layerObj); void setLayer(Layer layer, OpenXRLayer* layerObj);
int layerCount(); int layerCount();
const XrCompositionLayerBaseHeader** layerHeaders(); const XrCompositionLayerBaseHeader** layerHeaders();
LayerObjectStack& layerObjects() { return mLayerObjects; };
private: private:
LayerObjectStack mLayerObjects; LayerObjectStack mLayerObjects;

View file

@ -16,12 +16,94 @@
#include <vector> #include <vector>
#include <array> #include <array>
#include <map>
#include <iostream> #include <iostream>
namespace MWVR 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() OpenXRManager::OpenXRManager()
: mPrivate(nullptr) : mPrivate(nullptr)
, mMutex() , mMutex()
@ -73,10 +155,10 @@ namespace MWVR
return impl().beginFrame(); return impl().beginFrame();
} }
void OpenXRManager::endFrame() void OpenXRManager::endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack)
{ {
if (realized()) if (realized())
return impl().endFrame(); return impl().endFrame(displayTime, layerStack);
} }
void OpenXRManager::updateControls() void OpenXRManager::updateControls()
@ -131,59 +213,6 @@ namespace MWVR
{ {
} }
//#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 <<( std::ostream& operator <<(

View file

@ -20,30 +20,29 @@ namespace MWVR
{ {
struct Timer struct Timer
{ {
Timer(std::string name) : mName(name) using Measure = std::pair < const char*, int64_t >;
{ using Measures = std::vector < Measure >;
mBegin = std::chrono::steady_clock::now(); using MeasurementContext = std::pair < const char*, Measures* >;
}
~Timer() Timer(const char* name);
{ ~Timer();
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); void checkpoint(const char* name);
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::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. //! Represents the pose of a limb in VR space.
struct Pose struct Pose
{ {
//! Position in VR space //! Position in VR space
osg::Vec3 position; osg::Vec3 position{ 0,0,0 };
//! Orientation in VR space. //! 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 //! 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. //! Describes what limb to track.
@ -57,31 +56,16 @@ namespace MWVR
//! Describes what space to track the limb in //! Describes what space to track the limb in
enum class TrackedSpace enum class TrackedSpace
{ {
STAGE, //!< Track limb in the VR stage space. Meaning a space with a floor level origin and fixed horizontal orientation. STAGE=0, //!< 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. 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(); LEFT_HAND = 0,
RIGHT_HAND = 1
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. // Use the pimpl pattern to avoid cluttering the namespace with openxr dependencies.
class OpenXRManagerImpl; class OpenXRManagerImpl;
@ -122,7 +106,7 @@ namespace MWVR
void handleEvents(); void handleEvents();
void waitFrame(); void waitFrame();
void beginFrame(); void beginFrame();
void endFrame(); void endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack);
void updateControls(); void updateControls();
void realize(osg::GraphicsContext* gc); void realize(osg::GraphicsContext* gc);

View file

@ -254,17 +254,19 @@ namespace MWVR
Log(Debug::Verbose) << ss.str(); Log(Debug::Verbose) << ss.str();
} }
XrFrameState OpenXRManagerImpl::frameState()
{
std::unique_lock<std::mutex> lock(mFrameStateMutex);
return mFrameState;
}
void void
OpenXRManagerImpl::waitFrame() OpenXRManagerImpl::waitFrame()
{ {
Log(Debug::Verbose) << "OpenXRSesssion::WaitFrame";
Timer timer("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; static std::mutex waitFrameMutex;
std::unique_lock<std::mutex> lock(waitFrameMutex);
if (!mSessionRunning) if (!mSessionRunning)
return; return;
@ -274,40 +276,59 @@ namespace MWVR
CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState)); CHECK_XRCMD(xrWaitFrame(mSession, &frameWaitInfo, &frameState));
std::unique_lock<std::mutex> lock(mFrameStateMutex);
mTimeKeeper.progressToNextFrame(frameState); mFrameState = frameState;
Log(Debug::Verbose) << "OpenXRSesssion::WaitFrame END";
//}).detach();
} }
void void
OpenXRManagerImpl::beginFrame() OpenXRManagerImpl::beginFrame()
{ {
Log(Debug::Verbose) << "OpenXRSesssion::BeginFrame";
Timer timer("beginFrame"); Timer timer("beginFrame");
XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO }; XrFrameBeginInfo frameBeginInfo{ XR_TYPE_FRAME_BEGIN_INFO };
CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo)); CHECK_XRCMD(xrBeginFrame(mSession, &frameBeginInfo));
Log(Debug::Verbose) << "OpenXRSesssion::BeginFrame END";
} }
void void
OpenXRManagerImpl::endFrame() OpenXRManagerImpl::endFrame(int64_t displayTime, OpenXRLayerStack* layerStack)
{ {
Log(Debug::Verbose) << "OpenXRSesssion::EndFrame";
Timer timer("endFrame()"); Timer timer("endFrame()");
if (!mSessionRunning) if (!mSessionRunning)
return; return;
XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO }; XrFrameEndInfo frameEndInfo{ XR_TYPE_FRAME_END_INFO };
frameEndInfo.displayTime = mTimeKeeper.predictedDisplayTime(OpenXRFrameIndexer::instance().renderIndex()); frameEndInfo.displayTime = displayTime;
frameEndInfo.environmentBlendMode = mEnvironmentBlendMode; frameEndInfo.environmentBlendMode = mEnvironmentBlendMode;
frameEndInfo.layerCount = mLayerStack.layerCount(); if (layerStack)
frameEndInfo.layers = mLayerStack.layerHeaders(); {
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)); CHECK_XRCMD(xrEndFrame(mSession, &frameEndInfo));
Log(Debug::Verbose) << "OpenXRSesssion::EndFrame END";
} }
std::array<XrView, 2> std::array<XrView, 2>
OpenXRManagerImpl::getPredictedViews( OpenXRManagerImpl::getPredictedViews(
int64_t frameIndex, int64_t predictedDisplayTime,
TrackedSpace space) TrackedSpace space)
{ {
@ -317,7 +338,7 @@ namespace MWVR
XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO }; XrViewLocateInfo viewLocateInfo{ XR_TYPE_VIEW_LOCATE_INFO };
viewLocateInfo.viewConfigurationType = mViewConfigType; viewLocateInfo.viewConfigurationType = mViewConfigType;
viewLocateInfo.displayTime = mTimeKeeper.predictedDisplayTime(frameIndex); viewLocateInfo.displayTime = predictedDisplayTime;
switch (space) switch (space)
{ {
case TrackedSpace::STAGE: case TrackedSpace::STAGE:
@ -332,7 +353,7 @@ namespace MWVR
return views; 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 }; XrSpaceLocation location{ XR_TYPE_SPACE_LOCATION };
XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY }; XrSpaceVelocity velocity{ XR_TYPE_SPACE_VELOCITY };
@ -360,9 +381,9 @@ namespace MWVR
referenceSpace = mReferenceSpaceView; referenceSpace = mReferenceSpaceView;
break; break;
} }
CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, mTimeKeeper.predictedDisplayTime(frameIndex), &location)); CHECK_XRCMD(xrLocateSpace(limbSpace, referenceSpace, predictedDisplayTime, &location));
if (!(velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT)) //if (!(velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT))
Log(Debug::Warning) << "Unable to acquire linear velocity"; // Log(Debug::Warning) << "Unable to acquire linear velocity";
return MWVR::Pose{ return MWVR::Pose{
osg::fromXR(location.pose.position), osg::fromXR(location.pose.position),
osg::fromXR(location.pose.orientation), osg::fromXR(location.pose.orientation),
@ -439,12 +460,6 @@ namespace MWVR
} }
} }
XrTime OpenXRManagerImpl::predictedDisplayTime(int64_t frameIndex)
{
return mTimeKeeper.predictedDisplayTime(frameIndex);
}
const XrEventDataBaseHeader* const XrEventDataBaseHeader*
OpenXRManagerImpl::nextEvent() OpenXRManagerImpl::nextEvent()
{ {
@ -476,65 +491,65 @@ namespace MWVR
return XrPosef{ osg::toXR(pose.orientation), osg::toXR(pose.position) }; return XrPosef{ osg::toXR(pose.orientation), osg::toXR(pose.position) };
} }
XrTime OpenXRTimeKeeper::predictedDisplayTime(int64_t frameIndex) //XrTime OpenXRTimeKeeper::predictedDisplayTime(int64_t frameIndex)
{ //{
std::unique_lock<std::mutex> lock(mMutex); // std::unique_lock<std::mutex> lock(mMutex);
//auto prediction = mPredictedFrameTime; // //auto prediction = mPredictedFrameTime;
//auto predictedPeriod = mPredictedPeriod; // //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) //void OpenXRTimeKeeper::progressToNextFrame(XrFrameState frameState)
{ //{
std::unique_lock<std::mutex> lock(mMutex); // std::unique_lock<std::mutex> lock(mMutex);
OpenXRFrameIndexer::instance().advanceRenderIndex(); // OpenXRFrameIndexer::instance().advanceRenderIndex();
//XrDuration realPeriod = frameState.predictedDisplayPeriod; // //XrDuration realPeriod = frameState.predictedDisplayPeriod;
//if(mFrameState.predictedDisplayTime != 0) // //if(mFrameState.predictedDisplayTime != 0)
// realPeriod = frameState.predictedDisplayTime - mFrameState.predictedDisplayTime; // // realPeriod = frameState.predictedDisplayTime - mFrameState.predictedDisplayTime;
auto now = clock::now(); // auto now = clock::now();
auto nanoseconds_elapsed = std::chrono::duration_cast<nanoseconds>(now - mLastFrame); // auto nanoseconds_elapsed = std::chrono::duration_cast<nanoseconds>(now - mLastFrameTimePoint);
XrDuration realPeriod = nanoseconds_elapsed.count(); // XrDuration realPeriod = nanoseconds_elapsed.count();
mFrameState = frameState; // mFrameState = frameState;
mPredictedFrameTime = mFrameState.predictedDisplayTime; // mPredictedFrameTime = mFrameState.predictedDisplayTime + mFrameState.predictedDisplayPeriod * 4;
mPredictedPeriod = mFrameState.predictedDisplayPeriod; // mPredictedPeriod = mFrameState.predictedDisplayPeriod;
// Real fps is lower than expected fps // // Real fps is lower than expected fps
// Adjust predictions // // Adjust predictions
// (Really wish OpenXR would handle this!) // // (Really wish OpenXR would handle this!)
if (realPeriod > mFrameState.predictedDisplayPeriod) // //if (realPeriod > mFrameState.predictedDisplayPeriod)
{ // //{
// predictedDisplayTime refers to the midpoint of the display period // // // predictedDisplayTime refers to the midpoint of the display period
// The upjustment must therefore only be half the magnitude // // // The upjustment must therefore only be half the magnitude
mPredictedFrameTime += (realPeriod - mFrameState.predictedDisplayPeriod) / 2; // // mPredictedFrameTime += (realPeriod - mFrameState.predictedDisplayPeriod) / 2;
mPredictedPeriod = realPeriod; // // mPredictedPeriod = realPeriod;
} // //}
seconds elapsed = std::chrono::duration_cast<seconds>(now - mLastFrame); // seconds elapsed = std::chrono::duration_cast<seconds>(now - mLastFrame);
std::swap(now, mLastFrame); // std::swap(now, mLastFrame);
double fps = 1. / elapsed.count(); // double fps = 1. / elapsed.count();
mFps = mFps * 0.8 + 0.2 * fps; // 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: 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: predictedDisplayTime=" << mFrameState.predictedDisplayTime << ", mPredictedFrameTime=" << mPredictedFrameTime << ", mFps=" << mFps;
} //}
} }
namespace osg namespace osg

View file

@ -44,31 +44,6 @@ namespace MWVR
MWVR::Pose fromXR(XrPosef pose); MWVR::Pose fromXR(XrPosef pose);
XrPosef toXR(MWVR::Pose 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 struct OpenXRManagerImpl
{ {
OpenXRManagerImpl(void); OpenXRManagerImpl(void);
@ -81,14 +56,14 @@ namespace MWVR
const XrEventDataBaseHeader* nextEvent(); const XrEventDataBaseHeader* nextEvent();
void waitFrame(); void waitFrame();
void beginFrame(); void beginFrame();
void endFrame(); void endFrame(int64_t displayTime, class OpenXRLayerStack* layerStack);
std::array<XrView, 2> getPredictedViews(int64_t frameIndex, TrackedSpace mSpace); std::array<XrView, 2> getPredictedViews(int64_t predictedDisplayTime, TrackedSpace mSpace);
MWVR::Pose getPredictedLimbPose(int64_t frameIndex, TrackedLimb limb, TrackedSpace space); MWVR::Pose getPredictedLimbPose(int64_t predictedDisplayTime, TrackedLimb limb, TrackedSpace space);
int eyes(); int eyes();
void handleEvents(); void handleEvents();
void updateControls(); void updateControls();
void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent); void HandleSessionStateChanged(const XrEventDataSessionStateChanged& stateChangedEvent);
XrTime predictedDisplayTime(int64_t frameIndex); XrFrameState frameState();
bool initialized = false; bool initialized = false;
long long mFrameIndex = 0; long long mFrameIndex = 0;
@ -105,10 +80,10 @@ namespace MWVR
XrSpace mReferenceSpaceView = XR_NULL_HANDLE; XrSpace mReferenceSpaceView = XR_NULL_HANDLE;
XrSpace mReferenceSpaceStage = XR_NULL_HANDLE; XrSpace mReferenceSpaceStage = XR_NULL_HANDLE;
XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER }; XrEventDataBuffer mEventDataBuffer{ XR_TYPE_EVENT_DATA_BUFFER };
OpenXRTimeKeeper mTimeKeeper{}; XrFrameState mFrameState{};
OpenXRLayerStack mLayerStack{};
XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN; XrSessionState mSessionState = XR_SESSION_STATE_UNKNOWN;
bool mSessionRunning = false; bool mSessionRunning = false;
std::mutex mFrameStateMutex{};
std::mutex mEventMutex{}; std::mutex mEventMutex{};
}; };
} }

View file

@ -5,27 +5,16 @@
namespace MWVR 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) OpenXRMenu::OpenXRMenu(
: OpenXRView(XR, title) 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) , 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(); //updatePosition();
} }
@ -38,6 +27,14 @@ namespace MWVR
OpenXRMenu::layer() OpenXRMenu::layer()
{ {
updatePosition(); 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()); return reinterpret_cast<XrCompositionLayerBaseHeader*>(mLayer.get());
} }
@ -47,22 +44,36 @@ namespace MWVR
return; return;
if (!mXR->sessionRunning()) if (!mXR->sessionRunning())
return; return;
if (OpenXRFrameIndexer::instance().updateIndex() == 0)
return;
// Menus are position one meter in front of the player, facing the player. // 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 mPose = predictedPose();
auto pose = mXR->impl().getPredictedLimbPose(OpenXRFrameIndexer::instance().updateIndex(), TrackedLimb::HEAD, TrackedSpace::STAGE); mPose.position += mPose.orientation * osg::Vec3(0, 0, -1);
pose.position += pose.orientation * osg::Vec3(0, 0, -1); mPose.orientation = -mPose.orientation;
mLayer->pose.position = osg::toXR(pose.position);
mLayer->pose.orientation = osg::toXR(-pose.orientation); Log(Debug::Verbose) << "Menu pose updated to: " << mPose;
mPositionNeedsUpdate = false; 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;
}
} }
} }

View file

@ -11,17 +11,20 @@ namespace MWVR
{ {
public: 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(); ~OpenXRMenu();
const XrCompositionLayerBaseHeader* layer() override; const XrCompositionLayerBaseHeader* layer() override;
const std::string& title() const { return mTitle; } const std::string& title() const { return mTitle; }
void updatePosition(); void updatePosition();
void postrenderCallback(osg::RenderInfo& renderInfo) override;
void swapBuffers(osg::GraphicsContext* gc) override;
protected: protected:
bool mPositionNeedsUpdate{ true }; bool mPositionNeedsUpdate{ true };
std::unique_ptr<XrCompositionLayerQuad> mLayer = nullptr; std::unique_ptr<XrCompositionLayerQuad> mLayer = nullptr;
std::string mTitle; std::string mTitle;
Pose mPose{ {}, {0,0,0,1}, {} };
osg::Vec2 mExtent;
}; };
} }

View 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;
}

View 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

View file

@ -27,7 +27,7 @@ namespace MWVR {
~OpenXRSwapchainImpl(); ~OpenXRSwapchainImpl();
void beginFrame(osg::GraphicsContext* gc); void beginFrame(osg::GraphicsContext* gc);
void endFrame(osg::GraphicsContext* gc); int endFrame(osg::GraphicsContext* gc);
osg::ref_ptr<OpenXRManager> mXR; osg::ref_ptr<OpenXRManager> mXR;
XrSwapchain mSwapchain = XR_NULL_HANDLE; XrSwapchain mSwapchain = XR_NULL_HANDLE;
@ -118,13 +118,16 @@ namespace MWVR {
mRenderBuffer->beginFrame(gc); mRenderBuffer->beginFrame(gc);
} }
void OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc) int swapCount = 0;
int OpenXRSwapchainImpl::endFrame(osg::GraphicsContext* gc)
{ {
Timer timer("Swapchain::endFrame"); Timer timer("Swapchain::endFrame");
// Blit frame to swapchain // Blit frame to swapchain
if (!mXR->sessionRunning()) if (!mXR->sessionRunning())
return; return -1;
XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO }; XrSwapchainImageAcquireInfo acquireInfo{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
uint32_t swapchainImageIndex = 0; uint32_t swapchainImageIndex = 0;
@ -134,11 +137,23 @@ namespace MWVR {
waitInfo.timeout = XR_INFINITE_DURATION; waitInfo.timeout = XR_INFINITE_DURATION;
CHECK_XRCMD(xrWaitSwapchainImage(mSwapchain, &waitInfo)); 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); 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 }; XrSwapchainImageReleaseInfo releaseInfo{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo)); CHECK_XRCMD(xrReleaseSwapchainImage(mSwapchain, &releaseInfo));
return swapchainImageIndex;
} }
OpenXRSwapchain::OpenXRSwapchain( OpenXRSwapchain::OpenXRSwapchain(
@ -156,9 +171,9 @@ namespace MWVR {
impl().beginFrame(gc); impl().beginFrame(gc);
} }
void OpenXRSwapchain::endFrame(osg::GraphicsContext* gc) int OpenXRSwapchain::endFrame(osg::GraphicsContext* gc)
{ {
impl().endFrame(gc); return impl().endFrame(gc);
} }
const XrSwapchainSubImage& const XrSwapchainSubImage&

View file

@ -10,12 +10,13 @@ namespace MWVR
{ {
class OpenXRSwapchainImpl; class OpenXRSwapchainImpl;
extern int swapCount;
class OpenXRSwapchain class OpenXRSwapchain
{ {
public: public:
struct Config struct Config
{ {
std::vector<int64_t> requestedFormats{};
int width = -1; int width = -1;
int height = -1; int height = -1;
int samples = -1; int samples = -1;
@ -29,7 +30,7 @@ namespace MWVR
//! Prepare for render (set FBO) //! Prepare for render (set FBO)
void beginFrame(osg::GraphicsContext* gc); void beginFrame(osg::GraphicsContext* gc);
//! Finalize render //! Finalize render
void endFrame(osg::GraphicsContext* gc); int endFrame(osg::GraphicsContext* gc);
//! Get the view surface //! Get the view surface
const XrSwapchainSubImage& subImage(void) const; const XrSwapchainSubImage& subImage(void) const;
//! Width of the view surface //! Width of the view surface

View file

@ -17,17 +17,15 @@ namespace MWVR {
OpenXRView::OpenXRView( OpenXRView::OpenXRView(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRManager> XR,
std::string name) std::string name,
OpenXRSwapchain::Config config,
osg::ref_ptr<osg::State> state)
: mXR(XR) : mXR(XR)
, mSwapchain(nullptr) , mSwapchainConfig{ config }
, mSwapchainConfig{} , mSwapchain(new OpenXRSwapchain(mXR, state, mSwapchainConfig))
, mName(name) , mName(name)
, mTimer(mName.c_str())
{ {
mSwapchainConfig.requestedFormats = {
GL_RGBA8,
GL_RGBA8_SNORM,
};
} }
OpenXRView::~OpenXRView() OpenXRView::~OpenXRView()
@ -49,78 +47,35 @@ namespace MWVR {
camera->setInitialDrawCallback(new OpenXRView::InitialDrawCallback()); 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(); 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) void OpenXRView::prerenderCallback(osg::RenderInfo& renderInfo)
{ {
if(mName == "LeftEye")
mXR->waitFrame();
Log(Debug::Verbose) << mName << ": prerenderCallback"; Log(Debug::Verbose) << mName << ": prerenderCallback";
if (mSwapchain) if (mSwapchain)
{ {
mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext()); mSwapchain->beginFrame(renderInfo.getState()->getGraphicsContext());
} }
mTimer.checkpoint("Prerender");
} }
void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo) void OpenXRView::postrenderCallback(osg::RenderInfo& renderInfo)
{ {
// osg will sometimes call this without a corresponding prerender.
Log(Debug::Verbose) << mName << ": postrenderCallback"; 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()); // 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 (...)
{ {
swapchain().endFrame(gc);
} }
return !!mSwapchain; void OpenXRView::setPredictedPose(const Pose& pose)
}
void OpenXRView::PredrawCallback::operator()(osg::RenderInfo& info) const
{ {
mView->prerenderCallback(info); mPredictedPose = pose;
} //Log(Debug::Verbose) << mName << " predicted pose updated to " << pose;
};
void OpenXRView::PostdrawCallback::operator()(osg::RenderInfo& info) const
{
mView->postrenderCallback(info);
}
} }

View file

@ -13,36 +13,6 @@ namespace MWVR
class OpenXRView : public osg::Referenced class OpenXRView : public osg::Referenced
{ {
public: 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 class InitialDrawCallback : public osg::Camera::DrawCallback
{ {
@ -51,11 +21,8 @@ namespace MWVR
}; };
protected: 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(); virtual ~OpenXRView();
void setWidth(int width);
void setHeight(int height);
void setSamples(int samples);
public: public:
//! Prepare for render (set FBO) //! Prepare for render (set FBO)
@ -66,16 +33,21 @@ namespace MWVR
osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc); osg::Camera* createCamera(int order, const osg::Vec4& clearColor, osg::GraphicsContext* gc);
//! Get the view surface //! Get the view surface
OpenXRSwapchain& swapchain(void) { return *mSwapchain; } 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; osg::ref_ptr<OpenXRManager> mXR;
std::unique_ptr<OpenXRSwapchain> mSwapchain;
OpenXRSwapchain::Config mSwapchainConfig; OpenXRSwapchain::Config mSwapchainConfig;
std::unique_ptr<OpenXRSwapchain> mSwapchain;
std::string mName{}; std::string mName{};
bool mRendering{ false }; bool mRendering{ false };
osg::ref_ptr<OpenXRView::PredrawCallback> mPredraw{ nullptr }; Timer mTimer;
Pose mPredictedPose{ {}, {0,0,0,1}, {} };
}; };
} }

View file

@ -16,8 +16,13 @@ namespace MWVR
, mMetersPerUnit(metersPerUnit) , mMetersPerUnit(metersPerUnit)
, mConfigured(false) , mConfigured(false)
, mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW}) , mCompositionLayerProjectionViews(2, {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW})
, mXRSession(nullptr)
, mPreDraw(new PredrawCallback(this))
, mPostDraw(new PostdrawCallback(this))
{ {
mViewer->setRealizeOperation(mRealizeOperation); mViewer->setRealizeOperation(mRealizeOperation);
mCompositionLayerProjectionViews[0].pose.orientation.w = 1;
mCompositionLayerProjectionViews[1].pose.orientation.w = 1;
} }
OpenXRViewer::~OpenXRViewer(void) OpenXRViewer::~OpenXRViewer(void)
@ -36,24 +41,18 @@ namespace MWVR
const XrCompositionLayerBaseHeader* const XrCompositionLayerBaseHeader*
OpenXRViewer::layer() 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()); return reinterpret_cast<XrCompositionLayerBaseHeader*>(mLayer.get());
} }
void OpenXRViewer::traversals() void OpenXRViewer::traversals()
{ {
Log(Debug::Verbose) << "Pre-Update"; //Log(Debug::Verbose) << "Pre-Update";
mXR->handleEvents(); mXR->handleEvents();
OpenXRFrameIndexer::instance().advanceUpdateIndex();
mViewer->updateTraversal(); mViewer->updateTraversal();
Log(Debug::Verbose) << "Post-Update"; //Log(Debug::Verbose) << "Post-Update";
Log(Debug::Verbose) << "Pre-Rendering"; //Log(Debug::Verbose) << "Pre-Rendering";
mViewer->renderingTraversals(); mViewer->renderingTraversals();
Log(Debug::Verbose) << "Post-Rendering"; //Log(Debug::Verbose) << "Post-Rendering";
} }
void OpenXRViewer::realize(osg::GraphicsContext* context) void OpenXRViewer::realize(osg::GraphicsContext* context)
@ -70,91 +69,102 @@ namespace MWVR
return; 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. // 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.) // (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()) if (!mXR->realized())
mXR->realize(context); mXR->realize(context);
mViews[OpenXRWorldView::LEFT_VIEW] = new OpenXRWorldView(mXR, "LeftEye", context->getState(), mMetersPerUnit, OpenXRWorldView::LEFT_VIEW); OpenXRSwapchain::Config leftConfig;
mViews[OpenXRWorldView::RIGHT_VIEW] = new OpenXRWorldView(mXR, "RightEye", context->getState(), mMetersPerUnit, OpenXRWorldView::RIGHT_VIEW); 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.) // 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); leftCamera->setName("LeftEye");
//mRightCamera->setGraphicsContext(mRightGW); rightCamera->setName("RightEye");
mLeftCamera->setCullMask(~MWRender::Mask_GUI); mViewer->addSlave(leftCamera, leftView->projectionMatrix(), leftView->viewMatrix(), true);
mRightCamera->setCullMask(~MWRender::Mask_GUI); 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->setLightingMode(osg::View::SKY_LIGHT);
mViewer->setReleaseContextAtEndOfFrameHint(false); mViewer->setReleaseContextAtEndOfFrameHint(false);
// Rendering main camera is a waste of time with VR enabled
//camera->setGraphicsContext(nullptr);
mXRInput.reset(new OpenXRInputManager(mXR)); 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; OpenXRSwapchain::Config config;
config.requestedFormats = { config.width = mainCamera->getViewport()->width();
GL_RGBA8, config.height = mainCamera->getViewport()->height();
GL_RGBA8_SNORM,
};
config.width = mMainCamera->getViewport()->width();
config.height = mMainCamera->getViewport()->height();
config.samples = 1; config.samples = 1;
// Mirror texture doesn't have to be an OpenXR swapchain. // Mirror texture doesn't have to be an OpenXR swapchain.
// It's just convenient. // It's just convenient.
mMirrorTextureSwapchain.reset(new OpenXRSwapchain(mXR, context->getState(), config)); 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))); auto menuView = new OpenXRMenu(mXR, config, context->getState(), "MainMenu", osg::Vec2(1.f, 1.f));
mMenuCamera = mXRMenu->createCamera(2, clearColor, context); mViews["MenuView"] = menuView;
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)); auto menuCamera = mCameras["MenuView"] = menuView->createCamera(2, clearColor, context);
mMainCamera->setGraphicsContext(nullptr); 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; mConfigured = true;
} }
void OpenXRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc) const void OpenXRViewer::blitEyesToMirrorTexture(osg::GraphicsContext* gc)
{ {
mMirrorTextureSwapchain->beginFrame(gc); mMirrorTextureSwapchain->beginFrame(gc);
mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height()); mViews["LeftEye"]->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["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()); //mXRMenu->swapchain().current()->blit(gc, 0, 0, mMirrorTextureSwapchain->width() / 2, mMirrorTextureSwapchain->height());
} }
@ -163,34 +173,46 @@ namespace MWVR
OpenXRViewer::SwapBuffersCallback::swapBuffersImplementation( OpenXRViewer::SwapBuffersCallback::swapBuffersImplementation(
osg::GraphicsContext* gc) osg::GraphicsContext* gc)
{ {
mViewer->swapBuffers(gc); mViewer->mXRSession->swapBuffers(gc);
} }
void OpenXRViewer::swapBuffers(osg::GraphicsContext* gc) void OpenXRViewer::swapBuffers(osg::GraphicsContext* gc)
{ {
Timer timer("swapBuffers");
Log(Debug::Verbose) << "SwapBuffers()";
std::unique_lock<std::mutex> lock(mMutex);
if (!mConfigured) if (!mConfigured)
return; return;
auto* state = gc->getState(); auto* state = gc->getState();
auto* gl = osg::GLExtensions::Get(state->getContextID(), false); 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(); auto eyePoses = mXRSession->predictedPoses().eye;
//blitEyesToMirrorTexture(gc); auto leftEyePose = toXR(mViews["LeftEye"]->predictedPose());
mViews[OpenXRWorldView::LEFT_VIEW]->swapchain().endFrame(gc); auto rightEyePose = toXR(mViews["RightEye"]->predictedPose());
mViews[OpenXRWorldView::RIGHT_VIEW]->swapchain().endFrame(gc); mCompositionLayerProjectionViews[0].pose = leftEyePose;
mXRMenu->swapchain().endFrame(gc); mCompositionLayerProjectionViews[1].pose = rightEyePose;
mXR->endFrame(); timer.checkpoint("Poses");
//gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
//mMirrorTextureSwapchain->renderBuffer()->blit(gc, 0, 0, mMirrorTextureSwapchain->width(), mMirrorTextureSwapchain->height()); // 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");
//mMirrorTextureSwapchain->endFrame(gc);
gc->swapBuffersImplementation(); 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 void
@ -206,4 +228,37 @@ namespace MWVR
{ {
return mViewer->realized(); 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");
}
}
} }

View file

@ -3,11 +3,13 @@
#include <memory> #include <memory>
#include <array> #include <array>
#include <map>
#include <osg/Group> #include <osg/Group>
#include <osg/Camera> #include <osg/Camera>
#include <osgViewer/Viewer> #include <osgViewer/Viewer>
#include "openxrmanager.hpp" #include "openxrmanager.hpp"
#include "openxrsession.hpp"
#include "openxrlayer.hpp" #include "openxrlayer.hpp"
#include "openxrworldview.hpp" #include "openxrworldview.hpp"
#include "openxrmenu.hpp" #include "openxrmenu.hpp"
@ -41,6 +43,34 @@ namespace MWVR
OpenXRViewer* mViewer; 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: public:
OpenXRViewer( OpenXRViewer(
osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRManager> XR,
@ -54,31 +84,28 @@ namespace MWVR
const XrCompositionLayerBaseHeader* layer() override; const XrCompositionLayerBaseHeader* layer() override;
void traversals(); void traversals();
void blitEyesToMirrorTexture(osg::GraphicsContext* gc) const; void preDrawCallback(osg::RenderInfo& info);
void swapBuffers(osg::GraphicsContext* gc) ; void postDrawCallback(osg::RenderInfo& info);
void blitEyesToMirrorTexture(osg::GraphicsContext* gc);
void swapBuffers(osg::GraphicsContext* gc) override;
void realize(osg::GraphicsContext* gc); void realize(osg::GraphicsContext* gc);
bool realized() { return mConfigured; } bool realized() { return mConfigured; }
protected: public:
std::unique_ptr<XrCompositionLayerProjection> mLayer = nullptr; std::unique_ptr<XrCompositionLayerProjection> mLayer = nullptr;
std::vector<XrCompositionLayerProjectionView> mCompositionLayerProjectionViews; std::vector<XrCompositionLayerProjectionView> mCompositionLayerProjectionViews;
osg::observer_ptr<OpenXRManager> mXR = nullptr; osg::observer_ptr<OpenXRManager> mXR = nullptr;
osg::ref_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr; osg::ref_ptr<OpenXRManager::RealizeOperation> mRealizeOperation = nullptr;
osg::observer_ptr<osgViewer::Viewer> mViewer = nullptr; osg::observer_ptr<osgViewer::Viewer> mViewer = nullptr;
std::unique_ptr<MWVR::OpenXRInputManager> mXRInput = nullptr; std::unique_ptr<OpenXRInputManager> mXRInput = nullptr;
std::unique_ptr<MWVR::OpenXRMenu> mXRMenu = nullptr; std::unique_ptr<OpenXRSession> mXRSession = nullptr;
std::array<osg::ref_ptr<OpenXRWorldView>, 2> mViews{}; 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; PredrawCallback* mPreDraw{ nullptr };
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mLeftGW; PostdrawCallback* mPostDraw{ nullptr };
//osg::ref_ptr<SDLUtil::GraphicsWindowSDL2> mRightGW;
osg::Camera* mMainCamera = nullptr;
osg::Camera* mMenuCamera = nullptr;
osg::Camera* mLeftCamera = nullptr;
osg::Camera* mRightCamera = nullptr;
std::unique_ptr<OpenXRSwapchain> mMirrorTextureSwapchain = nullptr; std::unique_ptr<OpenXRSwapchain> mMirrorTextureSwapchain = nullptr;

View file

@ -78,57 +78,45 @@ namespace MWVR
osg::Matrix OpenXRWorldView::projectionMatrix() 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 near = Settings::Manager::getFloat("near clip", "Camera");
float far = Settings::Manager::getFloat("viewing distance", "Camera") * mMetersPerUnit; float far = Settings::Manager::getFloat("viewing distance", "Camera") * mMetersPerUnit;
//return perspectiveFovMatrix() //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 OpenXRWorldView::viewMatrix()
{ {
osg::Matrix viewMatrix; auto pose = predictedPose();
auto hmdViews = mXR->impl().getPredictedViews(OpenXRFrameIndexer::instance().updateIndex(), TrackedSpace::VIEW); osg::Vec3 position = pose.position;
auto pose = hmdViews[mView].pose;
osg::Vec3 position = osg::fromXR(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 #if 1
position = -osg::fromXR(stagePose.position); // Comfort shortcut.
position.y() += 0.9144 * 2.; // TODO: Head pose should affect more than just the view !
position.y() -= 0.9144 * 2.;
#endif #endif
// invert orientation (conjugate of Quaternion) and position to apply to the view matrix as offset // invert orientation (co jugate of Quaternion) and position to apply to the view matrix as offset
viewMatrix.setTrans(position * mMetersPerUnit); osg::Matrix viewMatrix;
viewMatrix.postMultRotate(osg::fromXR(stagePose.orientation).conj()); viewMatrix.setTrans(-position * mMetersPerUnit);
viewMatrix.postMultRotate(pose.orientation.conj());
// Scale to world units
//viewMatrix.postMultScale(osg::Vec3d(1.f / mMetersPerUnit, 1.f / mMetersPerUnit, 1.f / mMetersPerUnit));
return viewMatrix; return viewMatrix;
} }
OpenXRWorldView::OpenXRWorldView( OpenXRWorldView::OpenXRWorldView(
osg::ref_ptr<OpenXRManager> XR, std::string name, osg::ref_ptr<osg::State> state, float metersPerUnit, SubView view) osg::ref_ptr<OpenXRManager> XR,
: OpenXRView(XR, name) std::string name,
osg::ref_ptr<osg::State> state,
OpenXRSwapchain::Config config,
float metersPerUnit )
: OpenXRView(XR, name, config, state)
, mMetersPerUnit(metersPerUnit) , 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() OpenXRWorldView::~OpenXRWorldView()
@ -154,13 +142,8 @@ namespace MWVR
auto* camera = renderInfo.getCurrentCamera(); auto* camera = renderInfo.getCurrentCamera();
auto name = camera->getName(); 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 void
@ -168,21 +151,38 @@ namespace MWVR
osg::View& view, osg::View& view,
osg::View::Slave& slave) osg::View::Slave& slave)
{ {
mXR->handleEvents(); mView->mTimer.checkpoint("UpdateSlave");
if (!mXR->sessionRunning())
return;
auto* camera = slave._camera.get(); auto* camera = slave._camera.get();
auto name = camera->getName(); auto name = camera->getName();
Log(Debug::Verbose) << "Updating slave " << name; Log(Debug::Verbose) << name << ": slave update";
//auto viewMatrix = view.getCamera()->getViewMatrix() * mView->viewMatrix(); auto& poses = mSession->predictedPoses();
//auto projMatrix = mView->projectionMatrix();
//camera->setViewMatrix(viewMatrix); if (name == "LeftEye")
//camera->setProjectionMatrix(projMatrix); {
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); slave.updateSlaveImplementation(view);
} }

View file

@ -2,36 +2,31 @@
#define OPENXRWORLDVIEW_HPP #define OPENXRWORLDVIEW_HPP
#include "openxrview.hpp" #include "openxrview.hpp"
#include "openxrsession.hpp"
namespace MWVR namespace MWVR
{ {
class OpenXRWorldView : public OpenXRView class OpenXRWorldView : public OpenXRView
{ {
public: 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 class UpdateSlaveCallback : public osg::View::Slave::UpdateSlaveCallback
{ {
public: public:
UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, osg::ref_ptr<OpenXRWorldView> view, osg::GraphicsContext* gc) UpdateSlaveCallback(osg::ref_ptr<OpenXRManager> XR, OpenXRSession* session, osg::ref_ptr<OpenXRWorldView> view, osg::GraphicsContext* gc)
: mXR(XR), mView(view), mGC(gc) : mXR(XR), mSession(session), mView(view), mGC(gc)
{} {}
void updateSlave(osg::View& view, osg::View::Slave& slave) override; void updateSlave(osg::View& view, osg::View::Slave& slave) override;
private: private:
osg::ref_ptr<OpenXRManager> mXR; osg::ref_ptr<OpenXRManager> mXR;
OpenXRSession* mSession;
osg::ref_ptr<OpenXRWorldView> mView; osg::ref_ptr<OpenXRWorldView> mView;
osg::ref_ptr<osg::GraphicsContext> mGC; osg::ref_ptr<osg::GraphicsContext> mGC;
}; };
public: 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(); ~OpenXRWorldView();
//! Prepare for render (update matrices) //! Prepare for render (update matrices)
@ -40,11 +35,8 @@ namespace MWVR
osg::Matrix projectionMatrix(); osg::Matrix projectionMatrix();
//! View offset for this view //! View offset for this view
osg::Matrix viewMatrix(); osg::Matrix viewMatrix();
//! Which view this is
SubView view() { return mView; }
float mMetersPerUnit = 1.f; float mMetersPerUnit = 1.f;
SubView mView;
}; };
} }