//// Created by Bradley Austin Davis on 2015/05/12// Copyright 2015 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.//// 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" // Odd ordering of header is required to avoid 'macro redinition warnings'#include <AudioClient.h> #include <QtCore/QThread>#include <QtCore/QLoggingCategory>#include <QtCore/QFileInfo>#include <QtCore/QDateTime> #include <GLMHelpers.h> #include <gl/Context.h>#include <gl/GLShaders.h> #include <gpu/Frame.h>#include <gpu/gl/GLBackend.h> #include <ViewFrustum.h>#include <PathUtils.h>#include <shared/NsightHelpers.h>#include <controllers/Pose.h>#include <display-plugins/CompositorHelper.h>#include <ui-plugins/PluginContainer.h>#include <gl/OffscreenGLCanvas.h>#include <ThreadHelpers.h> #include "OpenVrHelpers.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) const char* OpenVrThreadedSubmit{ "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here PoseData _nextRenderPoseData;PoseData _nextSimPoseData; #define MIN_CORES_FOR_NORMAL_RENDER 5bool forceInterleavedReprojection = (QThread::idealThreadCount() < MIN_CORES_FOR_NORMAL_RENDER); static std::array<vr::Hmd_Eye, 2> VR_EYES{ { vr::Eye_Left, vr::Eye_Right } };bool _openVrDisplayActive{ false };// Flip y-axis since GL UV coords are backwards.static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_LEFT{ 0, 0, 0.5f, 1 };static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_RIGHT{ 0.5f, 0, 1, 1 }; #define REPROJECTION_BINDING 1 static const char* HMD_REPROJECTION_VERT = R"SHADER(#version 450 core out vec3 vPosition;out vec2 vTexCoord; void main(void) { const float depth = 0.0; const vec4 UNIT_QUAD[4] = vec4[4]( vec4(-1.0, -1.0, depth, 1.0), vec4(1.0, -1.0, depth, 1.0), vec4(-1.0, 1.0, depth, 1.0), vec4(1.0, 1.0, depth, 1.0) ); vec4 pos = UNIT_QUAD[gl_VertexID]; gl_Position = pos; vPosition = pos.xyz; vTexCoord = (pos.xy + 1.0) * 0.5;})SHADER"; static const char* HMD_REPROJECTION_FRAG = R"SHADER(#version 450 core uniform sampler2D sampler;layout(binding = 1, std140) uniform Reprojection{ mat4 projections[2]; mat4 inverseProjections[2]; mat4 reprojection;}; in vec3 vPosition;in vec2 vTexCoord; out vec4 FragColor; void main() { vec2 uv = vTexCoord; mat4 eyeInverseProjection; mat4 eyeProjection; float xoffset = 1.0; vec2 uvmin = vec2(0.0); vec2 uvmax = vec2(1.0); // determine the correct projection and inverse projection to use. if (vTexCoord.x < 0.5) { uvmax.x = 0.5; eyeInverseProjection = inverseProjections[0]; eyeProjection = projections[0]; } else { xoffset = -1.0; uvmin.x = 0.5; uvmax.x = 1.0; eyeInverseProjection = inverseProjections[1]; eyeProjection = projections[1]; } // Account for stereo in calculating the per-eye NDC coordinates vec4 ndcSpace = vec4(vPosition, 1.0); ndcSpace.x *= 2.0; ndcSpace.x += xoffset; // Convert from NDC to eyespace vec4 eyeSpace = eyeInverseProjection * ndcSpace; eyeSpace /= eyeSpace.w; // Convert to a noramlized ray vec3 ray = eyeSpace.xyz; ray = normalize(ray); // Adjust the ray by the rotation ray = mat3(reprojection) * ray; // Project back on to the texture plane ray *= eyeSpace.z / ray.z; // Update the eyespace vector eyeSpace.xyz = ray; // Reproject back into NDC ndcSpace = eyeProjection * eyeSpace; ndcSpace /= ndcSpace.w; ndcSpace.x -= xoffset; ndcSpace.x /= 2.0; // Calculate the new UV coordinates uv = (ndcSpace.xy / 2.0) + 0.5; if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { FragColor = vec4(0.0, 0.0, 0.0, 1.0); } else { FragColor = texture(sampler, uv); }})SHADER"; struct Reprojection { mat4 projections[2]; mat4 inverseProjections[2]; mat4 reprojection;}; class OpenVrSubmitThread : public QThread, public Dependency {public: using Mutex = std::mutex; using Condition = std::condition_variable; using Lock = std::unique_lock<Mutex>; friend class OpenVrDisplayPlugin; std::shared_ptr<gl::OffscreenContext> _canvas; OpenVrSubmitThread(OpenVrDisplayPlugin& plugin) : _plugin(plugin) { setObjectName("OpenVR Submit Thread"); } void updateSource() { _plugin.withNonPresentThreadLock([&] { while (!_queue.empty()) { auto& front = _queue.front(); auto result = glClientWaitSync((GLsync)front.fence, 0, 0); if (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { break; } else if (GL_CONDITION_SATISFIED == result || GL_ALREADY_SIGNALED == result) { glDeleteSync((GLsync)front.fence); } else { assert(false); } front.fence = 0; _current = front; _queue.pop(); } }); } GLuint _program{ 0 }; void updateProgram() { if (!_program) { std::string vsSource = HMD_REPROJECTION_VERT; std::string fsSource = HMD_REPROJECTION_FRAG; GLuint vertexShader{ 0 }, fragmentShader{ 0 }; std::string error; ::gl::compileShader(GL_VERTEX_SHADER, vsSource, vertexShader, error); ::gl::compileShader(GL_FRAGMENT_SHADER, fsSource, fragmentShader, error); _program = ::gl::buildProgram({ { vertexShader, fragmentShader } }); ::gl::linkProgram(_program, error); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); qDebug() << "Rebuild proigram"; } } #define COLOR_BUFFER_COUNT 4 void run() override { GLuint _framebuffer{ 0 }; std::array<GLuint, COLOR_BUFFER_COUNT> _colors; size_t currentColorBuffer{ 0 }; size_t globalColorBufferCount{ 0 }; GLuint _uniformBuffer{ 0 }; GLuint _vao{ 0 }; GLuint _depth{ 0 }; Reprojection _reprojection; QThread::currentThread()->setPriority(QThread::Priority::TimeCriticalPriority); _canvas->makeCurrent(); glCreateBuffers(1, &_uniformBuffer); glNamedBufferStorage(_uniformBuffer, sizeof(Reprojection), 0, GL_DYNAMIC_STORAGE_BIT); glCreateVertexArrays(1, &_vao); glBindVertexArray(_vao); glCreateFramebuffers(1, &_framebuffer); { glCreateRenderbuffers(1, &_depth); glNamedRenderbufferStorage(_depth, GL_DEPTH24_STENCIL8, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); glNamedFramebufferRenderbuffer(_framebuffer, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); glCreateTextures(GL_TEXTURE_2D, COLOR_BUFFER_COUNT, &_colors[0]); for (size_t i = 0; i < COLOR_BUFFER_COUNT; ++i) { glTextureStorage2D(_colors[i], 1, GL_RGBA8, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); } } glDisable(GL_DEPTH_TEST); glViewport(0, 0, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); _canvas->doneCurrent(); while (!_quit) { _canvas->makeCurrent(); updateSource(); if (!_current.texture) { _canvas->doneCurrent(); QThread::usleep(1); continue; } updateProgram(); { auto presentRotation = glm::mat3(_nextRender.poses[0]); auto renderRotation = glm::mat3(_current.pose); for (size_t i = 0; i < 2; ++i) { _reprojection.projections[i] = _plugin._eyeProjections[i]; _reprojection.inverseProjections[i] = _plugin._eyeInverseProjections[i]; } _reprojection.reprojection = glm::inverse(renderRotation) * presentRotation; glNamedBufferSubData(_uniformBuffer, 0, sizeof(Reprojection), &_reprojection); glNamedFramebufferTexture(_framebuffer, GL_COLOR_ATTACHMENT0, _colors[currentColorBuffer], 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _framebuffer); { glClearColor(1, 1, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glTextureParameteri(_current.textureID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTextureParameteri(_current.textureID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(_program); glBindBufferBase(GL_UNIFORM_BUFFER, REPROJECTION_BINDING, _uniformBuffer); glBindTexture(GL_TEXTURE_2D, _current.textureID); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); static const vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; static const vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; vr::Texture_t texture{ (void*)(uintptr_t)_colors[currentColorBuffer], vr::TextureType_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &texture, &leftBounds); vr::VRCompositor()->Submit(vr::Eye_Right, &texture, &rightBounds); _plugin._presentRate.increment(); PoseData nextRender, nextSim; nextRender.frameIndex = _plugin.presentCount(); vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); // Copy invalid poses in nextSim from nextRender for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { if (!nextSim.vrPoses[i].bPoseIsValid) { nextSim.vrPoses[i] = nextRender.vrPoses[i]; } } mat4 sensorResetMat; _plugin.withNonPresentThreadLock([&] { sensorResetMat = _plugin._sensorResetMat; }); nextRender.update(sensorResetMat); nextSim.update(sensorResetMat); _plugin.withNonPresentThreadLock([&] { _nextRender = nextRender; _nextSim = nextSim; ++_presentCount; _presented.notify_one(); }); ++globalColorBufferCount; currentColorBuffer = globalColorBufferCount % COLOR_BUFFER_COUNT; } _canvas->doneCurrent(); } _canvas->makeCurrent(); glDeleteBuffers(1, &_uniformBuffer); glDeleteFramebuffers(1, &_framebuffer); CHECK_GL_ERROR(); glDeleteTextures(4, &_colors[0]); glDeleteProgram(_program); glBindVertexArray(0); glDeleteVertexArrays(1, &_vao); _canvas->doneCurrent(); _canvas->moveToThread(_plugin.thread()); } void update(const CompositeInfo& newCompositeInfo) { _queue.push(newCompositeInfo); } void waitForPresent() { auto lastCount = _presentCount.load(); Lock lock(_plugin._presentMutex); _presented.wait(lock, [&]() -> bool { return _presentCount.load() > lastCount; }); _nextSimPoseData = _nextSim; _nextRenderPoseData = _nextRender; } CompositeInfo _current; CompositeInfo::Queue _queue; PoseData _nextRender, _nextSim; bool _quit{ false }; GLuint _currentTexture{ 0 }; std::atomic<uint32_t> _presentCount{ 0 }; Condition _presented; OpenVrDisplayPlugin& _plugin;}; bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported();} glm::mat4 OpenVrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { if (_system) { ViewFrustum baseFrustum; baseFrustum.setProjection(baseProjection); float baseNearClip = baseFrustum.getNearClip(); float baseFarClip = baseFrustum.getFarClip(); vr::EVREye openVrEye = (eye == Left) ? vr::Eye_Left : vr::Eye_Right; return toGlm(_system->GetProjectionMatrix(openVrEye, baseNearClip, baseFarClip)); } else { return baseProjection; }} glm::mat4 OpenVrDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { if (_system) { ViewFrustum baseFrustum; baseFrustum.setProjection(baseProjection); float baseNearClip = baseFrustum.getNearClip(); float baseFarClip = baseFrustum.getFarClip(); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes return toGlm(_system->GetProjectionMatrix((vr::EVREye)0, baseNearClip, baseFarClip)); } else { return baseProjection; }} float OpenVrDisplayPlugin::getTargetFrameRate() const { if (forceInterleavedReprojection && !_asyncReprojectionActive) { return TARGET_RATE_OpenVr / 2.0f; } return TARGET_RATE_OpenVr;} 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; // Different HMDs end up showing the squeezed-vision egg as different sizes. These values // attempt to make them appear the same. _visionSqueezeDeviceLowX = 0.8f; _visionSqueezeDeviceHighX = 0.98f; _visionSqueezeDeviceLowY = 0.8f; _visionSqueezeDeviceHighY = 0.9f; emit deviceConnected(getName());} const QString OpenVrDisplayPlugin::getName() const { std::string headsetName = getOpenVrDeviceName(); if (headsetName == "HTC") { headsetName += " Vive"; } return QString::fromStdString(headsetName);} bool OpenVrDisplayPlugin::internalActivate() { if (!_system) { _system = acquireOpenVrSystem(); } if (!_system) { qWarning() << "Failed to initialize OpenVR"; return false; } // If OpenVR isn't running, then the compositor won't be accessible // FIXME find a way to launch the compositor? if (!vr::VRCompositor()) { qWarning() << "Failed to acquire OpenVR compositor"; releaseOpenVrSystem(); _system = nullptr; return false; } vr::Compositor_FrameTiming timing; memset(&timing, 0, sizeof(timing)); timing.m_nSize = sizeof(vr::Compositor_FrameTiming); vr::VRCompositor()->GetFrameTiming(&timing); auto usingOpenVRForOculus = oculusViaOpenVR(); _asyncReprojectionActive = (timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync) || usingOpenVRForOculus; _threadedSubmit = !_asyncReprojectionActive; if (usingOpenVRForOculus) { qDebug() << "Oculus active via OpenVR: " << usingOpenVRForOculus; } qDebug() << "OpenVR Async Reprojection active: " << _asyncReprojectionActive; qDebug() << "OpenVR Threaded submit enabled: " << _threadedSubmit; _openVrDisplayActive = true; _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)); }); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes _cullingProjection = _eyeProjections[0]; }); // enable async time warp if (forceInterleavedReprojection) { vr::VRCompositor()->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 = 0.0f; 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 } if (_threadedSubmit) { _submitThread = std::make_shared<OpenVrSubmitThread>(*this); if (!_submitCanvas) { withOtherThreadContext([&] { _submitCanvas = std::make_shared<gl::OffscreenContext>(); _submitCanvas->create(); _submitCanvas->doneCurrent(); }); } connect(_submitThread.get(), &QThread::started, [] { setThreadName("OpenVR Submit Thread"); }); _submitCanvas->moveToThread(_submitThread.get()); }Lines 1–500 of 874. Load more
// // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2015 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // // 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" // Odd ordering of header is required to avoid 'macro redinition warnings' #include <AudioClient.h> #include <QtCore/QThread> #include <QtCore/QLoggingCategory> #include <QtCore/QFileInfo> #include <QtCore/QDateTime> #include <GLMHelpers.h> #include <gl/Context.h> #include <gl/GLShaders.h> #include <gpu/Frame.h> #include <gpu/gl/GLBackend.h> #include <ViewFrustum.h> #include <PathUtils.h> #include <shared/NsightHelpers.h> #include <controllers/Pose.h> #include <display-plugins/CompositorHelper.h> #include <ui-plugins/PluginContainer.h> #include <gl/OffscreenGLCanvas.h> #include <ThreadHelpers.h> #include "OpenVrHelpers.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) const char* OpenVrThreadedSubmit{ "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here PoseData _nextRenderPoseData; PoseData _nextSimPoseData; #define MIN_CORES_FOR_NORMAL_RENDER 5 bool forceInterleavedReprojection = (QThread::idealThreadCount() < MIN_CORES_FOR_NORMAL_RENDER); static std::array<vr::Hmd_Eye, 2> VR_EYES{ { vr::Eye_Left, vr::Eye_Right } }; bool _openVrDisplayActive{ false }; // Flip y-axis since GL UV coords are backwards. static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_LEFT{ 0, 0, 0.5f, 1 }; static vr::VRTextureBounds_t OPENVR_TEXTURE_BOUNDS_RIGHT{ 0.5f, 0, 1, 1 }; #define REPROJECTION_BINDING 1 static const char* HMD_REPROJECTION_VERT = R"SHADER( #version 450 core out vec3 vPosition; out vec2 vTexCoord; void main(void) { const float depth = 0.0; const vec4 UNIT_QUAD[4] = vec4[4]( vec4(-1.0, -1.0, depth, 1.0), vec4(1.0, -1.0, depth, 1.0), vec4(-1.0, 1.0, depth, 1.0), vec4(1.0, 1.0, depth, 1.0) ); vec4 pos = UNIT_QUAD[gl_VertexID]; gl_Position = pos; vPosition = pos.xyz; vTexCoord = (pos.xy + 1.0) * 0.5; } )SHADER"; static const char* HMD_REPROJECTION_FRAG = R"SHADER( #version 450 core uniform sampler2D sampler; layout(binding = 1, std140) uniform Reprojection { mat4 projections[2]; mat4 inverseProjections[2]; mat4 reprojection; }; in vec3 vPosition; in vec2 vTexCoord; out vec4 FragColor; void main() { vec2 uv = vTexCoord; mat4 eyeInverseProjection; mat4 eyeProjection; float xoffset = 1.0; vec2 uvmin = vec2(0.0); vec2 uvmax = vec2(1.0); // determine the correct projection and inverse projection to use. if (vTexCoord.x < 0.5) { uvmax.x = 0.5; eyeInverseProjection = inverseProjections[0]; eyeProjection = projections[0]; } else { xoffset = -1.0; uvmin.x = 0.5; uvmax.x = 1.0; eyeInverseProjection = inverseProjections[1]; eyeProjection = projections[1]; } // Account for stereo in calculating the per-eye NDC coordinates vec4 ndcSpace = vec4(vPosition, 1.0); ndcSpace.x *= 2.0; ndcSpace.x += xoffset; // Convert from NDC to eyespace vec4 eyeSpace = eyeInverseProjection * ndcSpace; eyeSpace /= eyeSpace.w; // Convert to a noramlized ray vec3 ray = eyeSpace.xyz; ray = normalize(ray); // Adjust the ray by the rotation ray = mat3(reprojection) * ray; // Project back on to the texture plane ray *= eyeSpace.z / ray.z; // Update the eyespace vector eyeSpace.xyz = ray; // Reproject back into NDC ndcSpace = eyeProjection * eyeSpace; ndcSpace /= ndcSpace.w; ndcSpace.x -= xoffset; ndcSpace.x /= 2.0; // Calculate the new UV coordinates uv = (ndcSpace.xy / 2.0) + 0.5; if (any(greaterThan(uv, uvmax)) || any(lessThan(uv, uvmin))) { FragColor = vec4(0.0, 0.0, 0.0, 1.0); } else { FragColor = texture(sampler, uv); } } )SHADER"; struct Reprojection { mat4 projections[2]; mat4 inverseProjections[2]; mat4 reprojection; }; class OpenVrSubmitThread : public QThread, public Dependency { public: using Mutex = std::mutex; using Condition = std::condition_variable; using Lock = std::unique_lock<Mutex>; friend class OpenVrDisplayPlugin; std::shared_ptr<gl::OffscreenContext> _canvas; OpenVrSubmitThread(OpenVrDisplayPlugin& plugin) : _plugin(plugin) { setObjectName("OpenVR Submit Thread"); } void updateSource() { _plugin.withNonPresentThreadLock([&] { while (!_queue.empty()) { auto& front = _queue.front(); auto result = glClientWaitSync((GLsync)front.fence, 0, 0); if (GL_TIMEOUT_EXPIRED == result || GL_WAIT_FAILED == result) { break; } else if (GL_CONDITION_SATISFIED == result || GL_ALREADY_SIGNALED == result) { glDeleteSync((GLsync)front.fence); } else { assert(false); } front.fence = 0; _current = front; _queue.pop(); } }); } GLuint _program{ 0 }; void updateProgram() { if (!_program) { std::string vsSource = HMD_REPROJECTION_VERT; std::string fsSource = HMD_REPROJECTION_FRAG; GLuint vertexShader{ 0 }, fragmentShader{ 0 }; std::string error; ::gl::compileShader(GL_VERTEX_SHADER, vsSource, vertexShader, error); ::gl::compileShader(GL_FRAGMENT_SHADER, fsSource, fragmentShader, error); _program = ::gl::buildProgram({ { vertexShader, fragmentShader } }); ::gl::linkProgram(_program, error); glDeleteShader(vertexShader); glDeleteShader(fragmentShader); qDebug() << "Rebuild proigram"; } } #define COLOR_BUFFER_COUNT 4 void run() override { GLuint _framebuffer{ 0 }; std::array<GLuint, COLOR_BUFFER_COUNT> _colors; size_t currentColorBuffer{ 0 }; size_t globalColorBufferCount{ 0 }; GLuint _uniformBuffer{ 0 }; GLuint _vao{ 0 }; GLuint _depth{ 0 }; Reprojection _reprojection; QThread::currentThread()->setPriority(QThread::Priority::TimeCriticalPriority); _canvas->makeCurrent(); glCreateBuffers(1, &_uniformBuffer); glNamedBufferStorage(_uniformBuffer, sizeof(Reprojection), 0, GL_DYNAMIC_STORAGE_BIT); glCreateVertexArrays(1, &_vao); glBindVertexArray(_vao); glCreateFramebuffers(1, &_framebuffer); { glCreateRenderbuffers(1, &_depth); glNamedRenderbufferStorage(_depth, GL_DEPTH24_STENCIL8, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); glNamedFramebufferRenderbuffer(_framebuffer, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depth); glCreateTextures(GL_TEXTURE_2D, COLOR_BUFFER_COUNT, &_colors[0]); for (size_t i = 0; i < COLOR_BUFFER_COUNT; ++i) { glTextureStorage2D(_colors[i], 1, GL_RGBA8, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); } } glDisable(GL_DEPTH_TEST); glViewport(0, 0, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y); _canvas->doneCurrent(); while (!_quit) { _canvas->makeCurrent(); updateSource(); if (!_current.texture) { _canvas->doneCurrent(); QThread::usleep(1); continue; } updateProgram(); { auto presentRotation = glm::mat3(_nextRender.poses[0]); auto renderRotation = glm::mat3(_current.pose); for (size_t i = 0; i < 2; ++i) { _reprojection.projections[i] = _plugin._eyeProjections[i]; _reprojection.inverseProjections[i] = _plugin._eyeInverseProjections[i]; } _reprojection.reprojection = glm::inverse(renderRotation) * presentRotation; glNamedBufferSubData(_uniformBuffer, 0, sizeof(Reprojection), &_reprojection); glNamedFramebufferTexture(_framebuffer, GL_COLOR_ATTACHMENT0, _colors[currentColorBuffer], 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _framebuffer); { glClearColor(1, 1, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glTextureParameteri(_current.textureID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTextureParameteri(_current.textureID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glUseProgram(_program); glBindBufferBase(GL_UNIFORM_BUFFER, REPROJECTION_BINDING, _uniformBuffer); glBindTexture(GL_TEXTURE_2D, _current.textureID); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); static const vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; static const vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; vr::Texture_t texture{ (void*)(uintptr_t)_colors[currentColorBuffer], vr::TextureType_OpenGL, vr::ColorSpace_Auto }; vr::VRCompositor()->Submit(vr::Eye_Left, &texture, &leftBounds); vr::VRCompositor()->Submit(vr::Eye_Right, &texture, &rightBounds); _plugin._presentRate.increment(); PoseData nextRender, nextSim; nextRender.frameIndex = _plugin.presentCount(); vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); // Copy invalid poses in nextSim from nextRender for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { if (!nextSim.vrPoses[i].bPoseIsValid) { nextSim.vrPoses[i] = nextRender.vrPoses[i]; } } mat4 sensorResetMat; _plugin.withNonPresentThreadLock([&] { sensorResetMat = _plugin._sensorResetMat; }); nextRender.update(sensorResetMat); nextSim.update(sensorResetMat); _plugin.withNonPresentThreadLock([&] { _nextRender = nextRender; _nextSim = nextSim; ++_presentCount; _presented.notify_one(); }); ++globalColorBufferCount; currentColorBuffer = globalColorBufferCount % COLOR_BUFFER_COUNT; } _canvas->doneCurrent(); } _canvas->makeCurrent(); glDeleteBuffers(1, &_uniformBuffer); glDeleteFramebuffers(1, &_framebuffer); CHECK_GL_ERROR(); glDeleteTextures(4, &_colors[0]); glDeleteProgram(_program); glBindVertexArray(0); glDeleteVertexArrays(1, &_vao); _canvas->doneCurrent(); _canvas->moveToThread(_plugin.thread()); } void update(const CompositeInfo& newCompositeInfo) { _queue.push(newCompositeInfo); } void waitForPresent() { auto lastCount = _presentCount.load(); Lock lock(_plugin._presentMutex); _presented.wait(lock, [&]() -> bool { return _presentCount.load() > lastCount; }); _nextSimPoseData = _nextSim; _nextRenderPoseData = _nextRender; } CompositeInfo _current; CompositeInfo::Queue _queue; PoseData _nextRender, _nextSim; bool _quit{ false }; GLuint _currentTexture{ 0 }; std::atomic<uint32_t> _presentCount{ 0 }; Condition _presented; OpenVrDisplayPlugin& _plugin; }; bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } glm::mat4 OpenVrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { if (_system) { ViewFrustum baseFrustum; baseFrustum.setProjection(baseProjection); float baseNearClip = baseFrustum.getNearClip(); float baseFarClip = baseFrustum.getFarClip(); vr::EVREye openVrEye = (eye == Left) ? vr::Eye_Left : vr::Eye_Right; return toGlm(_system->GetProjectionMatrix(openVrEye, baseNearClip, baseFarClip)); } else { return baseProjection; } } glm::mat4 OpenVrDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const { if (_system) { ViewFrustum baseFrustum; baseFrustum.setProjection(baseProjection); float baseNearClip = baseFrustum.getNearClip(); float baseFarClip = baseFrustum.getFarClip(); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes return toGlm(_system->GetProjectionMatrix((vr::EVREye)0, baseNearClip, baseFarClip)); } else { return baseProjection; } } float OpenVrDisplayPlugin::getTargetFrameRate() const { if (forceInterleavedReprojection && !_asyncReprojectionActive) { return TARGET_RATE_OpenVr / 2.0f; } return TARGET_RATE_OpenVr; } 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; // Different HMDs end up showing the squeezed-vision egg as different sizes. These values // attempt to make them appear the same. _visionSqueezeDeviceLowX = 0.8f; _visionSqueezeDeviceHighX = 0.98f; _visionSqueezeDeviceLowY = 0.8f; _visionSqueezeDeviceHighY = 0.9f; emit deviceConnected(getName()); } const QString OpenVrDisplayPlugin::getName() const { std::string headsetName = getOpenVrDeviceName(); if (headsetName == "HTC") { headsetName += " Vive"; } return QString::fromStdString(headsetName); } bool OpenVrDisplayPlugin::internalActivate() { if (!_system) { _system = acquireOpenVrSystem(); } if (!_system) { qWarning() << "Failed to initialize OpenVR"; return false; } // If OpenVR isn't running, then the compositor won't be accessible // FIXME find a way to launch the compositor? if (!vr::VRCompositor()) { qWarning() << "Failed to acquire OpenVR compositor"; releaseOpenVrSystem(); _system = nullptr; return false; } vr::Compositor_FrameTiming timing; memset(&timing, 0, sizeof(timing)); timing.m_nSize = sizeof(vr::Compositor_FrameTiming); vr::VRCompositor()->GetFrameTiming(&timing); auto usingOpenVRForOculus = oculusViaOpenVR(); _asyncReprojectionActive = (timing.m_nReprojectionFlags & VRCompositor_ReprojectionAsync) || usingOpenVRForOculus; _threadedSubmit = !_asyncReprojectionActive; if (usingOpenVRForOculus) { qDebug() << "Oculus active via OpenVR: " << usingOpenVRForOculus; } qDebug() << "OpenVR Async Reprojection active: " << _asyncReprojectionActive; qDebug() << "OpenVR Threaded submit enabled: " << _threadedSubmit; _openVrDisplayActive = true; _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)); }); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes _cullingProjection = _eyeProjections[0]; }); // enable async time warp if (forceInterleavedReprojection) { vr::VRCompositor()->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 = 0.0f; 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 } if (_threadedSubmit) { _submitThread = std::make_shared<OpenVrSubmitThread>(*this); if (!_submitCanvas) { withOtherThreadContext([&] { _submitCanvas = std::make_shared<gl::OffscreenContext>(); _submitCanvas->create(); _submitCanvas->doneCurrent(); }); } connect(_submitThread.get(), &QThread::started, [] { setThreadName("OpenVR Submit Thread"); }); _submitCanvas->moveToThread(_submitThread.get()); }