// // Created by Bradley Austin Davis on 2014/04/13. // 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 "OculusDisplayPlugin.h" #include // FIXME get rid of this #include #include #include "OculusHelpers.h" #if (OVR_MAJOR_VERSION >= 6) // A base class for FBO wrappers that need to use the Oculus C // API to manage textures via ovr_CreateSwapTextureSetGL, // ovr_CreateMirrorTextureGL, etc template struct RiftFramebufferWrapper : public FramebufferWrapper { ovrHmd hmd; RiftFramebufferWrapper(const ovrHmd & hmd) : hmd(hmd) { color = 0; depth = 0; }; void Resize(const uvec2 & size) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); this->size = size; initColor(); initDone(); } protected: virtual void initDepth() override final { } }; // A wrapper for constructing and using a swap texture set, // where each frame you draw to a texture via the FBO, // then submit it and increment to the next texture. // The Oculus SDK manages the creation and destruction of // the textures struct SwapFramebufferWrapper : public RiftFramebufferWrapper { SwapFramebufferWrapper(const ovrHmd & hmd) : RiftFramebufferWrapper(hmd) { } ~SwapFramebufferWrapper() { if (color) { ovr_DestroySwapTextureSet(hmd, color); color = nullptr; } } void Increment() { ++color->CurrentIndex; color->CurrentIndex %= color->TextureCount; } protected: virtual void initColor() override { if (color) { ovr_DestroySwapTextureSet(hmd, color); color = nullptr; } if (!OVR_SUCCESS(ovr_CreateSwapTextureSetGL(hmd, GL_RGBA, size.x, size.y, &color))) { qFatal("Unable to create swap textures"); } for (int i = 0; i < color->TextureCount; ++i) { ovrGLTexture& ovrTex = (ovrGLTexture&)color->Textures[i]; glBindTexture(GL_TEXTURE_2D, ovrTex.OGL.TexId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } glBindTexture(GL_TEXTURE_2D, 0); } virtual void initDone() override { } virtual void onBind(oglplus::Framebuffer::Target target) override { ovrGLTexture& tex = (ovrGLTexture&)(color->Textures[color->CurrentIndex]); glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.OGL.TexId, 0); } virtual void onUnbind(oglplus::Framebuffer::Target target) override { glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); } }; // We use a FBO to wrap the mirror texture because it makes it easier to // render to the screen via glBlitFramebuffer struct MirrorFramebufferWrapper : public RiftFramebufferWrapper { MirrorFramebufferWrapper(const ovrHmd & hmd) : RiftFramebufferWrapper(hmd) { } virtual ~MirrorFramebufferWrapper() { if (color) { ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } } private: void initColor() override { if (color) { ovr_DestroyMirrorTexture(hmd, (ovrTexture*)color); color = nullptr; } ovrResult result = ovr_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); Q_ASSERT(OVR_SUCCESS(result)); } void initDone() override { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color->OGL.TexId, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } }; #endif const QString OculusDisplayPlugin::NAME("Oculus Rift"); const QString & OculusDisplayPlugin::getName() const { return NAME; } static const QString MONO_PREVIEW = "Mono Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; void OculusDisplayPlugin::activate() { _container->addMenuItem(PluginType::DISPLAY_PLUGIN, MENU_PATH(), MONO_PREVIEW, [this](bool clicked) { _monoPreview = clicked; }, true, true); _container->removeMenu(FRAMERATE); OculusBaseDisplayPlugin::activate(); } void OculusDisplayPlugin::customizeContext() { OculusBaseDisplayPlugin::customizeContext(); #if (OVR_MAJOR_VERSION >= 6) _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); // We're rendering both eyes to the same texture, so only one of the // pointers is populated _sceneLayer.ColorTexture[0] = _sceneFbo->color; // not needed since the structure was zeroed on init, but explicit _sceneLayer.ColorTexture[1] = nullptr; #endif enableVsync(false); // Only enable mirroring if we know vsync is disabled _enablePreview = !isVsyncEnabled(); } void OculusDisplayPlugin::uncustomizeContext() { #if (OVR_MAJOR_VERSION >= 6) _sceneFbo.reset(); #endif OculusBaseDisplayPlugin::uncustomizeContext(); } void OculusDisplayPlugin::internalPresent() { #if (OVR_MAJOR_VERSION >= 6) if (!_currentSceneTexture) { return; } using namespace oglplus; // Need to make sure only the display plugin is responsible for // controlling vsync wglSwapIntervalEXT(0); // screen preview mirroring if (_enablePreview) { auto windowSize = toGlm(_window->size()); if (_monoPreview) { Context::Viewport(windowSize.x * 2, windowSize.y); Context::Scissor(0, windowSize.y, windowSize.x, windowSize.y); } else { Context::Viewport(windowSize.x, windowSize.y); } glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); Q_ASSERT(0 == err); drawUnitQuad(); } _sceneFbo->Bound([&] { auto size = _sceneFbo->size; Context::Viewport(size.x, size.y); glBindTexture(GL_TEXTURE_2D, _currentSceneTexture); GLenum err = glGetError(); drawUnitQuad(); }); uint32_t frameIndex { 0 }; EyePoses eyePoses; { Lock lock(_mutex); Q_ASSERT(_sceneTextureToFrameIndexMap.contains(_currentSceneTexture)); frameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; Q_ASSERT(_frameEyePoses.contains(frameIndex)); eyePoses = _frameEyePoses[frameIndex]; } _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = eyePoses.first; _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = eyePoses.second; { ovrViewScaleDesc viewScaleDesc; viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f; viewScaleDesc.HmdToEyeViewOffset[0] = _eyeOffsets[0]; viewScaleDesc.HmdToEyeViewOffset[1] = _eyeOffsets[1]; ovrLayerHeader* layers = &_sceneLayer.Header; ovrResult result = ovr_SubmitFrame(_hmd, 0, &viewScaleDesc, &layers, 1); if (!OVR_SUCCESS(result)) { qDebug() << result; } } _sceneFbo->Increment(); #endif /* The swapbuffer call here is only required if we want to mirror the content to the screen. However, it should only be done if we can reliably disable v-sync on the mirror surface, otherwise the swapbuffer delay will interefere with the framerate of the headset */ if (_enablePreview) { swapBuffers(); } } void OculusDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { auto ovrPose = ovrPoseFromGlm(pose); { Lock lock(_mutex); if (eye == Eye::Left) { _frameEyePoses[frameIndex].first = ovrPose; } else { _frameEyePoses[frameIndex].second = ovrPose; } } }