mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-17 03:17:57 +02:00
303 lines
11 KiB
C++
303 lines
11 KiB
C++
//
|
|
// Created by Bradley Austin Davis on 2015/05/12
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
#include "OpenVrDisplayPlugin.h"
|
|
|
|
#include <memory>
|
|
|
|
#include <QMainWindow>
|
|
#include <QLoggingCategory>
|
|
#include <QGLWidget>
|
|
#include <QEvent>
|
|
#include <QResizeEvent>
|
|
|
|
#include <GLMHelpers.h>
|
|
#include <gl/GlWindow.h>
|
|
|
|
#include <gpu/Frame.h>
|
|
#include <gpu/gl/GLBackend.h>
|
|
|
|
#include <controllers/Pose.h>
|
|
#include <PerfStat.h>
|
|
#include <ui-plugins/PluginContainer.h>
|
|
#include <ViewFrustum.h>
|
|
#include <display-plugins/CompositorHelper.h>
|
|
#include <shared/NsightHelpers.h>
|
|
#include "OpenVrHelpers.h"
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
|
|
|
|
const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)");
|
|
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here
|
|
|
|
static vr::IVRCompositor* _compositor { nullptr };
|
|
|
|
PoseData _nextRenderPoseData;
|
|
PoseData _nextSimPoseData;
|
|
|
|
static mat4 _sensorResetMat;
|
|
static std::array<vr::Hmd_Eye, 2> VR_EYES { { vr::Eye_Left, vr::Eye_Right } };
|
|
bool _openVrDisplayActive { false };
|
|
|
|
|
|
bool OpenVrDisplayPlugin::isSupported() const {
|
|
return openVrSupported();
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::init() {
|
|
Plugin::init();
|
|
|
|
_lastGoodHMDPose.m[0][0] = 1.0f;
|
|
_lastGoodHMDPose.m[0][1] = 0.0f;
|
|
_lastGoodHMDPose.m[0][2] = 0.0f;
|
|
_lastGoodHMDPose.m[0][3] = 0.0f;
|
|
_lastGoodHMDPose.m[1][0] = 0.0f;
|
|
_lastGoodHMDPose.m[1][1] = 1.0f;
|
|
_lastGoodHMDPose.m[1][2] = 0.0f;
|
|
_lastGoodHMDPose.m[1][3] = 1.8f;
|
|
_lastGoodHMDPose.m[2][0] = 0.0f;
|
|
_lastGoodHMDPose.m[2][1] = 0.0f;
|
|
_lastGoodHMDPose.m[2][2] = 1.0f;
|
|
_lastGoodHMDPose.m[2][3] = 0.0f;
|
|
|
|
emit deviceConnected(getName());
|
|
}
|
|
|
|
bool OpenVrDisplayPlugin::internalActivate() {
|
|
_openVrDisplayActive = true;
|
|
_container->setIsOptionChecked(StandingHMDSensorMode, true);
|
|
|
|
if (!_system) {
|
|
_system = acquireOpenVrSystem();
|
|
}
|
|
if (!_system) {
|
|
qWarning() << "Failed to initialize OpenVR";
|
|
return false;
|
|
}
|
|
|
|
_system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y);
|
|
// Recommended render target size is per-eye, so double the X size for
|
|
// left + right eyes
|
|
_renderTargetSize.x *= 2;
|
|
|
|
withNonPresentThreadLock([&] {
|
|
openvr_for_each_eye([&](vr::Hmd_Eye eye) {
|
|
_eyeOffsets[eye] = toGlm(_system->GetEyeToHeadTransform(eye));
|
|
_eyeProjections[eye] = toGlm(_system->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL));
|
|
});
|
|
// FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes
|
|
_cullingProjection = _eyeProjections[0];
|
|
});
|
|
|
|
_compositor = vr::VRCompositor();
|
|
Q_ASSERT(_compositor);
|
|
|
|
// enable async time warp
|
|
// _compositor->ForceInterleavedReprojectionOn(true);
|
|
|
|
// set up default sensor space such that the UI overlay will align with the front of the room.
|
|
auto chaperone = vr::VRChaperone();
|
|
if (chaperone) {
|
|
float const UI_RADIUS = 1.0f;
|
|
float const UI_HEIGHT = 1.6f;
|
|
float const UI_Z_OFFSET = 0.5;
|
|
|
|
float xSize, zSize;
|
|
chaperone->GetPlayAreaSize(&xSize, &zSize);
|
|
glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET);
|
|
_sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos));
|
|
} else {
|
|
#if DEV_BUILD
|
|
qDebug() << "OpenVR: error could not get chaperone pointer";
|
|
#endif
|
|
}
|
|
|
|
return Parent::internalActivate();
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::internalDeactivate() {
|
|
Parent::internalDeactivate();
|
|
_openVrDisplayActive = false;
|
|
_container->setIsOptionChecked(StandingHMDSensorMode, false);
|
|
if (_system) {
|
|
// Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and
|
|
// we don't want ViveControllerManager to consider old values to be valid.
|
|
releaseOpenVrSystem();
|
|
_system = nullptr;
|
|
}
|
|
_compositor = nullptr;
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::customizeContext() {
|
|
// Display plugins in DLLs must initialize glew locally
|
|
static std::once_flag once;
|
|
std::call_once(once, [] {
|
|
glewExperimental = true;
|
|
GLenum err = glewInit();
|
|
glGetError(); // clear the potential error from glewExperimental
|
|
});
|
|
|
|
Parent::customizeContext();
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::resetSensors() {
|
|
glm::mat4 m;
|
|
withNonPresentThreadLock([&] {
|
|
m = toGlm(_nextSimPoseData.vrPoses[0].mDeviceToAbsoluteTracking);
|
|
});
|
|
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(m));
|
|
}
|
|
|
|
static bool isBadPose(vr::HmdMatrix34_t* mat) {
|
|
if (mat->m[1][3] < -0.2f) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|
handleOpenVrEvents();
|
|
if (openVrQuitRequested()) {
|
|
QMetaObject::invokeMethod(qApp, "quit");
|
|
return false;
|
|
}
|
|
_currentRenderFrameInfo = FrameInfo();
|
|
|
|
withNonPresentThreadLock([&] {
|
|
_currentRenderFrameInfo.renderPose = _nextSimPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd];
|
|
});
|
|
|
|
// HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames
|
|
// To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor.
|
|
if (isBadPose(&_nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) {
|
|
qDebug() << "WARNING: ignoring bad hmd pose from openvr";
|
|
|
|
// use the last known good HMD pose
|
|
_nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose;
|
|
} else {
|
|
_lastGoodHMDPose = _nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;
|
|
}
|
|
|
|
vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid };
|
|
{
|
|
vr::TrackedDeviceIndex_t controllerIndices[2] ;
|
|
auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2);
|
|
// Find the left and right hand controllers, if they exist
|
|
for (uint32_t i = 0; i < std::min<uint32_t>(trackedCount, 2); ++i) {
|
|
if (_nextSimPoseData.vrPoses[i].bPoseIsValid) {
|
|
auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]);
|
|
if (vr::TrackedControllerRole_LeftHand == role) {
|
|
handIndices[0] = controllerIndices[i];
|
|
} else if (vr::TrackedControllerRole_RightHand == role) {
|
|
handIndices[1] = controllerIndices[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_currentRenderFrameInfo.renderPose = _nextSimPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd];
|
|
|
|
bool keyboardVisible = isOpenVrKeyboardShown();
|
|
|
|
std::array<mat4, 2> handPoses;
|
|
if (!keyboardVisible) {
|
|
for (int i = 0; i < 2; ++i) {
|
|
if (handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) {
|
|
continue;
|
|
}
|
|
auto deviceIndex = handIndices[i];
|
|
const mat4& mat = _nextSimPoseData.poses[deviceIndex];
|
|
const vec3& linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex];
|
|
const vec3& angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex];
|
|
auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity);
|
|
static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y);
|
|
handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION);
|
|
}
|
|
}
|
|
|
|
withNonPresentThreadLock([&] {
|
|
_uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform();
|
|
// Make controller poses available to the presentation thread
|
|
_handPoses = handPoses;
|
|
_frameInfos[frameIndex] = _currentRenderFrameInfo;
|
|
});
|
|
return Parent::beginFrameRender(frameIndex);
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::hmdPresent() {
|
|
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
|
|
|
|
// Flip y-axis since GL UV coords are backwards.
|
|
static vr::VRTextureBounds_t leftBounds { 0, 0, 0.5f, 1 };
|
|
static vr::VRTextureBounds_t rightBounds { 0.5f, 0, 1, 1 };
|
|
auto glTexId = getGLBackend()->getTextureID(_compositeTexture, false);
|
|
vr::Texture_t vrTexture{ (void*)glTexId, vr::API_OpenGL, vr::ColorSpace_Auto };
|
|
|
|
_compositor->Submit(vr::Eye_Left, &vrTexture, &leftBounds);
|
|
_compositor->Submit(vr::Eye_Right, &vrTexture, &rightBounds);
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::postPreview() {
|
|
// Clear
|
|
{
|
|
// We want to make sure the glFinish waits for the entire present to complete, not just the submission
|
|
// of the command. So, we do a clear here right here so the glFinish will wait fully for the swap.
|
|
glClearColor(0, 0, 0, 1);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glFlush();
|
|
}
|
|
|
|
// Flush and wait for swap.
|
|
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
|
|
PoseData nextRender;
|
|
nextRender.frameIndex = presentCount();
|
|
vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
|
|
|
|
glm::mat4 resetMat;
|
|
withPresentThreadLock([&] {
|
|
resetMat = _sensorResetMat;
|
|
});
|
|
nextRender.update(resetMat);
|
|
withPresentThreadLock([&] {
|
|
_nextSimPoseData = nextRender;
|
|
});
|
|
_nextRenderPoseData = nextRender;
|
|
_hmdActivityLevel = vr::k_EDeviceActivityLevel_UserInteraction; // _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);
|
|
}
|
|
|
|
bool OpenVrDisplayPlugin::isHmdMounted() const {
|
|
return _hmdActivityLevel == vr::k_EDeviceActivityLevel_UserInteraction;
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::updatePresentPose() {
|
|
_currentPresentFrameInfo.presentPose = _nextRenderPoseData.poses[vr::k_unTrackedDeviceIndex_Hmd];
|
|
//_currentPresentFrameInfo.presentPose = _currentPresentFrameInfo.renderPose;
|
|
}
|
|
|
|
bool OpenVrDisplayPlugin::suppressKeyboard() {
|
|
if (isOpenVrKeyboardShown()) {
|
|
return false;
|
|
}
|
|
if (!_keyboardSupressionCount.fetch_add(1)) {
|
|
disableOpenVrKeyboard();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OpenVrDisplayPlugin::unsuppressKeyboard() {
|
|
if (_keyboardSupressionCount == 0) {
|
|
qWarning() << "Attempted to unsuppress a keyboard that was not suppressed";
|
|
return;
|
|
}
|
|
if (1 == _keyboardSupressionCount.fetch_sub(1)) {
|
|
enableOpenVrKeyboard(_container);
|
|
}
|
|
}
|
|
|
|
bool OpenVrDisplayPlugin::isKeyboardVisible() {
|
|
return isOpenVrKeyboardShown();
|
|
}
|