Restoring reprojection to OpenVR

This commit is contained in:
Brad Davis 2016-08-08 16:51:11 -07:00
parent f9d522a1ae
commit 719e555381
11 changed files with 386 additions and 46 deletions

View file

@ -0,0 +1,78 @@
//
// Created by Bradley Austin Davis on 2016/07/11
// Copyright 2013-2016 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
//
#version 410 core
uniform sampler2D sampler;
uniform mat3 reprojection = mat3(1);
uniform mat4 inverseProjections[2];
uniform mat4 projections[2];
in vec2 vTexCoord;
in vec3 vPosition;
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 = 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);
}
}

View file

@ -0,0 +1,20 @@
//
// Created by Bradley Austin Davis on 2016/07/11
// Copyright 2013-2016 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
//
#version 410 core
in vec3 Position;
in vec2 TexCoord;
out vec3 vPosition;
out vec2 vTexCoord;
void main() {
gl_Position = vec4(Position, 1);
vTexCoord = TexCoord;
vPosition = Position;
}

View file

@ -386,7 +386,6 @@ void OpenGLDisplayPlugin::customizeContext() {
}
auto renderSize = getRecommendedRenderSize();
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
_compositeTexture = _compositeFramebuffer->getRenderBuffer(0);
}
void OpenGLDisplayPlugin::uncustomizeContext() {
@ -394,7 +393,6 @@ void OpenGLDisplayPlugin::uncustomizeContext() {
_cursorPipeline.reset();
_overlayPipeline.reset();
_compositeFramebuffer.reset();
_compositeTexture.reset();
withPresentThreadLock([&] {
_currentFrame.reset();
while (!_newFrameQueue.empty()) {
@ -538,7 +536,6 @@ void OpenGLDisplayPlugin::compositeLayers() {
auto renderSize = getRecommendedRenderSize();
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
_compositeTexture = _compositeFramebuffer->getRenderBuffer(0);
}
{

View file

@ -69,7 +69,7 @@ protected:
glm::uvec2 getSurfaceSize() const;
glm::uvec2 getSurfacePixels() const;
void compositeLayers();
virtual void compositeLayers();
virtual void compositeScene();
virtual void compositeOverlay();
virtual void compositePointer();
@ -110,7 +110,6 @@ protected:
gpu::FramePointer _currentFrame;
gpu::FramebufferPointer _compositeFramebuffer;
gpu::TexturePointer _compositeTexture;
gpu::PipelinePointer _overlayPipeline;
gpu::PipelinePointer _simplePipeline;
gpu::PipelinePointer _presentPipeline;
@ -148,7 +147,7 @@ protected:
}
gpu::gl::GLBackend* getGLBackend();
private:
// Any resource shared by the main thread and the presentation thread must
// be serialized through this mutex
mutable Mutex _presentMutex;

View file

@ -163,7 +163,7 @@ void HmdDisplayPlugin::internalPresent() {
viewport.z *= 2;
}
batch.setViewportTransform(viewport);
batch.setResourceTexture(0, _compositeTexture);
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
batch.setPipeline(_presentPipeline);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});

View file

@ -11,6 +11,10 @@
#include <atomic>
#if _DEBUG
#include <QtCore/QDebug>
#endif
#include "Forward.h"
#include "Format.h"
#include "Resource.h"

View file

@ -17,10 +17,6 @@ Frame::~Frame() {
framebuffer.reset();
}
if (overlay && overlayRecycler) {
overlayRecycler(overlay);
overlay.reset();
}
assert(bufferUpdates.empty());
if (!bufferUpdates.empty()) {
qFatal("Buffer sync error... frame destroyed without buffer updates being applied");

View file

@ -32,6 +32,8 @@ namespace gpu {
Mat4 pose;
/// The collection of batches which make up the frame
Batches batches;
/// The main thread updates to buffers that are applicable for this frame.
BufferUpdates bufferUpdates;
/// The destination framebuffer in which the results will be placed
FramebufferPointer framebuffer;
/// The destination texture containing the 2D overlay
@ -39,10 +41,6 @@ namespace gpu {
/// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE
FramebufferRecycler framebufferRecycler;
/// How to process the overlay texture when the frame dies. MUST BE THREAD SAFE
OverlayRecycler overlayRecycler;
BufferUpdates bufferUpdates;
};
};

View file

@ -257,7 +257,7 @@ void OculusLegacyDisplayPlugin::hmdPresent() {
memset(eyePoses, 0, sizeof(ovrPosef) * 2);
eyePoses[0].Orientation = eyePoses[1].Orientation = ovrRotation;
GLint texture = getGLBackend()->getTextureID(_compositeTexture, false);
GLint texture = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0), false);
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
if (_hmdWindow->makeCurrent()) {

View file

@ -7,26 +7,23 @@
//
#include "OpenVrDisplayPlugin.h"
#include <memory>
#include <QMainWindow>
#include <QLoggingCategory>
#include <QGLWidget>
#include <QEvent>
#include <QResizeEvent>
#include <QtCore/QThread>
#include <QtCore/QLoggingCategory>
#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 <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 <gl/OglplusHelpers.h>
#include "OpenVrHelpers.h"
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
@ -34,14 +31,197 @@ 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 };
// 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 OPENVR_THREADED_SUBMIT 1
#if OPENVR_THREADED_SUBMIT
static QString readFile(const QString& filename) {
QFile file(filename);
file.open(QFile::Text | QFile::ReadOnly);
QString result;
result.append(QTextStream(&file).readAll());
return result;
}
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;
OffscreenGLCanvas _canvas;
BasicFramebufferWrapperPtr _framebuffer;
ProgramPtr _program;
ShapeWrapperPtr _plane;
struct ReprojectionUniforms {
int32_t reprojectionMatrix{ -1 };
int32_t inverseProjectionMatrix{ -1 };
int32_t projectionMatrix{ -1 };
} _reprojectionUniforms;
OpenVrSubmitThread(OpenVrDisplayPlugin& plugin) : _plugin(plugin) {
_canvas.create(plugin._container->getPrimaryContext());
_canvas.doneCurrent();
_canvas.moveToThreadWithContext(this);
}
void updateReprojectionProgram() {
static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.vert";
static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_reproject.frag";
#if LIVE_SHADER_RELOAD
static qint64 vsBuiltAge = 0;
static qint64 fsBuiltAge = 0;
QFileInfo vsInfo(vsFile);
QFileInfo fsInfo(fsFile);
auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch();
auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch();
if (!_reprojectionProgram || vsAge > vsBuiltAge || fsAge > fsBuiltAge) {
vsBuiltAge = vsAge;
fsBuiltAge = fsAge;
#else
if (!_program) {
#endif
QString vsSource = readFile(vsFile);
QString fsSource = readFile(fsFile);
ProgramPtr program;
try {
compileProgram(program, vsSource.toLocal8Bit().toStdString(), fsSource.toLocal8Bit().toStdString());
if (program) {
using namespace oglplus;
_reprojectionUniforms.reprojectionMatrix = Uniform<glm::mat3>(*program, "reprojection").Location();
_reprojectionUniforms.inverseProjectionMatrix = Uniform<glm::mat4>(*program, "inverseProjections").Location();
_reprojectionUniforms.projectionMatrix = Uniform<glm::mat4>(*program, "projections").Location();
_program = program;
}
} catch (std::runtime_error& error) {
qWarning() << "Error building reprojection shader " << error.what();
}
}
}
void updateSource() {
Lock lock(_plugin._presentMutex);
while (!_queue.empty()) {
auto& front = _queue.front();
auto result = glClientWaitSync(front.fence, 0, 0);
if (GL_TIMEOUT_EXPIRED == result && GL_WAIT_FAILED == result) {
break;
}
glDeleteSync(front.fence);
front.fence = 0;
_current = front;
_queue.pop();
}
}
void run() override {
QThread::currentThread()->setPriority(QThread::Priority::TimeCriticalPriority);
_canvas.makeCurrent();
glDisable(GL_DEPTH_TEST);
glViewport(0, 0, _plugin._renderTargetSize.x, _plugin._renderTargetSize.y);
_framebuffer = std::make_shared<BasicFramebufferWrapper>();
_framebuffer->Init(_plugin._renderTargetSize);
updateReprojectionProgram();
_plane = loadPlane(_program);
_canvas.doneCurrent();
while (!_quit) {
_canvas.makeCurrent();
updateSource();
if (!_current.texture) {
_canvas.doneCurrent();
QThread::usleep(1);
continue;
}
{
auto presentRotation = glm::mat3(_nextRender.poses[0]);
auto renderRotation = glm::mat3(_current.pose);
auto correction = glm::inverse(renderRotation) * presentRotation;
_framebuffer->Bound([&] {
glBindTexture(GL_TEXTURE_2D, _current.textureID);
_program->Use();
using namespace oglplus;
Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear);
Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear);
Uniform<glm::mat3>(*_program, _reprojectionUniforms.reprojectionMatrix).Set(correction);
//Uniform<glm::mat4>(*_reprojectionProgram, PROJECTION_MATRIX_LOCATION).Set(_eyeProjections);
//Uniform<glm::mat4>(*_reprojectionProgram, INVERSE_PROJECTION_MATRIX_LOCATION).Set(_eyeInverseProjections);
// FIXME what's the right oglplus mechanism to do this? It's not that ^^^ ... better yet, switch to a uniform buffer
glUniformMatrix4fv(_reprojectionUniforms.inverseProjectionMatrix, 2, GL_FALSE, &(_plugin._eyeInverseProjections[0][0][0]));
glUniformMatrix4fv(_reprojectionUniforms.projectionMatrix, 2, GL_FALSE, &(_plugin._eyeProjections[0][0][0]));
_plane->UseInProgram(*_program);
_plane->Draw();
});
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*)oglplus::GetName(_framebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto };
vr::VRCompositor()->Submit(vr::Eye_Left, &texture, &leftBounds);
vr::VRCompositor()->Submit(vr::Eye_Right, &texture, &rightBounds);
PoseData nextRender, nextSim;
nextRender.frameIndex = _plugin.presentCount();
vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount);
{
Lock lock(_plugin._presentMutex);
_presentCount++;
_presented.notify_one();
_nextRender = nextRender;
_nextRender.update(_plugin._sensorResetMat);
_nextSim = nextSim;
_nextSim.update(_plugin._sensorResetMat);
}
}
_canvas.doneCurrent();
}
_canvas.makeCurrent();
_plane.reset();
_program.reset();
_framebuffer.reset();
_canvas.doneCurrent();
}
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;
};
#endif
bool OpenVrDisplayPlugin::isSupported() const {
return openVrSupported();
@ -92,11 +272,8 @@ bool OpenVrDisplayPlugin::internalActivate() {
_cullingProjection = _eyeProjections[0];
});
_compositor = vr::VRCompositor();
Q_ASSERT(_compositor);
// enable async time warp
// _compositor->ForceInterleavedReprojectionOn(true);
//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();
@ -115,11 +292,25 @@ bool OpenVrDisplayPlugin::internalActivate() {
#endif
}
#if OPENVR_THREADED_SUBMIT
withMainThreadContext([&] {
_submitThread = std::make_shared<OpenVrSubmitThread>(*this);
});
_submitThread->setObjectName("OpenVR Submit Thread");
_submitThread->start(QThread::TimeCriticalPriority);
#endif
return Parent::internalActivate();
}
void OpenVrDisplayPlugin::internalDeactivate() {
Parent::internalDeactivate();
#if OPENVR_THREADED_SUBMIT
_submitThread->_quit = true;
_submitThread->wait();
#endif
_openVrDisplayActive = false;
_container->setIsOptionChecked(StandingHMDSensorMode, false);
if (_system) {
@ -128,7 +319,6 @@ void OpenVrDisplayPlugin::internalDeactivate() {
releaseOpenVrSystem();
_system = nullptr;
}
_compositor = nullptr;
}
void OpenVrDisplayPlugin::customizeContext() {
@ -141,6 +331,18 @@ void OpenVrDisplayPlugin::customizeContext() {
});
Parent::customizeContext();
_compositeInfos[0].texture = _compositeFramebuffer->getRenderBuffer(0);
for (size_t i = 0; i < COMPOSITING_BUFFER_SIZE; ++i) {
if (0 != i) {
_compositeInfos[i].texture = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, _renderTargetSize.x, _renderTargetSize.y, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT)));
}
_compositeInfos[i].textureID = getGLBackend()->getTextureID(_compositeInfos[i].texture, false);
}
}
void OpenVrDisplayPlugin::uncustomizeContext() {
Parent::uncustomizeContext();
}
void OpenVrDisplayPlugin::resetSensors() {
@ -228,25 +430,48 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
return Parent::beginFrameRender(frameIndex);
}
void OpenVrDisplayPlugin::compositeLayers() {
#if OPENVR_THREADED_SUBMIT
++_renderingIndex;
_renderingIndex %= COMPOSITING_BUFFER_SIZE;
auto& newComposite = _compositeInfos[_renderingIndex];
newComposite.pose = _currentPresentFrameInfo.presentPose;
_compositeFramebuffer->setRenderBuffer(0, newComposite.texture);
#endif
Parent::compositeLayers();
#if OPENVR_THREADED_SUBMIT
newComposite.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (!newComposite.textureID) {
newComposite.textureID = getGLBackend()->getTextureID(newComposite.texture, false);
}
withPresentThreadLock([&] {
_submitThread->update(newComposite);
});
#endif
}
void OpenVrDisplayPlugin::hmdPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
glFlush();
// 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 };
#if OPENVR_THREADED_SUBMIT
_submitThread->waitForPresent();
_compositor->Submit(vr::Eye_Left, &vrTexture, &leftBounds);
_compositor->Submit(vr::Eye_Right, &vrTexture, &rightBounds);
_compositor->PostPresentHandoff();
#else
vr::Texture_t vrTexture{ (void*)glTexId, vr::API_OpenGL, vr::ColorSpace_Auto };
vr::VRCompositor()->Submit(vr::Eye_Left, &vrTexture, &OPENVR_TEXTURE_BOUNDS_LEFT);
vr::VRCompositor()->Submit(vr::Eye_Right, &vrTexture, &OPENVR_TEXTURE_BOUNDS_RIGHT);
vr::VRCompositor()->PostPresentHandoff();
#endif
}
void OpenVrDisplayPlugin::postPreview() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
PoseData nextRender, nextSim;
nextRender.frameIndex = presentCount();
#if !OPENVR_THREADED_SUBMIT
vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount);
glm::mat4 resetMat;
@ -255,12 +480,12 @@ void OpenVrDisplayPlugin::postPreview() {
});
nextRender.update(resetMat);
nextSim.update(resetMat);
withPresentThreadLock([&] {
_nextSimPoseData = nextSim;
});
_nextRenderPoseData = nextRender;
_hmdActivityLevel = vr::k_EDeviceActivityLevel_UserInteraction; // _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);
#endif
}
bool OpenVrDisplayPlugin::isHmdMounted() const {

View file

@ -15,6 +15,21 @@
const float TARGET_RATE_OpenVr = 90.0f; // FIXME: get from sdk tracked device property? This number is vive-only.
class OpenVrSubmitThread;
static const size_t COMPOSITING_BUFFER_SIZE = 3;
struct CompositeInfo {
using Queue = std::queue<CompositeInfo>;
using Array = std::array<CompositeInfo, COMPOSITING_BUFFER_SIZE>;
gpu::TexturePointer texture;
GLuint textureID { 0 };
glm::mat4 pose;
GLsync fence{ 0 };
};
class OpenVrDisplayPlugin : public HmdDisplayPlugin {
using Parent = HmdDisplayPlugin;
public:
@ -26,6 +41,7 @@ public:
float getTargetFrameRate() const override { return TARGET_RATE_OpenVr; }
void customizeContext() override;
void uncustomizeContext() override;
// Stereo specific methods
void resetSensors() override;
@ -41,16 +57,23 @@ protected:
void internalDeactivate() override;
void updatePresentPose() override;
void compositeLayers() override;
void hmdPresent() override;
bool isHmdMounted() const override;
void postPreview() override;
private:
CompositeInfo::Array _compositeInfos;
size_t _renderingIndex { 0 };
vr::IVRSystem* _system { nullptr };
std::atomic<vr::EDeviceActivityLevel> _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown };
std::atomic<uint32_t> _keyboardSupressionCount{ 0 };
static const QString NAME;
vr::HmdMatrix34_t _lastGoodHMDPose;
std::shared_ptr<OpenVrSubmitThread> _submitThread;
mat4 _sensorResetMat;
friend class OpenVrSubmitThread;
};