1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-15 16:39: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/openxrmenu.hpp
mwvr/openxrmenu.cpp
mwvr/openxrsession.hpp
mwvr/openxrsession.cpp
mwvr/openxrswapchain.hpp
mwvr/openxrswapchain.cpp
mwvr/openxrtexture.hpp

View file

@ -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

View file

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

View file

@ -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;

View file

@ -16,12 +16,94 @@
#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)
, mMutex()
@ -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,7 +178,7 @@ 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();
@ -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 <<(

View file

@ -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);

View file

@ -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

View file

@ -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{};
};
}

View file

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

View file

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

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();
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&

View file

@ -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

View file

@ -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
void OpenXRView::setPredictedPose(const Pose& pose)
{
mView->prerenderCallback(info);
}
void OpenXRView::PostdrawCallback::operator()(osg::RenderInfo& info) const
{
mView->postrenderCallback(info);
}
mPredictedPose = pose;
//Log(Debug::Verbose) << mName << " predicted pose updated to " << pose;
};
}

View file

@ -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}, {} };
};
}

View file

@ -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);
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->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
@ -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");
}
}
}

View file

@ -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;

View file

@ -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);
}

View file

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