// // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_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(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 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()->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(); }