From dd5901d3517cc519819a2c45b37b1fd93e571ce8 Mon Sep 17 00:00:00 2001 From: madsbuvi Date: Mon, 4 Apr 2022 22:51:23 +0200 Subject: [PATCH] Initial commit Multiview shaders. Refactor Frustum management Rewrite shared shadow map cull mask should respect stereo Stereo savegame screencap LocalMap refactoring use the vertex buffer hint instead of the display list patch to enable/disable display lists Character preview fixes --- .gitlab-ci.yml | 10 +- CI/before_script.msvc.sh | 49 ++- CMakeLists.txt | 5 + apps/openmw/engine.cpp | 55 ++- apps/openmw/engine.hpp | 20 + apps/openmw/mwbase/windowmanager.hpp | 6 + apps/openmw/mwgui/mapwindow.cpp | 4 + apps/openmw/mwgui/windowmanagerimp.cpp | 25 +- apps/openmw/mwgui/windowmanagerimp.hpp | 3 + apps/openmw/mwrender/characterpreview.cpp | 172 +++++--- apps/openmw/mwrender/characterpreview.hpp | 4 +- apps/openmw/mwrender/localmap.cpp | 302 +++++++------ apps/openmw/mwrender/localmap.hpp | 19 +- apps/openmw/mwrender/postprocessor.cpp | 191 +++++--- apps/openmw/mwrender/postprocessor.hpp | 14 +- apps/openmw/mwrender/renderingmanager.cpp | 108 ++++- apps/openmw/mwrender/renderingmanager.hpp | 5 +- apps/openmw/mwrender/screenshotmanager.cpp | 24 +- apps/openmw/mwrender/sky.cpp | 7 +- apps/openmw/mwrender/water.cpp | 14 +- cmake/CheckOsgMultiview.cmake | 26 ++ components/CMakeLists.txt | 6 +- components/sceneutil/color.cpp | 157 +++++++ components/sceneutil/color.hpp | 204 +++++++++ components/sceneutil/depth.cpp | 145 +++++- components/sceneutil/depth.hpp | 69 +++ components/sceneutil/mwshadowtechnique.cpp | 152 ++++++- components/sceneutil/mwshadowtechnique.hpp | 30 ++ components/sceneutil/rtt.cpp | 212 +++++++-- components/sceneutil/rtt.hpp | 51 ++- components/sceneutil/shadow.cpp | 3 + components/sceneutil/statesetupdater.cpp | 8 + components/sceneutil/statesetupdater.hpp | 5 + components/shader/shadervisitor.cpp | 3 + components/stereo/frustum.cpp | 162 +++++++ components/stereo/frustum.hpp | 76 ++++ components/stereo/multiview.cpp | 484 +++++++++++++++++++++ components/stereo/multiview.hpp | 85 ++++ components/stereo/stereomanager.cpp | 459 +++++++++++++++++++ components/stereo/stereomanager.hpp | 134 ++++++ components/stereo/types.cpp | 168 +++++++ components/stereo/types.hpp | 61 +++ components/terrain/material.cpp | 3 + files/settings-default.cfg | 57 +++ files/shaders/CMakeLists.txt | 2 + files/shaders/debug_fragment.glsl | 2 +- files/shaders/debug_vertex.glsl | 2 +- files/shaders/groundcover_fragment.glsl | 2 +- files/shaders/groundcover_vertex.glsl | 4 +- files/shaders/gui_fragment.glsl | 2 +- files/shaders/gui_vertex.glsl | 2 +- files/shaders/multiview_fragment.glsl | 48 ++ files/shaders/multiview_vertex.glsl | 80 ++++ files/shaders/nv_default_fragment.glsl | 2 +- files/shaders/nv_default_vertex.glsl | 5 +- files/shaders/nv_nolighting_fragment.glsl | 2 +- files/shaders/nv_nolighting_vertex.glsl | 2 +- files/shaders/objects_fragment.glsl | 2 +- files/shaders/objects_vertex.glsl | 5 +- files/shaders/s360_fragment.glsl | 2 +- files/shaders/s360_vertex.glsl | 2 +- files/shaders/shadowcasting_fragment.glsl | 2 +- files/shaders/shadowcasting_vertex.glsl | 2 +- files/shaders/sky_fragment.glsl | 2 +- files/shaders/sky_vertex.glsl | 18 +- files/shaders/terrain_fragment.glsl | 2 +- files/shaders/terrain_vertex.glsl | 5 +- files/shaders/water_fragment.glsl | 4 +- files/shaders/water_vertex.glsl | 2 +- 69 files changed, 3566 insertions(+), 434 deletions(-) create mode 100644 cmake/CheckOsgMultiview.cmake create mode 100644 components/sceneutil/color.cpp create mode 100644 components/sceneutil/color.hpp create mode 100644 components/stereo/frustum.cpp create mode 100644 components/stereo/frustum.hpp create mode 100644 components/stereo/multiview.cpp create mode 100644 components/stereo/multiview.hpp create mode 100644 components/stereo/stereomanager.cpp create mode 100644 components/stereo/stereomanager.hpp create mode 100644 components/stereo/types.cpp create mode 100644 components/stereo/types.hpp create mode 100644 files/shaders/multiview_fragment.glsl create mode 100644 files/shaders/multiview_vertex.glsl diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e81ffb481..fdfbdac13b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -424,6 +424,14 @@ Windows_Ninja_Engine_Release: <<: *engine-targets config: "Release" +Windows_Ninja_Engine_Release_MultiView: + extends: + - .Windows_Ninja_Base + variables: + <<: *engine-targets + multiview: "-M" + config: "Release" + Windows_Ninja_Engine_Debug: extends: - .Windows_Ninja_Base @@ -506,7 +514,7 @@ Windows_Ninja_Tests_RelWithDebInfo: - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview - cd MSVC2019_64 - cmake --build . --config $config --target ($targets.Split(',')) - ccache --show-stats diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 7c9ea20bd1..bdf7d24eb8 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -75,6 +75,7 @@ TEST_FRAMEWORK="" GOOGLE_INSTALL_ROOT="" INSTALL_PREFIX="." BUILD_BENCHMARKS="" +OSG_MULTIVIEW_BUILD="" ACTIVATE_MSVC="" SINGLE_CONFIG="" @@ -139,7 +140,10 @@ while [ $# -gt 0 ]; do b ) BUILD_BENCHMARKS=true ;; - + + M ) + OSG_MULTIVIEW_BUILD=true ;; + h ) cat < +#include +#include + #include #include @@ -40,6 +43,7 @@ #include #include +#include #include #include "mwinput/inputmanagerimp.hpp" @@ -251,6 +255,18 @@ namespace Log(Debug::Info) << "OpenGL Version: " << glGetString(GL_VERSION); } }; + + class InitializeStereoOperation final : public osg::GraphicsOperation + { + public: + InitializeStereoOperation() : GraphicsOperation("InitializeStereoOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override + { + Stereo::Manager::instance().initializeStereo(graphicsContext); + } + }; } void OMW::Engine::executeLocalScripts() @@ -413,6 +429,9 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(nullptr) , mScreenCaptureOperation(nullptr) + , mSelectDepthFormatOperation(new SceneUtil::SelectDepthFormatOperation()) + , mSelectColorFormatOperation(new SceneUtil::Color::SelectColorFormatOperation()) + , mStereoManager(nullptr) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) @@ -448,6 +467,8 @@ OMW::Engine::~Engine() if (mScreenCaptureOperation != nullptr) mScreenCaptureOperation->stop(); + mStereoManager = nullptr; + mEnvironment.cleanup(); delete mScriptContext; @@ -640,6 +661,15 @@ void OMW::Engine::createWindow(Settings::Manager& settings) if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); + realizeOperations->add(mSelectDepthFormatOperation); + realizeOperations->add(mSelectColorFormatOperation); + + if (Stereo::getStereo()) + { + realizeOperations->add(new InitializeStereoOperation()); + Stereo::setVertexBufferHint(); + } + mViewer->realize(); mViewer->getEventQueue()->getCurrentEventState()->setWindowRectangle(0, 0, graphicsWindow->getTraits()->width, graphicsWindow->getTraits()->height); @@ -674,11 +704,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setStateManager ( std::make_unique (mCfgMgr.getUserDataPath() / "saves", mContentFiles)); - createWindow(settings); + mStereoManager = std::make_unique(mViewer); - osg::ref_ptr rootNode (new osg::Group); + osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); + createWindow(settings); + mVFS = std::make_unique(mFSStrict); VFS::registerArchives(mVFS.get(), mFileCollections, mArchives, true); @@ -757,22 +789,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr exts = osg::GLExtensions::Get(0, false); bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); - bool enableReverseZ = false; - - if (Settings::Manager::getBool("reverse z", "Camera")) - { - if (exts && exts->isClipControlSupported) - { - enableReverseZ = true; - Log(Debug::Info) << "Using reverse-z depth buffer"; - } - else - Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; - } - else - Log(Debug::Info) << "Using standard depth buffer"; - - SceneUtil::AutoDepth::setReversed(enableReverseZ); #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 @@ -784,6 +800,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) osg::ref_ptr guiRoot = new osg::Group; guiRoot->setName("GUI Root"); guiRoot->setNodeMask(MWRender::Mask_GUI); + mStereoManager->disableStereoForNode(guiRoot); rootNode->addChild(guiRoot); auto windowMgr = std::make_unique(mWindow, mViewer, guiRoot, mResourceSystem.get(), mWorkQueue.get(), @@ -794,7 +811,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setWindowManager (std::move(windowMgr)); auto inputMgr = std::make_unique(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); - mEnvironment.setInputManager (std::move(inputMgr)); + mEnvironment.setInputManager(std::move(inputMgr)); // Create sound system mEnvironment.setSoundManager (std::make_unique(mVFS.get(), mUseSound)); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 5fe032d27e..d30bd33e80 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -39,6 +39,11 @@ namespace MWLua class LuaManager; } +namespace Stereo +{ + class Manager; +} + namespace Files { struct ConfigurationManager; @@ -49,6 +54,16 @@ namespace osgViewer class ScreenCaptureHandler; } +namespace SceneUtil +{ + class SelectDepthFormatOperation; + + namespace Color + { + class SelectColorFormatOperation; + } +} + struct SDL_Window; namespace OMW @@ -69,9 +84,14 @@ namespace OMW osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; osg::ref_ptr mScreenCaptureOperation; + osg::ref_ptr mSelectDepthFormatOperation; + osg::ref_ptr mSelectColorFormatOperation; std::string mCellName; std::vector mContentFiles; std::vector mGroundcoverFiles; + + std::unique_ptr mStereoManager; + bool mSkipMenu; bool mUseSound; bool mCompileAll; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 04f21906a0..f47ae24e87 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -367,6 +367,12 @@ namespace MWBase virtual void forceLootMode(const MWWorld::Ptr& ptr) = 0; virtual void asyncPrepareSaveMap() = 0; + + /// Sets the cull masks for all applicable views + virtual void setCullMask(uint32_t mask) = 0; + + /// Same as viewer->getCamera()->getCullMask(), provided for consistency. + virtual uint32_t getCullMask() = 0; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 428b925920..b15ff1d178 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -748,7 +748,11 @@ namespace MWGui // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) +#ifdef USE_OPENXR + : WindowPinnableBase("openmw_map_window_vr.layout") +#else : WindowPinnableBase("openmw_map_window.layout") +#endif , LocalMapBase(customMarkers, localMapRender) , NoDrop(drag, mMainWidget) , mGlobalMap(nullptr) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 23c16082b4..94b80d3faf 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -572,17 +572,17 @@ namespace MWGui void WindowManager::enableScene(bool enable) { unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile; - if (!enable && mViewer->getCamera()->getCullMask() != disablemask) + if (!enable && getCullMask() != disablemask) { mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - mOldCullMask = mViewer->getCamera()->getCullMask(); + mOldCullMask = getCullMask(); mViewer->getUpdateVisitor()->setTraversalMask(disablemask); - mViewer->getCamera()->setCullMask(disablemask); + setCullMask(disablemask); } - else if (enable && mViewer->getCamera()->getCullMask() == disablemask) + else if (enable && getCullMask() == disablemask) { mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); - mViewer->getCamera()->setCullMask(mOldCullMask); + setCullMask(mOldCullMask); } } @@ -1226,6 +1226,21 @@ namespace MWGui updateVisible(); } + void WindowManager::setCullMask(uint32_t mask) + { + mViewer->getCamera()->setCullMask(mask); + + // We could check whether stereo is enabled here, but these methods are + // trivial and have no effect in mono or multiview so just call them regardless. + mViewer->getCamera()->setCullMaskLeft(mask); + mViewer->getCamera()->setCullMaskRight(mask); + } + + uint32_t WindowManager::getCullMask() + { + return mViewer->getCamera()->getCullMask(); + } + void WindowManager::popGuiMode(bool noSound) { if (mDragAndDrop && mDragAndDrop->mIsOnDragAndDrop) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 90e8d1b0d7..8ab50e32ed 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -582,6 +582,9 @@ namespace MWGui void handleScheduledMessageBoxes(); void pushGuiMode(GuiMode mode, const MWWorld::Ptr& arg, bool force); + + void setCullMask(uint32_t mask) override; + uint32_t getCullMask() override; }; } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 7cca787580..f2bbe10460 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -41,9 +42,10 @@ namespace MWRender class DrawOnceCallback : public SceneUtil::NodeCallback { public: - DrawOnceCallback () + DrawOnceCallback(osg::Node* subgraph) : mRendered(false) , mLastRenderedFrame(0) + , mSubgraph(subgraph) { } @@ -61,6 +63,9 @@ namespace MWRender nv->setFrameStamp(fs); + // Update keyframe controllers in the scene graph first... + // RTTNode does not continue update traversal, so manually continue the update traversal since we need it. + mSubgraph->accept(*nv); traverse(node, nv); nv->setFrameStamp(previousFramestamp); @@ -84,6 +89,7 @@ namespace MWRender private: bool mRendered; unsigned int mLastRenderedFrame; + osg::ref_ptr mSubgraph; }; @@ -138,6 +144,96 @@ namespace MWRender } }; + class CharacterPreviewRTTNode : public SceneUtil::RTTNode + { + static constexpr float fovYDegrees = 12.3f; + static constexpr float znear = 0.1f; + static constexpr float zfar = 10000.f; + + public: + CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) + : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, StereoAwareness::Unaware_MultiViewShaders) + , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) + { + if (SceneUtil::AutoDepth::isReversed()) + mPerspectiveMatrix = static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar)); + else + mPerspectiveMatrix = osg::Matrixf::perspective(fovYDegrees, mAspectRatio, znear, zfar); + mGroup->getOrCreateStateSet()->addUniform(new osg::Uniform("projectionMatrix", mPerspectiveMatrix)); + mViewMatrix = osg::Matrixf::identity(); + setColorBufferInternalFormat(GL_RGBA); + } + + void setDefaults(osg::Camera* camera) override + { + + // hints that the camera is not relative to the master camera + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setViewport(0, 0, width(), height()); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setName("CharacterPreview"); + camera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); + camera->setCullMask(~(Mask_UpdateVisitor)); + SceneUtil::setCameraClearDepth(camera); + + // hints that the camera is not relative to the master camera + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setProjectionMatrixAsPerspective(fovYDegrees, mAspectRatio, znear, zfar); + camera->setViewport(0, 0, width(), height()); + camera->setRenderOrder(osg::Camera::PRE_RENDER); +#ifdef OSG_HAS_MULTIVIEW + if (shouldDoTextureArray()) + { + auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2); + auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2); + viewUniform->setElement(0, osg::Matrix::identity()); + viewUniform->setElement(1, osg::Matrix::identity()); + projUniform->setElement(0, mPerspectiveMatrix); + projUniform->setElement(1, mPerspectiveMatrix); + mGroup->getOrCreateStateSet()->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + mGroup->getOrCreateStateSet()->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } +#endif + + camera->setNodeMask(Mask_RenderToTexture); + camera->addChild(mGroup); + }; + + void apply(osg::Camera* camera) override + { + if(mCameraStateset) + camera->setStateSet(mCameraStateset); + camera->setViewMatrix(mViewMatrix); + }; + + void addChild(osg::Node* node) + { + mGroup->addChild(node); + } + + void setCameraStateset(osg::StateSet* stateset) + { + mCameraStateset = stateset; + } + + void setViewMatrix(const osg::Matrixf& viewMatrix) + { + mViewMatrix = viewMatrix; + } + + osg::ref_ptr mGroup = new osg::Group; + osg::Matrixf mPerspectiveMatrix; + osg::Matrixf mViewMatrix; + osg::ref_ptr mCameraStateset; + float mAspectRatio; + }; + CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, const MWWorld::Ptr& character, int sizeX, int sizeY, const osg::Vec3f& position, const osg::Vec3f& lookAt) : mParent(parent) @@ -149,31 +245,11 @@ namespace MWRender , mSizeX(sizeX) , mSizeY(sizeY) { - mTexture = new osg::Texture2D; - mTexture->setTextureSize(sizeX, sizeY); - mTexture->setInternalFormat(GL_RGBA); - mTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - mTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mTextureStateSet = new osg::StateSet; mTextureStateSet->setAttribute(new osg::BlendFunc(osg::BlendFunc::ONE, osg::BlendFunc::ONE_MINUS_SRC_ALPHA)); - mCamera = new osg::Camera; - // hints that the camera is not relative to the master camera - mCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); - mCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - mCamera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 0.f)); - mCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - mCamera->setViewport(0, 0, sizeX, sizeY); - mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); - mCamera->setName("CharacterPreview"); - mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - mCamera->setCullMask(~(Mask_UpdateVisitor)); - - mCamera->setNodeMask(Mask_RenderToTexture); - - SceneUtil::setCameraClearDepth(mCamera); + mRTTNode = new CharacterPreviewRTTNode(sizeX, sizeY); + mRTTNode->setNodeMask(Mask_RenderToTexture); bool ffp = mResourceSystem->getSceneManager()->getLightingMethod() == SceneUtil::LightingMethod::FFP; @@ -191,14 +267,6 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - const float fovYDegrees = 12.3f; - const float aspectRatio = static_cast(sizeX) / static_cast(sizeY); - const float znear = 0.1f; - const float zfar = 10000.f; - mCamera->setProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar); - osg::Matrixf projectionMatrix = SceneUtil::AutoDepth::isReversed() ? static_cast(SceneUtil::getReversedZProjectionMatrixAsPerspective(fovYDegrees, aspectRatio, znear, zfar)) : static_cast(mCamera->getProjectionMatrix()); - stateset->addUniform(new osg::Uniform("projectionMatrix", projectionMatrix)); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); // assign large value to effectively turn off fog @@ -266,23 +334,22 @@ namespace MWRender lightManager->addChild(lightSource); - mCamera->addChild(lightManager); + mRTTNode->addChild(lightManager); mNode = new osg::PositionAttitudeTransform; lightManager->addChild(mNode); - mDrawOnceCallback = new DrawOnceCallback; - mCamera->addUpdateCallback(mDrawOnceCallback); + mDrawOnceCallback = new DrawOnceCallback(mRTTNode->mGroup); + mRTTNode->addUpdateCallback(mDrawOnceCallback); - mParent->addChild(mCamera); + mParent->addChild(mRTTNode); mCharacter.mCell = nullptr; } CharacterPreview::~CharacterPreview () { - mCamera->removeChildren(0, mCamera->getNumChildren()); - mParent->removeChild(mCamera); + mParent->removeChild(mRTTNode); } int CharacterPreview::getTextureWidth() const @@ -308,7 +375,7 @@ namespace MWRender osg::ref_ptr CharacterPreview::getTexture() { - return mTexture; + return static_cast(mRTTNode->getColorTexture(nullptr)); } void CharacterPreview::rebuild() @@ -325,7 +392,7 @@ namespace MWRender void CharacterPreview::redraw() { - mCamera->setNodeMask(Mask_RenderToTexture); + mRTTNode->setNodeMask(Mask_RenderToTexture); mDrawOnceCallback->redrawNextFrame(); } @@ -346,7 +413,7 @@ namespace MWRender osg::ref_ptr stateset = new osg::StateSet; mViewport = new osg::Viewport(0, mSizeY-sizeY, std::min(mSizeX, sizeX), std::min(mSizeY, sizeY)); stateset->setAttributeAndModes(mViewport); - mCamera->setStateSet(stateset); + mRTTNode->setCameraStateset(stateset); redraw(); } @@ -433,10 +500,11 @@ namespace MWRender // Set the traversal number from the last draw, so that the frame switch used for RigGeometry double buffering works correctly visitor.setTraversalNumber(mDrawOnceCallback->getLastRenderedFrame()); - osg::Node::NodeMask nodeMask = mCamera->getNodeMask(); - mCamera->setNodeMask(~0u); - mCamera->accept(visitor); - mCamera->setNodeMask(nodeMask); + auto* camera = mRTTNode->getCamera(nullptr); + osg::Node::NodeMask nodeMask = camera->getNodeMask(); + camera->setNodeMask(~0u); + camera->accept(visitor); + camera->setNodeMask(nodeMask); if (intersector->containsIntersections()) { @@ -459,7 +527,8 @@ namespace MWRender mNode->setScale(scale); - mCamera->setViewMatrixAsLookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0,0,1)); + auto viewMatrix = osg::Matrixf::lookAt(mPosition * scale.z(), mLookAt * scale.z(), osg::Vec3f(0, 0, 1)); + mRTTNode->setViewMatrix(viewMatrix); } // -------------------------------------------------------------------------------------------------- @@ -492,7 +561,7 @@ namespace MWRender rebuild(); } - class UpdateCameraCallback : public SceneUtil::NodeCallback + class UpdateCameraCallback : public SceneUtil::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) @@ -502,10 +571,10 @@ namespace MWRender { } - void operator()(osg::Camera* cam, osg::NodeVisitor* nv) + void operator()(CharacterPreviewRTTNode* node, osg::NodeVisitor* nv) { // Update keyframe controllers in the scene graph first... - traverse(cam, nv); + traverse(node, nv); // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); @@ -514,7 +583,8 @@ namespace MWRender osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]); osg::Vec3 headOffset = worldMat.getTrans(); - cam->setViewMatrixAsLookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0,0,1)); + auto viewMatrix = osg::Matrixf::lookAt(headOffset + mPosOffset, headOffset + mLookAtOffset, osg::Vec3(0, 0, 1)); + node->setViewMatrix(viewMatrix); } private: @@ -531,13 +601,13 @@ namespace MWRender // attach camera to follow the head node if (mUpdateCameraCallback) - mCamera->removeUpdateCallback(mUpdateCameraCallback); + mRTTNode->removeUpdateCallback(mUpdateCameraCallback); const osg::Node* head = mAnimation->getNode("Bip01 Head"); if (head) { mUpdateCameraCallback = new UpdateCameraCallback(head, mPosition, mLookAt); - mCamera->addUpdateCallback(mUpdateCameraCallback); + mRTTNode->addUpdateCallback(mUpdateCameraCallback); } else Log(Debug::Error) << "Error: Bip01 Head node not found"; diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 0d7c1959c3..a8777d8548 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -26,6 +26,7 @@ namespace MWRender class NpcAnimation; class DrawOnceCallback; + class CharacterPreviewRTTNode; class CharacterPreview { @@ -56,10 +57,9 @@ namespace MWRender osg::ref_ptr mParent; Resource::ResourceSystem* mResourceSystem; - osg::ref_ptr mTexture; osg::ref_ptr mTextureStateSet; - osg::ref_ptr mCamera; osg::ref_ptr mDrawOnceCallback; + osg::ref_ptr mRTTNode; osg::Vec3f mPosition; osg::Vec3f mLookAt; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 98eebf0b75..2fd3c8b7f7 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -15,12 +15,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -34,36 +36,6 @@ namespace { - - class CameraLocalUpdateCallback : public SceneUtil::NodeCallback - { - public: - CameraLocalUpdateCallback(MWRender::LocalMap* parent) - : mRendered(false) - , mParent(parent) - { - } - - void operator()(osg::Camera* node, osg::NodeVisitor*) - { - if (mRendered) - node->setNodeMask(0); - - if (!mRendered) - { - mRendered = true; - mParent->markForRemoval(node); - } - - // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, - // so it has been updated already. - } - - private: - bool mRendered; - MWRender::LocalMap* mParent; - }; - float square(float val) { return val*val; @@ -82,6 +54,28 @@ namespace namespace MWRender { + class LocalMapRenderToTexture: public SceneUtil::RTTNode + { + public: + LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, + float x, float y, const osg::Vec3d& upVector, float zmin, float zmax); + + void setDefaults(osg::Camera* camera) override; + + bool isActive() { return mActive; } + void setIsActive(bool active) { mActive = active; } + + osg::Node* mSceneRoot; + osg::Matrix mProjectionMatrix; + osg::Matrix mViewMatrix; + bool mActive; + }; + + class CameraLocalUpdateCallback : public SceneUtil::NodeCallback + { + public: + void operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv); + }; LocalMap::LocalMap(osg::Group* root) : mRoot(root) @@ -104,10 +98,8 @@ LocalMap::LocalMap(osg::Group* root) LocalMap::~LocalMap() { - for (auto& camera : mActiveCameras) - removeCamera(camera); - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); + for (auto& rtt : mLocalMapRTTs) + mRoot->removeChild(rtt); } const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) @@ -173,93 +165,14 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) } } -osg::ref_ptr LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) -{ - osg::ref_ptr camera (new osg::Camera); - - if (SceneUtil::AutoDepth::isReversed()) - camera->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10)); - else - camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); - - camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); - camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); - camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); - camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); - camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); - camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - camera->setRenderOrder(osg::Camera::PRE_RENDER); - - camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); - camera->setNodeMask(Mask_RenderToTexture); - - // Disable small feature culling, it's not going to be reliable for this camera - osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING|osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); - camera->setCullingMode(cullingMode); - - SceneUtil::setCameraClearDepth(camera); - - osg::ref_ptr stateset = new osg::StateSet; - stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); - stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(camera->getProjectionMatrix())), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - // assign large value to effectively turn off fog - // shaders don't respect glDisable(GL_FOG) - osg::ref_ptr fog (new osg::Fog); - fog->setStart(10000000); - fog->setEnd(10000000); - stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr lightmodel = new osg::LightModel; - lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); - stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::ref_ptr light = new osg::Light; - light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); - light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); - light->setAmbient(osg::Vec4(0,0,0,1)); - light->setSpecular(osg::Vec4(0,0,0,0)); - light->setLightNum(0); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - - osg::ref_ptr lightSource = new osg::LightSource; - lightSource->setLight(light); - - lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); - - // override sun for local map - SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot.get()), light, stateset); - - camera->addChild(lightSource); - camera->setStateSet(stateset); - camera->setViewport(0, 0, mMapResolution, mMapResolution); - camera->setUpdateCallback(new CameraLocalUpdateCallback(this)); - - return camera; -} - -void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int y) +void LocalMap::setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax) { - osg::ref_ptr texture (new osg::Texture2D); - texture->setTextureSize(mMapResolution, mMapResolution); - texture->setInternalFormat(GL_RGB); - texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + mLocalMapRTTs.emplace_back(new LocalMapRenderToTexture(mSceneRoot, mMapResolution, mMapWorldSize, left, top, upVector, zmin, zmax)); - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); + mRoot->addChild(mLocalMapRTTs.back()); - camera->addChild(mSceneRoot); - mRoot->addChild(camera); - mActiveCameras.push_back(camera); - - MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(x, y)] : mExteriorSegments[std::make_pair(x, y)]; - segment.mMapTexture = texture; + MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(segment_x, segment_y)] : mExteriorSegments[std::make_pair(segment_x, segment_y)]; + segment.mMapTexture = static_cast(mLocalMapRTTs.back()->getColorTexture(nullptr)); } void LocalMap::requestMap(const MWWorld::CellStore* cell) @@ -321,33 +234,19 @@ osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) return found->second.mFogOfWarTexture; } -void LocalMap::removeCamera(osg::Camera *cam) -{ - cam->removeChildren(0, cam->getNumChildren()); - mRoot->removeChild(cam); -} - -void LocalMap::markForRemoval(osg::Camera *cam) +void LocalMap::cleanupCameras() { - CameraVector::iterator found = std::find(mActiveCameras.begin(), mActiveCameras.end(), cam); - if (found == mActiveCameras.end()) + auto it = mLocalMapRTTs.begin(); + while (it != mLocalMapRTTs.end()) { - Log(Debug::Error) << "Error: trying to remove an inactive camera"; - return; + if (!(*it)->isActive()) + { + mRoot->removeChild(*it); + it = mLocalMapRTTs.erase(it); + } + else + it++; } - mActiveCameras.erase(found); - mCamerasPendingRemoval.push_back(cam); -} - -void LocalMap::cleanupCameras() -{ - if (mCamerasPendingRemoval.empty()) - return; - - for (auto& camera : mCamerasPendingRemoval) - removeCamera(camera); - - mCamerasPendingRemoval.clear(); } void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) @@ -361,9 +260,9 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) float zmin = bound.center().z() - bound.radius(); float zmax = bound.center().z() + bound.radius(); - osg::ref_ptr camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, - osg::Vec3d(0,1,0), zmin, zmax); - setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); + setupRenderToTexture(cell->getCell()->getGridX(), cell->getCell()->getGridY(), + x * mMapWorldSize + mMapWorldSize / 2.f, y * mMapWorldSize + mMapWorldSize / 2.f, + osg::Vec3d(0, 1, 0), zmin, zmax); MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (!segment.mFogOfWarImage) @@ -501,11 +400,8 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) osg::Vec2f pos = osg::Vec2f(rotatedCenter.x(), rotatedCenter.y()) + center; - osg::ref_ptr camera = createOrthographicCamera(pos.x(), pos.y(), - mMapWorldSize, mMapWorldSize, - osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); - - setupRenderToTexture(camera, x, y); + setupRenderToTexture(x, y, pos.x(), pos.y(), + osg::Vec3f(north.x(), north.y(), 0.f), zMin, zMax); auto coords = std::make_pair(x,y); MapSegment& segment = mInteriorSegments[coords]; @@ -682,6 +578,7 @@ void LocalMap::MapSegment::createFogOfWarTexture() mFogOfWarTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mFogOfWarTexture->setUnRefImageDataAfterApply(false); + mFogOfWarTexture->setImage(mFogOfWarImage); } void LocalMap::MapSegment::initFogOfWar() @@ -697,7 +594,6 @@ void LocalMap::MapSegment::initFogOfWar() memcpy(mFogOfWarImage->data(), &data[0], data.size()*4); createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); } void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) @@ -730,7 +626,6 @@ void LocalMap::MapSegment::loadFogOfWar(const ESM::FogTexture &esm) mFogOfWarImage->dirty(); createFogOfWarTexture(); - mFogOfWarTexture->setImage(mFogOfWarImage); mHasFogState = true; } @@ -762,4 +657,107 @@ void LocalMap::MapSegment::saveFogOfWar(ESM::FogTexture &fog) const fog.mImageData = std::vector(data.begin(), data.end()); } +LocalMapRenderToTexture::LocalMapRenderToTexture(osg::Node* sceneRoot, int res, int mapWorldSize, float x, float y, const osg::Vec3d& upVector, float zmin, float zmax) + : RTTNode(res, res, 0, false, 0, StereoAwareness::Unaware_MultiViewShaders) + , mSceneRoot(sceneRoot) + , mActive(true) +{ + if (SceneUtil::AutoDepth::isReversed()) + mProjectionMatrix = SceneUtil::getReversedZProjectionMatrixAsOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + else + mProjectionMatrix.makeOrtho(-mapWorldSize / 2, mapWorldSize / 2, -mapWorldSize / 2, mapWorldSize / 2, 5, (zmax - zmin) + 10); + + mViewMatrix.makeLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); + + setUpdateCallback(new CameraLocalUpdateCallback); +} + +void LocalMapRenderToTexture::setDefaults(osg::Camera* camera) +{ + // Disable small feature culling, it's not going to be reliable for this camera + osg::Camera::CullingMode cullingMode = (osg::Camera::DEFAULT_CULLING | osg::Camera::FAR_PLANE_CULLING) & ~(osg::Camera::SMALL_FEATURE_CULLING); + camera->setCullingMode(cullingMode); + + SceneUtil::setCameraClearDepth(camera); + camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); + camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + + camera->setCullMask(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskLeft(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setCullMaskRight(Mask_Scene | Mask_SimpleWater | Mask_Terrain | Mask_Object | Mask_Static); + camera->setNodeMask(Mask_RenderToTexture); + camera->setProjectionMatrix(mProjectionMatrix); + camera->setViewMatrix(mViewMatrix); + + auto* stateset = camera->getOrCreateStateSet(); + + stateset->setAttribute(new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL), osg::StateAttribute::OVERRIDE); + stateset->addUniform(new osg::Uniform("projectionMatrix", static_cast(mProjectionMatrix)), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (Stereo::getMultiview()) + { + auto* viewUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2); + auto* projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2); + viewUniform->setElement(0, osg::Matrix::identity()); + viewUniform->setElement(1, osg::Matrix::identity()); + projUniform->setElement(0, mProjectionMatrix); + projUniform->setElement(1, mProjectionMatrix); + stateset->addUniform(viewUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + stateset->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } + + // assign large value to effectively turn off fog + // shaders don't respect glDisable(GL_FOG) + osg::ref_ptr fog(new osg::Fog); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr lightmodel = new osg::LightModel; + lightmodel->setAmbientIntensity(osg::Vec4(0.3f, 0.3f, 0.3f, 1.f)); + stateset->setAttributeAndModes(lightmodel, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + osg::ref_ptr light = new osg::Light; + light->setPosition(osg::Vec4(-0.3f, -0.3f, 0.7f, 0.f)); + light->setDiffuse(osg::Vec4(0.7f, 0.7f, 0.7f, 1.f)); + light->setAmbient(osg::Vec4(0, 0, 0, 1)); + light->setSpecular(osg::Vec4(0, 0, 0, 0)); + light->setLightNum(0); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + + osg::ref_ptr lightSource = new osg::LightSource; + lightSource->setLight(light); + + lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + + // override sun for local map + SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); + + camera->addChild(lightSource); + camera->addChild(mSceneRoot); +} + +void CameraLocalUpdateCallback::operator()(LocalMapRenderToTexture* node, osg::NodeVisitor* nv) +{ + if (!node->isActive()) + node->setNodeMask(0); + + if (node->isActive()) + { + node->setIsActive(false); + } + + // Rtt-nodes do not forward update traversal to their cameras so we can traverse safely. + // Traverse in case there are nested callbacks. + traverse(node, nv); +} + } diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index f9ccd5a011..911671aee2 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -30,6 +30,8 @@ namespace osg namespace MWRender { + class LocalMapRenderToTexture; + /// /// \brief Local map rendering /// @@ -58,13 +60,6 @@ namespace MWRender osg::ref_ptr getFogOfWarTexture (int x, int y); - void removeCamera(osg::Camera* cam); - - /** - * Indicates a camera has been queued for rendering and can be cleaned up in the next frame. For internal use only. - */ - void markForRemoval(osg::Camera* cam); - /** * Removes cameras that have already been rendered. Should be called every frame to ensure that * we do not render the same map more than once. Note, this cleanup is difficult to implement in an @@ -104,11 +99,8 @@ namespace MWRender osg::ref_ptr mRoot; osg::ref_ptr mSceneRoot; - typedef std::vector< osg::ref_ptr > CameraVector; - - CameraVector mActiveCameras; - - CameraVector mCamerasPendingRemoval; + typedef std::vector< osg::ref_ptr > RTTVector; + RTTVector mLocalMapRTTs; typedef std::set > Grid; Grid mCurrentGrid; @@ -152,8 +144,7 @@ namespace MWRender void requestExteriorMap(const MWWorld::CellStore* cell); void requestInteriorMap(const MWWorld::CellStore* cell); - osg::ref_ptr createOrthographicCamera(float left, float top, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax); - void setupRenderToTexture(osg::ref_ptr camera, int x, int y); + void setupRenderToTexture(int segment_x, int segment_y, float left, float top, const osg::Vec3d& upVector, float zmin, float zmax); bool mInterior; osg::BoundingBox mBounds; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index afdec4f1b5..9fbc5de528 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -6,15 +6,20 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include +#include + #include "vismask.hpp" namespace @@ -38,8 +43,8 @@ namespace class CullCallback : public SceneUtil::NodeCallback { public: - CullCallback() - : mLastFrameNumber(0) + CullCallback(MWRender::PostProcessor* pp) + : mPostProcessor(pp) { } @@ -47,36 +52,21 @@ namespace { osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - unsigned int frame = cv->getTraversalNumber(); - if (frame != mLastFrameNumber) + if (!mPostProcessor->getMsaaFbo()) { - mLastFrameNumber = frame; - - MWRender::PostProcessor* postProcessor = dynamic_cast(cv->getCurrentCamera()->getUserData()); - - if (!postProcessor) - { - Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed"; - traverse(node, cv); - return; - } - - if (!postProcessor->getMsaaFbo()) - { - renderStage->setFrameBufferObject(postProcessor->getFbo()); - } - else - { - renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo()); - renderStage->setFrameBufferObject(postProcessor->getMsaaFbo()); - } + renderStage->setFrameBufferObject(mPostProcessor->getFbo()); + } + else + { + renderStage->setMultisampleResolveFramebufferObject(mPostProcessor->getFbo()); + renderStage->setFrameBufferObject(mPostProcessor->getMsaaFbo()); } traverse(node, cv); } private: - unsigned int mLastFrameNumber; + MWRender::PostProcessor* mPostProcessor; }; struct ResizedCallback : osg::GraphicsContext::ResizedCallback @@ -157,15 +147,13 @@ namespace MWRender PostProcessor::PostProcessor(osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) - , mDepthFormat(GL_DEPTH24_STENCIL8_EXT) { bool softParticles = Settings::Manager::getBool("soft particles", "Shaders"); - if (!SceneUtil::AutoDepth::isReversed() && !softParticles) + if (!SceneUtil::AutoDepth::isReversed() && !softParticles && !Stereo::getStereo()) return; osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); - unsigned int contextID = gc->getState()->getContextID(); osg::GLExtensions* ext = gc->getState()->get(); constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; @@ -184,24 +172,19 @@ namespace MWRender if (SceneUtil::AutoDepth::isReversed()) { - if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) - mDepthFormat = GL_DEPTH32F_STENCIL8; - else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) - mDepthFormat = GL_DEPTH32F_STENCIL8_NV; - else + if(SceneUtil::AutoDepth::depthSourceType() != GL_FLOAT_32_UNSIGNED_INT_24_8_REV) { // TODO: Once we have post-processing implemented we want to skip this return and continue with setup. // Rendering to a FBO to fullscreen geometry has overhead (especially when MSAA is enabled) and there are no // benefits if no floating point depth formats are supported. - Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; - - if (!softParticles) + if (!softParticles && !Stereo::getStereo()) return; } } - int width = viewer->getCamera()->getViewport()->width(); - int height = viewer->getCamera()->getViewport()->height(); + auto* traits = gc->getTraits(); + int width = traits->width; + int height = traits->height; createTexturesAndCamera(width, height); resize(width, height); @@ -210,15 +193,17 @@ namespace MWRender mRootNode->addChild(rootNode); mViewer->setSceneData(mRootNode); - // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate - // RTT camera this would not be needed. - mViewer->getCamera()->addCullCallback(new CullCallback); - mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); - mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); + if (!Stereo::getStereo()) + { + // We need to manually set the FBO and resolve FBO during the cull callback. If we were using a separate + // RTT camera this would not be needed. + mViewer->getCamera()->addCullCallback(new CullCallback(this)); + mViewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + mViewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, mSceneTex); mViewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mDepthTex); + } mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); - mViewer->getCamera()->setUserData(this); } void PostProcessor::resize(int width, int height) @@ -260,15 +245,89 @@ namespace MWRender mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); + + if (Stereo::getStereo()) + Stereo::Manager::instance().screenResolutionChanged(); } + class HUDCameraStatesetUpdater final : public SceneUtil::StateSetUpdater + { + public: + public: + HUDCameraStatesetUpdater(osg::ref_ptr HUDCamera, osg::ref_ptr program, osg::ref_ptr sceneTex) + : mHUDCamera(HUDCamera) + , mProgram(program) + , mSceneTex(sceneTex) + { + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); + stateset->setAttributeAndModes(mProgram, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("sceneTex", 0)); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + + if (osg::DisplaySettings::instance()->getStereo()) + { + stateset->setAttribute(new osg::Viewport); + stateset->addUniform(new osg::Uniform("viewportIndex", 0)); + } + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + if (Stereo::getMultiview()) + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->multiviewColorBuffer(), osg::StateAttribute::ON); + } + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(0), osg::StateAttribute::ON); + + auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); + auto fullViewport = mHUDCamera->getViewport(); + viewport->setViewport( + 0, + 0, + fullViewport->width() / 2, + fullViewport->height() + ); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* cv) override + { + auto& multiviewFbo = Stereo::Manager::instance().multiviewFramebuffer(); + stateset->setTextureAttributeAndModes(0, multiviewFbo->layerColorBuffer(1), osg::StateAttribute::ON); + + auto viewport = static_cast(stateset->getAttribute(osg::StateAttribute::VIEWPORT)); + auto fullViewport = mHUDCamera->getViewport(); + viewport->setViewport( + fullViewport->width() / 2, + 0, + fullViewport->width() / 2, + fullViewport->height() + ); + } + + private: + osg::ref_ptr mHUDCamera; + osg::ref_ptr mProgram; + osg::ref_ptr mSceneTex; + }; + void PostProcessor::createTexturesAndCamera(int width, int height) { mDepthTex = new osg::Texture2D; mDepthTex->setTextureSize(width, height); - mDepthTex->setSourceFormat(GL_DEPTH_STENCIL_EXT); - mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT_32_UNSIGNED_INT_24_8_REV : GL_UNSIGNED_INT_24_8_EXT); - mDepthTex->setInternalFormat(mDepthFormat); + mDepthTex->setSourceFormat(SceneUtil::AutoDepth::depthSourceFormat()); + mDepthTex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); + mDepthTex->setInternalFormat(SceneUtil::AutoDepth::depthInternalFormat()); mDepthTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mDepthTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); mDepthTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -283,9 +342,9 @@ namespace MWRender mSceneTex = new osg::Texture2D; mSceneTex->setTextureSize(width, height); - mSceneTex->setSourceFormat(GL_RGB); - mSceneTex->setSourceType(GL_UNSIGNED_BYTE); - mSceneTex->setInternalFormat(GL_RGB); + mSceneTex->setSourceFormat(SceneUtil::Color::colorSourceFormat()); + mSceneTex->setSourceType(SceneUtil::Color::colorSourceType()); + mSceneTex->setInternalFormat(SceneUtil::Color::colorInternalFormat()); mSceneTex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST); mSceneTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST); mSceneTex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); @@ -296,6 +355,7 @@ namespace MWRender mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); + mHUDCamera->setClearMask(0); mHUDCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); mHUDCamera->setViewport(0, 0, width, height); @@ -325,8 +385,28 @@ namespace MWRender } )GLSL"; + constexpr char fragSrcMultiview[] = R"GLSL( + #version 330 compatibility + + #extension GL_EXT_texture_array : require + + varying vec2 uv; + uniform sampler2DArray sceneTex; + + void main() + { + vec3 array_uv = vec3(uv.x * 2, uv.y, 0); + if(array_uv.x >= 1.0) + { + array_uv.x -= 1.0; + array_uv.z = 1; + } + gl_FragData[0] = texture2DArray(sceneTex, array_uv); + } + )GLSL"; + osg::ref_ptr vertShader = new osg::Shader(osg::Shader::VERTEX, vertSrc); - osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, fragSrc); + osg::ref_ptr fragShader = new osg::Shader(osg::Shader::FRAGMENT, Stereo::getMultiview() ? fragSrcMultiview : fragSrc); osg::ref_ptr program = new osg::Program; program->addShader(vertShader); @@ -334,13 +414,8 @@ namespace MWRender mHUDCamera->addChild(createFullScreenTri()); mHUDCamera->setNodeMask(Mask_RenderToTexture); - - auto* stateset = mHUDCamera->getOrCreateStateSet(); - stateset->setTextureAttributeAndModes(0, mSceneTex, osg::StateAttribute::ON); - stateset->setAttributeAndModes(program, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("sceneTex", 0)); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mHUDCamera->setCullCallback(new HUDCameraStatesetUpdater(mHUDCamera, program, mSceneTex)); } } + diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index f2ef238737..b1217b011b 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -7,11 +7,18 @@ #include #include +#include + namespace osgViewer { class Viewer; } +namespace Stereo +{ + class MultiviewFramebuffer; +} + namespace MWRender { class PostProcessor : public osg::Referenced @@ -23,7 +30,6 @@ namespace MWRender auto getFbo() { return mFbo; } auto getFirstPersonRBProxy() { return mFirstPersonDepthRBProxy; } - int getDepthFormat() { return mDepthFormat; } osg::ref_ptr getOpaqueDepthTex() { return mOpaqueDepthTex; } void resize(int width, int height); @@ -35,6 +41,7 @@ namespace MWRender osg::ref_ptr mRootNode; osg::ref_ptr mHUDCamera; + std::shared_ptr mMultiviewFbo; osg::ref_ptr mMsaaFbo; osg::ref_ptr mFbo; osg::ref_ptr mFirstPersonDepthRBProxy; @@ -42,9 +49,8 @@ namespace MWRender osg::ref_ptr mSceneTex; osg::ref_ptr mDepthTex; osg::ref_ptr mOpaqueDepthTex; - - int mDepthFormat; }; } -#endif \ No newline at end of file +#endif + diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 384580adb8..15d690534a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -21,6 +21,9 @@ #include +#include +#include + #include #include #include @@ -31,6 +34,7 @@ #include #include +#include #include #include #include @@ -45,6 +49,7 @@ #include +#include "../mwbase/windowmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" @@ -70,6 +75,54 @@ namespace MWRender { + class PerViewUniformStateUpdater final : public SceneUtil::StateSetUpdater + { + public: + public: + PerViewUniformStateUpdater() + { + } + + void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + } + + void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mProjectionMatrix); + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(Stereo::Manager::instance().computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); + } + + void setProjectionMatrix(const osg::Matrixf& projectionMatrix) + { + mProjectionMatrix = projectionMatrix; + } + + const osg::Matrixf& projectionMatrix() const + { + return mProjectionMatrix; + } + + private: + osg::Matrixf mProjectionMatrix; + }; + class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater { public: @@ -82,9 +135,8 @@ namespace MWRender { } - void setDefaults(osg::StateSet *stateset) override + void setDefaults(osg::StateSet* stateset) override { - stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); @@ -98,10 +150,6 @@ namespace MWRender void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override { - auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); - if (uProjectionMatrix) - uProjectionMatrix->set(mProjectionMatrix); - auto* uLinearFac = stateset->getUniform("linearFac"); if (uLinearFac) uLinearFac->set(mLinearFac); @@ -130,11 +178,6 @@ namespace MWRender } } - void setProjectionMatrix(const osg::Matrixf& projectionMatrix) - { - mProjectionMatrix = projectionMatrix; - } - void setLinearFac(float linearFac) { mLinearFac = linearFac; @@ -167,7 +210,6 @@ namespace MWRender private: - osg::Matrixf mProjectionMatrix; float mLinearFac; float mNear; float mFar; @@ -312,14 +354,16 @@ namespace MWRender auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(Settings::Manager::getString("lighting method", "Shaders")); resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - // Shadows and radial fog have problems with fixed-function mode + // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP - || reverseZ; + || reverseZ + || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders")); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders")); @@ -368,6 +412,9 @@ namespace MWRender globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; globalDefines["useGPUShader4"] = "0"; + globalDefines["GLSLVersion"] = "120"; + globalDefines["useOVR_multiview"] = "0"; + globalDefines["numViews"] = "1"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; @@ -453,16 +500,19 @@ namespace MWRender mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); + mPerViewUniformStateUpdater = new PerViewUniformStateUpdater(); + rootNode->addCullCallback(mPerViewUniformStateUpdater); + mPostProcessor = new PostProcessor(viewer, mRootNode); - resourceSystem->getSceneManager()->setDepthFormat(mPostProcessor->getDepthFormat()); + resourceSystem->getSceneManager()->setDepthFormat(SceneUtil::AutoDepth::depthInternalFormat()); resourceSystem->getSceneManager()->setOpaqueDepthTex(mPostProcessor->getOpaqueDepthTex()); - if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(mPostProcessor->getDepthFormat())) + if (reverseZ && !SceneUtil::isFloatingPointDepthFormat(SceneUtil::AutoDepth::depthInternalFormat())) Log(Debug::Warning) << "Floating point depth format not in use but reverse-z buffer is enabled, consider disabling it."; // water goes after terrain for correct waterculling order mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath)); - + mCamera.reset(new Camera(mViewer->getCamera())); mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get())); @@ -514,7 +564,8 @@ namespace MWRender mViewer->getCamera()->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); mViewer->getCamera()->setCullingMode(cullingMode); - mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); + auto mask = ~(Mask_UpdateVisitor | Mask_SimpleWater); + MWBase::Environment::get().getWindowManager()->setCullMask(mask); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models")); @@ -537,6 +588,7 @@ namespace MWRender SceneUtil::setCameraClearDepth(mViewer->getCamera()); + updateProjectionMatrix(); mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -753,14 +805,15 @@ namespace MWRender } else if (mode == Render_Scene) { - unsigned int mask = mViewer->getCamera()->getCullMask(); + auto* wm = MWBase::Environment::get().getWindowManager(); + unsigned int mask = wm->getCullMask(); bool enabled = !(mask&sToggleWorldMask); if (enabled) mask |= sToggleWorldMask; else mask &= ~sToggleWorldMask; mWater->showWorld(enabled); - mViewer->getCamera()->setCullMask(mask); + wm->setCullMask(mask); return enabled; } else if (mode == Render_NavMesh) @@ -1155,14 +1208,23 @@ namespace MWRender if (SceneUtil::AutoDepth::isReversed()) { mSharedUniformStateUpdater->setLinearFac(-mNearClip / (mViewDistance - mNearClip) - 1.f); - mSharedUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); + mPerViewUniformStateUpdater->setProjectionMatrix(SceneUtil::getReversedZProjectionMatrixAsPerspective(fov, aspect, mNearClip, mViewDistance)); } else - mSharedUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); + mPerViewUniformStateUpdater->setProjectionMatrix(mViewer->getCamera()->getProjectionMatrix()); mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setFar(mViewDistance); - mSharedUniformStateUpdater->setScreenRes(width, height); + if (Stereo::getStereo()) + { + auto res = Stereo::Manager::instance().eyeResolution(); + mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); + Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->projectionMatrix()); + } + else + { + mSharedUniformStateUpdater->setScreenRes(width, height); + } // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b25e675748..fe3b33b20e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -76,6 +76,7 @@ namespace MWRender { class StateUpdater; class SharedUniformStateUpdater; + class PerViewUniformStateUpdater; class EffectManager; class ScreenshotManager; @@ -97,7 +98,7 @@ namespace MWRender class RenderingManager : public MWRender::RenderingInterface { public: - RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, + RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& resourcePath, DetourNavigator::Navigator& navigator, const MWWorld::GroundcoverStore& groundcoverStore); ~RenderingManager(); @@ -255,6 +256,7 @@ namespace MWRender void updateRecastMesh(); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; @@ -292,6 +294,7 @@ namespace MWRender osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; + osg::ref_ptr mPerViewUniformStateUpdater; osg::Vec4f mAmbientColor; float mMinimumAmbientLuminance; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 90e6eec124..65b6a1bdbc 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include @@ -85,6 +87,12 @@ namespace MWRender { int screenW = renderInfo.getCurrentCamera()->getViewport()->width(); int screenH = renderInfo.getCurrentCamera()->getViewport()->height(); + if (Stereo::getStereo()) + { + auto eyeRes = Stereo::Manager::instance().eyeResolution(); + screenW = eyeRes.x(); + screenH = eyeRes.y(); + } double imageaspect = (double)mWidth/(double)mHeight; int leftPadding = std::max(0, static_cast(screenW - screenH * imageaspect) / 2); int topPadding = std::max(0, static_cast(screenH - screenW / imageaspect) / 2); @@ -94,13 +102,19 @@ namespace MWRender // Ensure we are reading from the resolved framebuffer and not the multisampled render buffer. Also ensure that the readbuffer is set correctly with rendeirng to FBO. // glReadPixel() cannot read from multisampled targets PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); - if (postProcessor && postProcessor->getFbo()) + if (ext) { - osg::GLExtensions* ext = osg::GLExtensions::Get(renderInfo.getContextID(), false); - if (ext) + osg::FrameBufferObject* fbo = nullptr; + if (Stereo::getStereo()) + fbo = Stereo::Manager::instance().multiviewFramebuffer()->layerFbo(0); + else if (postProcessor) + fbo = postProcessor->getFbo(); + + if (fbo) { - ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, postProcessor->getFbo()->getHandle(renderInfo.getContextID())); + ext->glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo->getHandle(renderInfo.getContextID())); renderInfo.getState()->glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); } } @@ -346,7 +360,7 @@ namespace MWRender rttCamera->addChild(mWater->getReflectionNode()); rttCamera->addChild(mWater->getRefractionNode()); - rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); + rttCamera->setCullMask(MWBase::Environment::get().getWindowManager()->getCullMask() & ~(Mask_GUI|Mask_FirstPerson)); rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a9c56bdf19..f0a591477a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -23,6 +23,7 @@ #include #include +#include #include @@ -332,8 +333,10 @@ namespace MWRender if (mSceneManager->getForceShaders()) { - auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", {}, osg::Shader::VERTEX); - auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", {}, osg::Shader::FRAGMENT); + Shader::ShaderManager::DefineMap defines = {}; + Stereo::Manager::instance().shaderStereoDefines(defines); + auto vertex = mSceneManager->getShaderManager().getShader("sky_vertex.glsl", defines, osg::Shader::VERTEX); + auto fragment = mSceneManager->getShaderManager().getShader("sky_fragment.glsl", defines, osg::Shader::FRAGMENT); auto program = mSceneManager->getShaderManager().getProgram(vertex, fragment); mEarlyRenderBinRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("pass", -1)); mEarlyRenderBinRoot->getOrCreateStateSet()->setAttributeAndModes(program, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7d9aca9b76..87f5eada20 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -33,6 +34,7 @@ #include #include +#include #include @@ -44,6 +46,8 @@ #include "../mwworld/cellstore.hpp" +#include "../mwbase/environment.hpp" + #include "vismask.hpp" #include "ripplesimulation.hpp" #include "renderbin.hpp" @@ -166,7 +170,7 @@ private: class InheritViewPointCallback : public SceneUtil::NodeCallback { public: - InheritViewPointCallback() {} + InheritViewPointCallback() {} void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { @@ -260,7 +264,7 @@ class Refraction : public SceneUtil::RTTNode { public: Refraction(uint32_t rttSize) - : RTTNode(rttSize, rttSize, 1, false) + : RTTNode(rttSize, rttSize, 0, false, 1, StereoAwareness::Aware) , mNodeMask(Refraction::sDefaultCullMask) { mClipCullNode = new ClipCullNode; @@ -335,7 +339,7 @@ class Reflection : public SceneUtil::RTTNode { public: Reflection(uint32_t rttSize, bool isInterior) - : RTTNode(rttSize, rttSize, 0, false) + : RTTNode(rttSize, rttSize, 0, false, 0, StereoAwareness::Aware) { setInterior(isInterior); mClipCullNode = new ClipCullNode; @@ -458,6 +462,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem mWaterGeom->setDrawCallback(new DepthClampCallback); mWaterGeom->setNodeMask(Mask_Water); mWaterGeom->setDataVariance(osg::Object::STATIC); + mWaterGeom->setName("Water Geometry"); mWaterNode = new osg::PositionAttitudeTransform; mWaterNode->setName("Water Root"); @@ -468,6 +473,7 @@ Water::Water(osg::Group *parent, osg::Group* sceneRoot, Resource::ResourceSystem osg::ref_ptr geom2 (osg::clone(mWaterGeom.get(), osg::CopyOp::DEEP_COPY_NODES)); createSimpleWaterStateSet(geom2, Fallback::Map::getFloat("Water_Map_Alpha")); geom2->setNodeMask(Mask_SimpleWater); + geom2->setName("Simple Water Geometry"); mWaterNode->addChild(geom2); mSceneRoot->addChild(mWaterNode); @@ -679,6 +685,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R const auto rippleDetail = std::clamp(Settings::Manager::getInt("rain ripple detail", "Water"), 0, 2); defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); + Stereo::Manager::instance().shaderStereoDefines(defineMap); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); osg::ref_ptr vertexShader(shaderMgr.getShader("water_vertex.glsl", defineMap, osg::Shader::VERTEX)); @@ -694,6 +701,7 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R normalMap->setMaxAnisotropy(16); normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); diff --git a/cmake/CheckOsgMultiview.cmake b/cmake/CheckOsgMultiview.cmake new file mode 100644 index 0000000000..1dcea31a32 --- /dev/null +++ b/cmake/CheckOsgMultiview.cmake @@ -0,0 +1,26 @@ +set(TMP_ROOT ${CMAKE_BINARY_DIR}/try-compile) +file(MAKE_DIRECTORY ${TMP_ROOT}) + +file(WRITE ${TMP_ROOT}/checkmultiview.cpp +" +#include +int main(void) +{ + (void)osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER; + return 0; +} +") + +message(STATUS "Checking if OSG supports multiview") + +try_compile(RESULT_VAR + ${TMP_ROOT}/temp + ${TMP_ROOT}/checkmultiview.cpp + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${OPENSCENEGRAPH_INCLUDE_DIRS}" + ) +set(HAVE_MULTIVIEW ${RESULT_VAR}) +if(HAVE_MULTIVIEW) + message(STATUS "Osg supports multiview") +else(HAVE_MULTIVIEW) + message(NOTICE "Osg does not support multiview, disabling use of GL_OVR_multiview") +endif(HAVE_MULTIVIEW) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 32c7c9535f..5ad88523b8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -61,7 +61,7 @@ add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt - screencapture depth + screencapture depth color ) add_component_dir (nif @@ -196,6 +196,10 @@ add_component_dir (misc compression osguservalues errorMarker color ) +add_component_dir (stereo + frustum multiview stereomanager types + ) + add_component_dir (debug debugging debuglog gldebug ) diff --git a/components/sceneutil/color.cpp b/components/sceneutil/color.cpp new file mode 100644 index 0000000000..825d7f65fd --- /dev/null +++ b/components/sceneutil/color.cpp @@ -0,0 +1,157 @@ +#include "color.hpp" + +#include +#include + +#include + +#include +#include + +namespace SceneUtil +{ + + bool isColorFormat(GLenum format) + { + static constexpr std::array formats = { + GL_RGB, + GL_RGB4, + GL_RGB5, + GL_RGB8, + GL_RGB8_SNORM, + GL_RGB10, + GL_RGB12, + GL_RGB16, + GL_RGB16_SNORM, + GL_SRGB, + GL_SRGB8, + GL_RGB16F, + GL_RGB32F, + GL_R11F_G11F_B10F, + GL_RGB9_E5, + GL_RGB8I, + GL_RGB8UI, + GL_RGB16I, + GL_RGB16UI, + GL_RGB32I, + GL_RGB32UI, + GL_RGBA, + GL_RGBA2, + GL_RGBA4, + GL_RGB5_A1, + GL_RGBA8, + GL_RGBA8_SNORM, + GL_RGB10_A2, + GL_RGB10_A2UI, + GL_RGBA12, + GL_RGBA16, + GL_RGBA16_SNORM, + GL_SRGB_ALPHA8, + GL_SRGB8_ALPHA8, + GL_RGBA16F, + GL_RGBA32F, + GL_RGBA8I, + GL_RGBA8UI, + GL_RGBA16I, + GL_RGBA16UI, + GL_RGBA32I, + GL_RGBA32UI, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + bool isFloatingPointColorFormat(GLenum format) + { + static constexpr std::array formats = { + GL_RGB16F, + GL_RGB32F, + GL_R11F_G11F_B10F, + GL_RGBA16F, + GL_RGBA32F, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + int getColorFormatChannelCount(GLenum format) + { + static constexpr std::array formats = { + GL_RGBA, + GL_RGBA2, + GL_RGBA4, + GL_RGB5_A1, + GL_RGBA8, + GL_RGBA8_SNORM, + GL_RGB10_A2, + GL_RGB10_A2UI, + GL_RGBA12, + GL_RGBA16, + GL_RGBA16_SNORM, + GL_SRGB_ALPHA8, + GL_SRGB8_ALPHA8, + GL_RGBA16F, + GL_RGBA32F, + GL_RGBA8I, + GL_RGBA8UI, + GL_RGBA16I, + GL_RGBA16UI, + GL_RGBA32I, + GL_RGBA32UI, + }; + if (std::find(formats.cbegin(), formats.cend(), format) != formats.cend()) + return 4; + return 3; + } + + void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) + { + if (getColorFormatChannelCount(internalFormat == 4)) + sourceFormat = GL_RGBA; + else + sourceFormat = GL_RGB; + + if (isFloatingPointColorFormat(internalFormat)) + sourceType = GL_FLOAT; + else + sourceType = GL_UNSIGNED_BYTE; + } + + namespace Color + { + GLenum sColorInternalFormat; + GLenum sColorSourceFormat; + GLenum sColorSourceType; + + GLenum colorInternalFormat() + { + return sColorInternalFormat; + } + + GLenum colorSourceFormat() + { + return sColorSourceFormat; + } + + GLenum colorSourceType() + { + return sColorSourceType; + } + + void SelectColorFormatOperation::operator()([[maybe_unused]] osg::GraphicsContext* graphicsContext) + { + sColorInternalFormat = GL_RGB; + + for (auto supportedFormat : mSupportedFormats) + { + if (isColorFormat(supportedFormat)) + { + sColorInternalFormat = supportedFormat; + break; + } + } + + getColorFormatSourceFormatAndType(sColorInternalFormat, sColorSourceFormat, sColorSourceType); + } + } +} diff --git a/components/sceneutil/color.hpp b/components/sceneutil/color.hpp new file mode 100644 index 0000000000..cd950c2749 --- /dev/null +++ b/components/sceneutil/color.hpp @@ -0,0 +1,204 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_COLOR_H +#define OPENMW_COMPONENTS_SCENEUTIL_COLOR_H + +#include + +namespace SceneUtil +{ + bool isColorFormat(GLenum format); + bool isFloatingPointColorFormat(GLenum format); + int getColorFormatChannelCount(GLenum format); + void getColorFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); + + namespace Color + { + GLenum colorSourceFormat(); + GLenum colorSourceType(); + GLenum colorInternalFormat(); + + class SelectColorFormatOperation final : public osg::GraphicsOperation + { + public: + SelectColorFormatOperation() : GraphicsOperation("SelectColorFormatOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override; + + void setSupportedFormats(const std::vector& supportedFormats) + { + mSupportedFormats = supportedFormats; + } + + private: + std::vector mSupportedFormats; + }; + } +} + +#ifndef GL_RGB +#define GL_RGB 0x1907 +#endif + +#ifndef GL_RGBA +#define GL_RGBA 0x1908 +#endif + +#ifndef GL_RGB4 +#define GL_RGB4 0x804F +#endif + +#ifndef GL_RGB5 +#define GL_RGB5 0x8050 +#endif + +#ifndef GL_RGB8 +#define GL_RGB8 0x8051 +#endif + +#ifndef GL_RGB8_SNORM +#define GL_RGB8_SNORM 0x8F96 +#endif + +#ifndef GL_RGB10 +#define GL_RGB10 0x8052 +#endif + +#ifndef GL_RGB12 +#define GL_RGB12 0x8053 +#endif + +#ifndef GL_RGB16 +#define GL_RGB16 0x8054 +#endif + +#ifndef GL_RGB16_SNORM +#define GL_RGB16_SNORM 0x8F9A +#endif + +#ifndef GL_RGBA2 +#define GL_RGBA2 0x8055 +#endif + +#ifndef GL_RGBA4 +#define GL_RGBA4 0x8056 +#endif + +#ifndef GL_RGB5_A1 +#define GL_RGB5_A1 0x8057 +#endif + +#ifndef GL_RGBA8 +#define GL_RGBA8 0x8058 +#endif + +#ifndef GL_RGBA8_SNORM +#define GL_RGBA8_SNORM 0x8F97 +#endif + +#ifndef GL_RGB10_A2 +#define GL_RGB10_A2 0x906F +#endif + +#ifndef GL_RGB10_A2UI +#define GL_RGB10_A2UI 0x906F +#endif + +#ifndef GL_RGBA12 +#define GL_RGBA12 0x805A +#endif + +#ifndef GL_RGBA16 +#define GL_RGBA16 0x805B +#endif + +#ifndef GL_RGBA16_SNORM +#define GL_RGBA16_SNORM 0x8F9B +#endif + +#ifndef GL_SRGB +#define GL_SRGB 0x8C40 +#endif + +#ifndef GL_SRGB8 +#define GL_SRGB8 0x8C41 +#endif + +#ifndef GL_SRGB_ALPHA8 +#define GL_SRGB_ALPHA8 0x8C42 +#endif + +#ifndef GL_SRGB8_ALPHA8 +#define GL_SRGB8_ALPHA8 0x8C43 +#endif + +#ifndef GL_RGB16F +#define GL_RGB16F 0x881B +#endif + +#ifndef GL_RGBA16F +#define GL_RGBA16F 0x881A +#endif + +#ifndef GL_RGB32F +#define GL_RGB32F 0x8815 +#endif + +#ifndef GL_RGBA32F +#define GL_RGBA32F 0x8814 +#endif + +#ifndef GL_R11F_G11F_B10F +#define GL_R11F_G11F_B10F 0x8C3A +#endif + + +#ifndef GL_RGB8I +#define GL_RGB8I 0x8D8F +#endif + +#ifndef GL_RGB8UI +#define GL_RGB8UI 0x8D7D +#endif + +#ifndef GL_RGB16I +#define GL_RGB16I 0x8D89 +#endif + +#ifndef GL_RGB16UI +#define GL_RGB16UI 0x8D77 +#endif + +#ifndef GL_RGB32I +#define GL_RGB32I 0x8D83 +#endif + +#ifndef GL_RGB32UI +#define GL_RGB32UI 0x8D71 +#endif + +#ifndef GL_RGBA8I +#define GL_RGBA8I 0x8D8E +#endif + +#ifndef GL_RGBA8UI +#define GL_RGBA8UI 0x8D7C +#endif + +#ifndef GL_RGBA16I +#define GL_RGBA16I 0x8D88 +#endif + +#ifndef GL_RGBA16UI +#define GL_RGBA16UI 0x8D76 +#endif + +#ifndef GL_RGBA32I +#define GL_RGBA32I 0x8D82 +#endif + +#ifndef GL_RGBA32UI +#define GL_RGBA32UI 0x8D70 +#endif + + +#endif diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index ee95f8c7ec..c52bdf2ed2 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -2,8 +2,7 @@ #include -#include - +#include #include namespace SceneUtil @@ -56,4 +55,144 @@ namespace SceneUtil return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); } -} \ No newline at end of file + + bool isDepthFormat(GLenum format) + { + constexpr std::array formats = { + GL_DEPTH_COMPONENT32F, + GL_DEPTH_COMPONENT32F_NV, + GL_DEPTH_COMPONENT16, + GL_DEPTH_COMPONENT24, + GL_DEPTH_COMPONENT32, + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + GL_DEPTH24_STENCIL8, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + bool isDepthStencilFormat(GLenum format) + { + constexpr std::array formats = { + GL_DEPTH32F_STENCIL8, + GL_DEPTH32F_STENCIL8_NV, + GL_DEPTH24_STENCIL8, + }; + + return std::find(formats.cbegin(), formats.cend(), format) != formats.cend(); + } + + void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType) + { + switch (internalFormat) + { + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32: + sourceType = GL_UNSIGNED_INT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + case GL_DEPTH_COMPONENT32F: + case GL_DEPTH_COMPONENT32F_NV: + sourceType = GL_FLOAT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + case GL_DEPTH24_STENCIL8: + sourceType = GL_UNSIGNED_INT_24_8_EXT; + sourceFormat = GL_DEPTH_STENCIL_EXT; + break; + case GL_DEPTH32F_STENCIL8: + case GL_DEPTH32F_STENCIL8_NV: + sourceType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + sourceFormat = GL_DEPTH_STENCIL_EXT; + break; + default: + sourceType = GL_UNSIGNED_INT; + sourceFormat = GL_DEPTH_COMPONENT; + break; + } + } + + GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat) + { + switch (internalFormat) + { + case GL_DEPTH24_STENCIL8: + return GL_DEPTH_COMPONENT24; + break; + case GL_DEPTH32F_STENCIL8: + return GL_DEPTH_COMPONENT32F; + break; + case GL_DEPTH32F_STENCIL8_NV: + return GL_DEPTH_COMPONENT32F_NV; + break; + default: + return internalFormat; + break; + } + } + + void SelectDepthFormatOperation::operator()(osg::GraphicsContext* graphicsContext) + { + bool enableReverseZ = false; + + if (Settings::Manager::getBool("reverse z", "Camera")) + { + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + if (exts && exts->isClipControlSupported) + { + enableReverseZ = true; + Log(Debug::Info) << "Using reverse-z depth buffer"; + } + else + Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer"; + } + else + Log(Debug::Info) << "Using standard depth buffer"; + + SceneUtil::AutoDepth::setReversed(enableReverseZ); + + constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: "; + std::vector requestedFormats; + unsigned int contextID = graphicsContext->getState()->getContextID(); + if (SceneUtil::AutoDepth::isReversed()) + { + if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) + { + requestedFormats.push_back(GL_DEPTH32F_STENCIL8); + } + else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) + { + requestedFormats.push_back(GL_DEPTH32F_STENCIL8_NV); + } + else + { + Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported."; + } + } + + requestedFormats.push_back(GL_DEPTH24_STENCIL8); + if (mSupportedFormats.empty()) + { + SceneUtil::AutoDepth::setDepthFormat(requestedFormats.front()); + } + else + { + for (auto requestedFormat : requestedFormats) + { + if (std::find(mSupportedFormats.cbegin(), mSupportedFormats.cend(), requestedFormat) != mSupportedFormats.cend()) + { + SceneUtil::AutoDepth::setDepthFormat(requestedFormat); + break; + } + } + } + } + + void AutoDepth::setDepthFormat(GLenum format) + { + sDepthInternalFormat = format; + getDepthFormatSourceFormatAndType(sDepthInternalFormat, sDepthSourceFormat, sDepthSourceType); + } +} diff --git a/components/sceneutil/depth.hpp b/components/sceneutil/depth.hpp index 1f4ba55297..67cf5bdb45 100644 --- a/components/sceneutil/depth.hpp +++ b/components/sceneutil/depth.hpp @@ -9,6 +9,26 @@ #define GL_DEPTH32F_STENCIL8_NV 0x8DAC #endif +#ifndef GL_DEPTH32F_STENCIL8 +#define GL_DEPTH32F_STENCIL8 0x8CAD +#endif + +#ifndef GL_FLOAT_32_UNSIGNED_INT_24_8_REV +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#endif + +#ifndef GL_DEPTH24_STENCIL8 +#define GL_DEPTH24_STENCIL8 0x88F0 +#endif + +#ifndef GL_DEPTH_STENCIL_EXT +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#endif + +#ifndef GL_UNSIGNED_INT_24_8_EXT +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#endif + namespace SceneUtil { // Sets camera clear depth to 0 if reversed depth buffer is in use, 1 otherwise. @@ -28,6 +48,18 @@ namespace SceneUtil // Returns true if the GL format is a floating point depth format. bool isFloatingPointDepthFormat(GLenum format); + // Returns true if the GL format is a depth format + bool isDepthFormat(GLenum format); + + // Returns true if the GL format is a depth+stencil format + bool isDepthStencilFormat(GLenum format); + + // Returns the corresponding source format and type for the given internal format + void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType); + + // Converts depth-stencil formats to their corresponding depth formats. + GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat); + // Brief wrapper around an osg::Depth that applies the reversed depth function when a reversed depth buffer is in use class AutoDepth : public osg::Depth { @@ -72,9 +104,29 @@ namespace SceneUtil return AutoDepth::sReversed; } + static void setDepthFormat(GLenum format); + + static GLenum depthInternalFormat() + { + return AutoDepth::sDepthInternalFormat; + } + + static GLenum depthSourceFormat() + { + return AutoDepth::sDepthSourceFormat; + } + + static GLenum depthSourceType() + { + return AutoDepth::sDepthSourceType; + } + private: static inline bool sReversed = false; + static inline GLenum sDepthSourceFormat = GL_DEPTH_COMPONENT; + static inline GLenum sDepthInternalFormat = GL_DEPTH_COMPONENT24; + static inline GLenum sDepthSourceType = GL_UNSIGNED_INT; osg::Depth::Function getReversedDepthFunction() const { @@ -116,6 +168,23 @@ namespace SceneUtil traverse(node); } }; + + class SelectDepthFormatOperation : public osg::GraphicsOperation + { + public: + SelectDepthFormatOperation() : GraphicsOperation("SelectDepthFormatOperation", false) + {} + + void operator()(osg::GraphicsContext* graphicsContext) override; + + void setSupportedFormats(const std::vector& supportedFormats) + { + mSupportedFormats = supportedFormats; + } + + private: + std::vector mSupportedFormats; + }; } #endif diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 931b316347..60dd613df5 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -633,6 +633,7 @@ void MWShadowTechnique::ShadowData::releaseGLObjects(osg::State* state) const // Frustum // MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, double maxZFar): + useCustomClipSpace(false), corners(8), faces(6), edges(12) @@ -652,19 +653,40 @@ MWShadowTechnique::Frustum::Frustum(osgUtil::CullVisitor* cv, double minZNear, d OSG_INFO<<"zNear = "< castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", { {"GLSLVersion", "120"} }, osg::Shader::VERTEX); osg::ref_ptr exts = osg::GLExtensions::Get(0, false); std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) @@ -910,7 +932,8 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, {"adjustCoverage", "1"}, - {"useGPUShader4", useGPUShader4} + {"useGPUShader4", useGPUShader4}, + {"GLSLVersion", "120"} }, osg::Shader::FRAGMENT)); } } @@ -931,6 +954,67 @@ MWShadowTechnique::ViewDependentData* MWShadowTechnique::getViewDependentData(os return vdd.release(); } +void SceneUtil::MWShadowTechnique::copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs) +{ + // Prepare for rendering shadows using the shadow map owned by rhs. + + // To achieve this i first copy all data that is not specific to this cv's camera and thus read-only, + // trusting openmw and osg won't overwrite that data before this frame is done rendering. + // This works due to the double buffering of CullVisitors by osg, but also requires that cull passes are serialized (relative to one another). + // Then initialize new copies of the data that will be written with view-specific data + // (the stateset and the texgens). + + lhs->_viewDependentShadowMap = rhs->_viewDependentShadowMap; + auto* stateset = lhs->getStateSet(cv.getTraversalNumber()); + stateset->clear(); + lhs->_lightDataList = rhs->_lightDataList; + lhs->_numValidShadows = rhs->_numValidShadows; + + ShadowDataList& sdl = lhs->getShadowDataList(); + ShadowDataList previous_sdl; + previous_sdl.swap(sdl); + for (const auto& rhs_sd : rhs->getShadowDataList()) + { + osg::ref_ptr lhs_sd; + + if (previous_sdl.empty()) + { + OSG_INFO << "Create new ShadowData" << std::endl; + lhs_sd = new ShadowData(lhs); + } + else + { + OSG_INFO << "Taking ShadowData from from of previous_sdl" << std::endl; + lhs_sd = previous_sdl.front(); + previous_sdl.erase(previous_sdl.begin()); + } + lhs_sd->_camera = rhs_sd->_camera; + lhs_sd->_textureUnit = rhs_sd->_textureUnit; + lhs_sd->_texture = rhs_sd->_texture; + sdl.push_back(lhs_sd); + } + + assignTexGenSettings(cv, lhs); + + if (lhs->_numValidShadows > 0) + { + prepareStateSetForRenderingShadow(*lhs, cv.getTraversalNumber()); + } +} + +void SceneUtil::MWShadowTechnique::setCustomFrustumCallback(CustomFrustumCallback* cfc) +{ + _customFrustumCallback = cfc; +} + +void SceneUtil::MWShadowTechnique::assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd) +{ + for (const auto& sd : vdd->getShadowDataList()) + { + assignTexGenSettings(&cv, sd->_camera, sd->_textureUnit, sd->_texgen); + } +} + void MWShadowTechnique::update(osg::NodeVisitor& nv) { OSG_INFO<<"MWShadowTechnique::update(osg::NodeVisitor& "<<&nv<<")"<getMaximumShadowMapDistance(),maxZFar); if (minZNear>maxZFar) minZNear = maxZFar*settings->getMinimumShadowMapNearFarRatio(); @@ -1047,6 +1132,36 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) cv.setNearFarRatio(minZNear / maxZFar); Frustum frustum(&cv, minZNear, maxZFar); + if (_customFrustumCallback) + { + OSG_INFO << "Calling custom frustum callback" << std::endl; + osgUtil::CullVisitor* sharedFrustumHint = nullptr; + _customClipSpace.init(); + _customFrustumCallback->operator()(cv, _customClipSpace, sharedFrustumHint); + frustum.setCustomClipSpace(_customClipSpace); + if (sharedFrustumHint) + { + // user hinted another view shares its frustum + std::lock_guard lock(_viewDependentDataMapMutex); + auto itr = _viewDependentDataMap.find(sharedFrustumHint); + if (itr != _viewDependentDataMap.end()) + { + OSG_INFO << "User provided a valid shared frustum hint, re-using previously generated shadow map" << std::endl; + + copyShadowMap(cv, vdd, itr->second); + + // return compute near far mode back to it's original settings + cv.setComputeNearFarMode(cachedNearFarMode); + return; + } + else + { + OSG_INFO << "User provided a shared frustum hint, but it was not valid." << std::endl; + } + } + } + + frustum.init(); if (_debugHud) { osg::ref_ptr vertexArray = new osg::Vec3Array(); @@ -1066,7 +1181,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) reducedNear = minZNear; reducedFar = maxZFar; } - + // return compute near far mode back to it's original settings cv.setComputeNearFarMode(cachedNearFarMode); @@ -1430,7 +1545,7 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) vdsmCallback->getProjectionMatrix()->set(camera->getProjectionMatrix()); } } - + // 4.4 compute main scene graph TexGen + uniform settings + setup state // assignTexGenSettings(&cv, camera.get(), textureUnit, sd->_texgen.get()); @@ -1459,6 +1574,8 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) } } + vdd->setNumValidShadows(numValidShadows); + if (numValidShadows>0) { prepareStateSetForRenderingShadow(*vdd, cv.getTraversalNumber()); @@ -1665,7 +1782,14 @@ osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustu OSG_INFO<<"computeLightViewFrustumPolytope()"< Vertices; Vertices corners; @@ -140,6 +145,18 @@ namespace SceneUtil { osg::Vec3d frustumCenterLine; }; + /** Custom frustum callback allowing the application to request shadow maps covering a + * different furstum than the camera normally would cover, by customizing the corners of the clip space. */ + struct CustomFrustumCallback : osg::Referenced + { + /** The callback operator. + * Output the custum frustum to the boundingBox variable. + * If sharedFrustumHint is set to a valid cull visitor, the shadow maps of that cull visitor will be re-used instead of recomputing new shadow maps + * Note that the customClipSpace bounding box will be uninitialized when this operator is called. If it is not initalized, or a valid shared frustum hint set, + * the resulting shadow map may be invalid. */ + virtual void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) = 0; + }; + // forward declare class ViewDependentData; @@ -197,7 +214,12 @@ namespace SceneUtil { virtual void releaseGLObjects(osg::State* = 0) const; + unsigned int numValidShadows(void) const { return _numValidShadows; } + + void setNumValidShadows(unsigned int numValidShadows) { _numValidShadows = numValidShadows; } + protected: + friend class MWShadowTechnique; virtual ~ViewDependentData() {} MWShadowTechnique* _viewDependentShadowMap; @@ -206,13 +228,19 @@ namespace SceneUtil { LightDataList _lightDataList; ShadowDataList _shadowDataList; + + unsigned int _numValidShadows; }; virtual ViewDependentData* createViewDependentData(osgUtil::CullVisitor* cv); ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); + void copyShadowMap(osgUtil::CullVisitor& cv, ViewDependentData* lhs, ViewDependentData* rhs); + + void setCustomFrustumCallback(CustomFrustumCallback* cfc); + void assignTexGenSettings(osgUtil::CullVisitor& cv, ViewDependentData* vdd); virtual void createShaders(); @@ -246,6 +274,8 @@ namespace SceneUtil { typedef std::map< osgUtil::CullVisitor*, osg::ref_ptr > ViewDependentDataMap; mutable std::mutex _viewDependentDataMapMutex; ViewDependentDataMap _viewDependentDataMap; + osg::ref_ptr _customFrustumCallback; + osg::BoundingBoxd _customClipSpace; osg::ref_ptr _shadowRecievingPlaceholderStateSet; diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index 9fe9a44516..099425330a 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -4,11 +4,16 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include namespace SceneUtil { @@ -22,11 +27,15 @@ namespace SceneUtil } }; - RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping) + RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness) : mTextureWidth(textureWidth) , mTextureHeight(textureHeight) + , mSamples(samples) + , mGenerateMipmaps(generateMipmaps) + , mColorBufferInternalFormat(Color::colorInternalFormat()) + , mDepthBufferInternalFormat(AutoDepth::depthInternalFormat()) , mRenderOrderNum(renderOrderNum) - , mDoPerViewMapping(doPerViewMapping) + , mStereoAwareness(stereoAwareness) { addCullCallback(new CullCallback); setCullingActive(false); @@ -34,28 +43,139 @@ namespace SceneUtil RTTNode::~RTTNode() { + for (auto& vdd : mViewDependentDataMap) + { + auto* camera = vdd.second->mCamera.get(); + if (camera) + { + camera->removeChildren(0, camera->getNumChildren()); + } + } + mViewDependentDataMap.clear(); } void RTTNode::cull(osgUtil::CullVisitor* cv) { + auto frameNumber = cv->getFrameStamp()->getFrameNumber(); auto* vdd = getViewDependentData(cv); - apply(vdd->mCamera); - vdd->mCamera->accept(*cv); + if (frameNumber > vdd->mFrameNumber) + { + apply(vdd->mCamera); + auto& sm = Stereo::Manager::instance(); + if (sm.getEye(cv) == Stereo::Eye::Left) + applyLeft(vdd->mCamera); + if (sm.getEye(cv) == Stereo::Eye::Right) + applyRight(vdd->mCamera); + vdd->mCamera->accept(*cv); + } + vdd->mFrameNumber = frameNumber; + } + + void RTTNode::setColorBufferInternalFormat(GLint internalFormat) + { + mColorBufferInternalFormat = internalFormat; + } + + void RTTNode::setDepthBufferInternalFormat(GLint internalFormat) + { + mDepthBufferInternalFormat = internalFormat; + } + + bool RTTNode::shouldDoPerViewMapping() + { + if(mStereoAwareness != StereoAwareness::Aware) + return false; + if (!Stereo::getMultiview()) + return true; + return false; + } + + bool RTTNode::shouldDoTextureArray() + { + if (mStereoAwareness == StereoAwareness::Unaware) + return false; + if (Stereo::getMultiview()) + return true; + return false; + } + + bool RTTNode::shouldDoTextureView() + { + if (mStereoAwareness != StereoAwareness::Unaware_MultiViewShaders) + return false; + if (Stereo::getMultiview()) + return true; + return false; + } + + osg::Texture2DArray* RTTNode::createTextureArray(GLint internalFormat) + { + osg::Texture2DArray* textureArray = new osg::Texture2DArray; + textureArray->setTextureSize(mTextureWidth, mTextureHeight, 2); + textureArray->setInternalFormat(internalFormat); + GLenum sourceFormat = 0; + GLenum sourceType = 0; + if (SceneUtil::isDepthFormat(internalFormat)) + { + SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + else + { + SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + textureArray->setSourceFormat(sourceFormat); + textureArray->setSourceType(sourceType); + textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return textureArray; + } + + osg::Texture2D* RTTNode::createTexture(GLint internalFormat) + { + osg::Texture2D* texture = new osg::Texture2D; + texture->setTextureSize(mTextureWidth, mTextureHeight); + texture->setInternalFormat(internalFormat); + GLenum sourceFormat = 0; + GLenum sourceType = 0; + if (SceneUtil::isDepthFormat(internalFormat)) + { + SceneUtil::getDepthFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + else + { + SceneUtil::getColorFormatSourceFormatAndType(internalFormat, sourceFormat, sourceType); + } + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; } osg::Texture* RTTNode::getColorTexture(osgUtil::CullVisitor* cv) { - return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture; + return getViewDependentData(cv)->mColorTexture; } osg::Texture* RTTNode::getDepthTexture(osgUtil::CullVisitor* cv) { - return getViewDependentData(cv)->mCamera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; + return getViewDependentData(cv)->mDepthTexture; + } + + osg::Camera* RTTNode::getCamera(osgUtil::CullVisitor* cv) + { + return getViewDependentData(cv)->mCamera; } RTTNode::ViewDependentData* RTTNode::getViewDependentData(osgUtil::CullVisitor* cv) { - if (!mDoPerViewMapping) + if (!shouldDoPerViewMapping()) // Always setting it to null is an easy way to disable per-view mapping when mDoPerViewMapping is false. // This is safe since the visitor is never dereferenced. cv = nullptr; @@ -63,7 +183,8 @@ namespace SceneUtil if (mViewDependentDataMap.count(cv) == 0) { auto camera = new osg::Camera(); - mViewDependentDataMap[cv].reset(new ViewDependentData); + auto vdd = std::make_shared(); + mViewDependentDataMap[cv] = vdd; mViewDependentDataMap[cv]->mCamera = camera; camera->setRenderOrder(osg::Camera::PRE_RENDER, mRenderOrderNum); @@ -72,34 +193,63 @@ namespace SceneUtil camera->setViewport(0, 0, mTextureWidth, mTextureHeight); SceneUtil::setCameraClearDepth(camera); - setDefaults(mViewDependentDataMap[cv]->mCamera.get()); + setDefaults(camera); + + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER)) + vdd->mColorTexture = camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._texture; + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER)) + vdd->mDepthTexture = camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._texture; - // Create any buffer attachments not added in setDefaults - if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) +#ifdef OSG_HAS_MULTIVIEW + if (shouldDoTextureArray()) { - auto colorBuffer = new osg::Texture2D; - colorBuffer->setTextureSize(mTextureWidth, mTextureHeight); - colorBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - colorBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - colorBuffer->setInternalFormat(GL_RGB); - colorBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - colorBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::COLOR_BUFFER, colorBuffer); - SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, colorBuffer); + // Create any buffer attachments not added in setDefaults + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) + { + vdd->mColorTexture = createTextureArray(mColorBufferInternalFormat); + camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps, mSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, mGenerateMipmaps); + } + + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + { + vdd->mDepthTexture = createTextureArray(mDepthBufferInternalFormat); + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + } + + if (shouldDoTextureView()) + { + // In this case, shaders being set to multiview forces us to render to a multiview framebuffer even though we don't need that. + // This forces us to make Texture2DArray. To make this possible to sample as a Texture2D, make a Texture2D view into the texture array. + vdd->mColorTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mColorTexture.get()), 0); + vdd->mDepthTexture = Stereo::createTextureView_Texture2DFromTexture2DArray(static_cast(vdd->mDepthTexture.get()), 0); + } + } + else +#endif + { + // Create any buffer attachments not added in setDefaults + if (camera->getBufferAttachmentMap().count(osg::Camera::COLOR_BUFFER) == 0) + { + vdd->mColorTexture = createTexture(mColorBufferInternalFormat); + camera->attach(osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps, mSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, vdd->mColorTexture, 0, 0, mGenerateMipmaps); + } + + if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + { + vdd->mDepthTexture = createTexture(mDepthBufferInternalFormat); + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, vdd->mDepthTexture, 0, 0, false, mSamples); + } } - if (camera->getBufferAttachmentMap().count(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER) == 0) + // OSG appears not to properly initialize this metadata. So when multisampling is enabled, OSG will use incorrect formats for the resolve buffers. + if (mSamples > 1) { - auto depthBuffer = new osg::Texture2D; - depthBuffer->setTextureSize(mTextureWidth, mTextureHeight); - depthBuffer->setSourceFormat(GL_DEPTH_STENCIL_EXT); - depthBuffer->setInternalFormat(GL_DEPTH24_STENCIL8_EXT); - depthBuffer->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - depthBuffer->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - depthBuffer->setSourceType(GL_UNSIGNED_INT_24_8_EXT); - depthBuffer->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); - depthBuffer->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, depthBuffer); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mColorBufferInternalFormat; + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = mGenerateMipmaps; + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mDepthBufferInternalFormat; + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = mGenerateMipmaps; } } diff --git a/components/sceneutil/rtt.hpp b/components/sceneutil/rtt.hpp index c587006c96..ec8243792f 100644 --- a/components/sceneutil/rtt.hpp +++ b/components/sceneutil/rtt.hpp @@ -9,6 +9,7 @@ namespace osg { class Texture2D; + class Texture2DArray; class Camera; } @@ -19,6 +20,8 @@ namespace osgUtil namespace SceneUtil { + class CreateTextureViewsCallback; + /// @brief Implements per-view RTT operations. /// @par With a naive RTT implementation, subsequent views of multiple views will overwrite the results of the previous views, leading to /// the results of the last view being broadcast to all views. An error in all cases where the RTT result depends on the view. @@ -32,37 +35,73 @@ namespace SceneUtil class RTTNode : public osg::Node { public: - RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping); + enum class StereoAwareness + { + Unaware, //! RTT does not vary by view. A single RTT context is created + Aware, //! RTT varies by view. One RTT context per view is created. Textures are automatically created as arrays if multiview is enabled. + Unaware_MultiViewShaders, //! RTT does not vary by view, but renders with multiview shaders and needs to create texture arrays if multiview is enabled. + }; + + RTTNode(uint32_t textureWidth, uint32_t textureHeight, uint32_t samples, bool generateMipmaps, int renderOrderNum, StereoAwareness stereoAwareness); ~RTTNode(); osg::Texture* getColorTexture(osgUtil::CullVisitor* cv); osg::Texture* getDepthTexture(osgUtil::CullVisitor* cv); + osg::Camera* getCamera(osgUtil::CullVisitor* cv); - /// Apply state - to override in derived classes - /// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame. + /// Set default settings - optionally override in derived classes virtual void setDefaults(osg::Camera* camera) {}; - /// Set default settings - optionally override in derived classes + /// Apply state - to override in derived classes + /// @note Due to the view mapping approach you *have* to apply all camera settings, even if they have not changed since the last frame. virtual void apply(osg::Camera* camera) {}; + /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() + virtual void applyLeft(osg::Camera* camera) {} + /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() + virtual void applyRight(osg::Camera* camera) {} + void cull(osgUtil::CullVisitor* cv); + uint32_t width() const { return mTextureWidth; } + uint32_t height() const { return mTextureHeight; } + uint32_t samples() const { return mSamples; } + bool generatesMipmaps() const { return mGenerateMipmaps; } + + void setColorBufferInternalFormat(GLint internalFormat); + void setDepthBufferInternalFormat(GLint internalFormat); + + protected: + bool shouldDoPerViewMapping(); + bool shouldDoTextureArray(); + bool shouldDoTextureView(); + osg::Texture2DArray* createTextureArray(GLint internalFormat); + osg::Texture2D* createTexture(GLint internalFormat); + private: + friend class CreateTextureViewsCallback; struct ViewDependentData { osg::ref_ptr mCamera; + osg::ref_ptr mColorTexture; + osg::ref_ptr mDepthTexture; + unsigned int mFrameNumber = 0; }; ViewDependentData* getViewDependentData(osgUtil::CullVisitor* cv); - typedef std::map< osgUtil::CullVisitor*, std::unique_ptr > ViewDependentDataMap; + typedef std::map< osgUtil::CullVisitor*, std::shared_ptr > ViewDependentDataMap; ViewDependentDataMap mViewDependentDataMap; uint32_t mTextureWidth; uint32_t mTextureHeight; + uint32_t mSamples; + bool mGenerateMipmaps; + GLint mColorBufferInternalFormat; + GLint mDepthBufferInternalFormat; int mRenderOrderNum; - bool mDoPerViewMapping; + StereoAwareness mStereoAwareness; }; } #endif diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index dfe4cf1507..5220fce78a 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "mwshadowtechnique.hpp" @@ -101,6 +102,7 @@ namespace SceneUtil mIndoorShadowCastingMask(indoorShadowCastingMask) { mShadowedScene->setShadowTechnique(mShadowTechnique); + Stereo::Manager::instance().setShadowTechnique(mShadowTechnique); mShadowedScene->addChild(sceneRoot); rootNode->addChild(mShadowedScene); @@ -117,6 +119,7 @@ namespace SceneUtil ShadowManager::~ShadowManager() { + Stereo::Manager::instance().setShadowTechnique(nullptr); } Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index 9ef82b9271..9778cf4852 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -1,5 +1,7 @@ #include "statesetupdater.hpp" +#include + #include #include #include @@ -38,6 +40,12 @@ namespace SceneUtil { auto stateset = getCvDependentStateset(cv); apply(stateset, cv); + auto& sm = Stereo::Manager::instance(); + if (sm.getEye(cv) == Stereo::Eye::Left) + applyLeft(stateset, cv); + if (sm.getEye(cv) == Stereo::Eye::Right) + applyRight(stateset, cv); + cv->pushStateSet(stateset); traverse(node, cv); cv->popStateSet(); diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index cc2e248457..fb8a9c2fc9 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -41,6 +41,11 @@ namespace SceneUtil /// even if it has not changed since the last frame. virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) {} + /// Apply any state specific to the Left view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback + virtual void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} + /// Apply any state specific to the Right view. Default implementation does nothing. Called after apply() \note requires the updater be a cull callback + virtual void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) {} + /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index d8bbeeadc2..8923fec666 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -647,6 +648,8 @@ namespace Shader defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + Stereo::Manager::instance().shaderStereoDefines(defineMap); + std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; diff --git a/components/stereo/frustum.cpp b/components/stereo/frustum.cpp new file mode 100644 index 0000000000..81fd022f58 --- /dev/null +++ b/components/stereo/frustum.cpp @@ -0,0 +1,162 @@ +#include "stereomanager.hpp" +#include "multiview.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "frustum.hpp" + +namespace Stereo +{ +#ifdef OSG_HAS_MULTIVIEW + struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback + { + MultiviewFrustumCallback(StereoFrustumManager* sfm) + : mSfm(sfm) + { + + } + + void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override + { + auto cm = cullStack.getCullingMode(); + bool nearCulling = !!(cm & osg::CullSettings::NEAR_PLANE_CULLING); + bool farCulling = !!(cm & osg::CullSettings::FAR_PLANE_CULLING); + frustum.setToBoundingBox(mSfm->boundingBox(), nearCulling, farCulling); + } + + StereoFrustumManager* mSfm; + }; +#endif + + struct ShadowFrustumCallback final : public SceneUtil::MWShadowTechnique::CustomFrustumCallback + { + ShadowFrustumCallback(StereoFrustumManager* parent) : mParent(parent) + { + } + + void operator()(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint) override + { + mParent->customFrustumCallback(cv, customClipSpace, sharedFrustumHint); + } + + StereoFrustumManager* mParent; + }; + + void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb) + { + static const std::array clipCorners = {{ + {-1.0, -1.0, -1.0}, + { 1.0, -1.0, -1.0}, + { 1.0, -1.0, 1.0}, + {-1.0, -1.0, 1.0}, + {-1.0, 1.0, -1.0}, + { 1.0, 1.0, -1.0}, + { 1.0, 1.0, 1.0}, + {-1.0, 1.0, 1.0} + }}; + + osg::Matrix slaveClipToView; + slaveClipToView.invert(slaveProjection); + + for (const auto& clipCorner : clipCorners) + { + auto masterViewVertice = clipCorner * slaveClipToView; + auto masterClipVertice = masterViewVertice * masterProjection; + bb.expandBy(masterClipVertice); + } + } + + StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) + : mCamera(camera) + , mShadowTechnique(nullptr) + , mShadowFrustumCallback(nullptr) + { + if (Stereo::getMultiview()) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewFrustumCallback = new MultiviewFrustumCallback(this); + mCamera->setInitialFrustumCallback(mMultiviewFrustumCallback); +#endif + } + + if (Settings::Manager::getBool("shared shadow maps", "Stereo")) + { + mShadowFrustumCallback = new ShadowFrustumCallback(this); + auto* renderer = static_cast(mCamera->getRenderer()); + for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) + { + mSharedFrustums[sceneView->getCullVisitorRight()] = sceneView->getCullVisitorLeft(); + } + } + } + + StereoFrustumManager::~StereoFrustumManager() + { + if (Stereo::getMultiview()) + { +#ifdef OSG_HAS_MULTIVIEW + mCamera->setInitialFrustumCallback(nullptr); +#endif + } + + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(nullptr); + } + + void StereoFrustumManager::setShadowTechnique( + SceneUtil::MWShadowTechnique* shadowTechnique) + { + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(nullptr); + mShadowTechnique = shadowTechnique; + if (mShadowTechnique) + mShadowTechnique->setCustomFrustumCallback(mShadowFrustumCallback); + } + + void StereoFrustumManager::customFrustumCallback( + osgUtil::CullVisitor& cv, + osg::BoundingBoxd& customClipSpace, + osgUtil::CullVisitor*& sharedFrustumHint) + { + auto it = mSharedFrustums.find(&cv); + if (it != mSharedFrustums.end()) + { + sharedFrustumHint = it->second; + } + + customClipSpace = mBoundingBox; + } + + void StereoFrustumManager::update(std::array projections) + { + mBoundingBox.init(); + for (auto& projection : projections) + joinBoundingBoxes(mCamera->getProjectionMatrix(), projection, mBoundingBox); + } +} diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp new file mode 100644 index 0000000000..579d3b6876 --- /dev/null +++ b/components/stereo/frustum.hpp @@ -0,0 +1,76 @@ +#ifndef STEREO_FRUSTUM_H +#define STEREO_FRUSTUM_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace osg +{ + class FrameBufferObject; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace usgUtil +{ + class CullVisitor; +} + +namespace SceneUtil +{ + class MWShadowTechnique; +} + +namespace Stereo +{ +#ifdef OSG_HAS_MULTIVIEW + struct MultiviewFrustumCallback; +#endif + + struct ShadowFrustumCallback; + + void joinBoundingBoxes(const osg::Matrix& masterProjection, const osg::Matrix& slaveProjection, osg::BoundingBoxd& bb); + + class StereoFrustumManager + { + public: + StereoFrustumManager(osg::Camera* camera); + ~StereoFrustumManager(); + + void update(std::array projections); + + const osg::BoundingBoxd& boundingBox() const { return mBoundingBox; } + + void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); + + void customFrustumCallback(osgUtil::CullVisitor& cv, osg::BoundingBoxd& customClipSpace, osgUtil::CullVisitor*& sharedFrustumHint); + + private: + osg::ref_ptr mCamera; + osg::ref_ptr mShadowTechnique; + osg::ref_ptr mShadowFrustumCallback; + std::map< osgUtil::CullVisitor*, osgUtil::CullVisitor*> mSharedFrustums; + osg::BoundingBoxd mBoundingBox; + +#ifdef OSG_HAS_MULTIVIEW + osg::ref_ptr mMultiviewFrustumCallback; +#endif + }; +} + +#endif diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp new file mode 100644 index 0000000000..56c1ada349 --- /dev/null +++ b/components/stereo/multiview.cpp @@ -0,0 +1,484 @@ +#include "multiview.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Stereo +{ + namespace + { + bool getMultiviewSupportedImpl(unsigned int contextID) + { +#ifdef OSG_HAS_MULTIVIEW + if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview")) + { + Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview\" not supported)"; + return false; + } + + if (!osg::isGLExtensionSupported(contextID, "GL_OVR_multiview2")) + { + Log(Debug::Verbose) << "Disabling Multiview (opengl extension \"GL_OVR_multiview2\" not supported)"; + return false; + } + return true; +#else + Log(Debug::Verbose) << "Disabling Multiview (OSG does not support multiview)"; + return false; +#endif + } + + bool getMultiviewSupported(unsigned int contextID) + { + static bool supported = getMultiviewSupportedImpl(contextID); + return supported; + } + + bool getTextureViewSupportedImpl(unsigned int contextID) + { + if (!osg::isGLExtensionOrVersionSupported(contextID, "ARB_texture_view", 4.3)) + { + Log(Debug::Verbose) << "Disabling texture views (opengl extension \"ARB_texture_view\" not supported)"; + return false; + } + return true; + } + + bool getTextureViewSupported(unsigned int contextID) + { + static bool supported = getTextureViewSupportedImpl(contextID); + return supported; + } + + bool getMultiviewImpl(unsigned int contextID) + { + if (!Stereo::getStereo()) + { + Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; + return false; + } + + if (!Settings::Manager::getBool("multiview", "Stereo")) + { + Log(Debug::Verbose) << "Disabling Multiview (disabled by config)"; + return false; + } + + if (!getMultiviewSupported(contextID)) + { + return false; + } + + if (!getTextureViewSupported(contextID)) + { + Log(Debug::Verbose) << "Disabling Multiview (texture views not supported)"; + return false; + } + + Log(Debug::Verbose) << "Enabling Multiview"; + return true; + } + + bool getMultiview(unsigned int contextID) + { + static bool multiView = getMultiviewImpl(contextID); + return multiView; + } + } + + bool getTextureViewSupported() + { + return getTextureViewSupported(0); + } + + bool getMultiview() + { + return getMultiview(0); + } + + void configureExtensions(unsigned int contextID) + { + getTextureViewSupported(contextID); + getMultiviewSupported(contextID); + getMultiview(contextID); + } + + void setVertexBufferHint() + { + if (getStereo() && Settings::Manager::getBool("multiview", "Stereo")) + { + auto* ds = osg::DisplaySettings::instance().get(); + if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") + && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) + { + // Note that this only works if this code is executed before realize() is called on the viewer. + // The hint is read by the state object only once, before the user realize operations are run. + // Therefore we have to set this hint without access to a graphics context to let us determine + // if multiview will actually be supported or not. So if the user has requested multiview, we + // will just have to set it regardless. + ds->setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT); + Log(Debug::Verbose) << "Disabling display lists"; + } + } + } + + class Texture2DViewSubloadCallback : public osg::Texture2D::SubloadCallback + { + public: + Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer); + + void load(const osg::Texture2D& texture, osg::State& state) const override; + void subload(const osg::Texture2D& texture, osg::State& state) const override; + + private: + osg::ref_ptr mTextureArray; + int mLayer; + }; + + Texture2DViewSubloadCallback::Texture2DViewSubloadCallback(osg::Texture2DArray* textureArray, int layer) + : mTextureArray(textureArray) + , mLayer(layer) + { + } + + void Texture2DViewSubloadCallback::load(const osg::Texture2D& texture, osg::State& state) const + { + state.checkGLErrors("before Texture2DViewSubloadCallback::load()"); + + auto contextId = state.getContextID(); + auto* gl = osg::GLExtensions::Get(contextId, false); + mTextureArray->apply(state); + + auto sourceTextureObject = mTextureArray->getTextureObject(contextId); + if (!sourceTextureObject) + { + Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2DArray did not have a texture object"; + return; + } + + auto targetTextureObject = texture.getTextureObject(contextId); + if (!sourceTextureObject) + { + Log(Debug::Error) << "Texture2DViewSubloadCallback: Texture2D did not have a texture object"; + return; + } + + + // OSG already bound this texture ID, giving it a target. + // Delete it and make a new texture ID. + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &targetTextureObject->_id); + glGenTextures(1, &targetTextureObject->_id); + + auto sourceId = sourceTextureObject->_id; + auto targetId = targetTextureObject->_id; + auto internalFormat = sourceTextureObject->_profile._internalFormat; + auto levels = std::max(1, sourceTextureObject->_profile._numMipmapLevels); + + { + ////// OSG BUG + // Texture views require immutable storage. + // OSG should always give immutable storage to sized internal formats, but does not do so for depth formats. + // Fortunately, we can just call glTexStorage3D here to make it immutable. This probably discards depth info for that frame, but whatever. +#ifndef GL_TEXTURE_IMMUTABLE_FORMAT +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +#endif + // Store any current binding and re-apply it after so i don't mess with state. + GLint oldBinding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &oldBinding); + + // Bind the source texture and check if it's immutable. + glBindTexture(GL_TEXTURE_2D_ARRAY, sourceId); + GLint immutable = 0; + glGetTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_IMMUTABLE_FORMAT, &immutable); + if(!immutable) + { + // It wasn't immutable, so make it immutable. + gl->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, internalFormat, sourceTextureObject->_profile._width, sourceTextureObject->_profile._height, 2); + state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTexStorage3D"); + } + glBindTexture(GL_TEXTURE_2D_ARRAY, oldBinding); + } + + gl->glTextureView(targetId, GL_TEXTURE_2D, sourceId, internalFormat, 0, levels, mLayer, 1); + state.checkGLErrors("after Texture2DViewSubloadCallback::load()::glTextureView"); + glBindTexture(GL_TEXTURE_2D, targetId); + } + + void Texture2DViewSubloadCallback::subload(const osg::Texture2D& texture, osg::State& state) const + { + // Nothing to do + } + + osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer) + { + if (!getTextureViewSupported()) + { + Log(Debug::Error) << "createTextureView_Texture2DFromTexture2DArray: Tried to use a texture view but glTextureView is not supported"; + return nullptr; + } + + osg::ref_ptr texture2d = new osg::Texture2D; + texture2d->setSubloadCallback(new Texture2DViewSubloadCallback(textureArray, layer)); + texture2d->setTextureSize(textureArray->getTextureWidth(), textureArray->getTextureHeight()); + texture2d->setBorderColor(textureArray->getBorderColor()); + texture2d->setBorderWidth(textureArray->getBorderWidth()); + texture2d->setLODBias(textureArray->getLODBias()); + texture2d->setFilter(osg::Texture::FilterParameter::MAG_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MAG_FILTER)); + texture2d->setFilter(osg::Texture::FilterParameter::MIN_FILTER, textureArray->getFilter(osg::Texture::FilterParameter::MIN_FILTER)); + texture2d->setInternalFormat(textureArray->getInternalFormat()); + texture2d->setNumMipmapLevels(textureArray->getNumMipmapLevels()); + return texture2d; + } + + class UpdateRenderStagesCallback : public SceneUtil::NodeCallback + { + public: + UpdateRenderStagesCallback(Stereo::MultiviewFramebuffer* multiviewFramebuffer) + : mMultiviewFramebuffer(multiviewFramebuffer) + { + mViewport = new osg::Viewport(0, 0, multiviewFramebuffer->width(), multiviewFramebuffer->height()); + mViewportStateset = new osg::StateSet(); + mViewportStateset->setAttribute(mViewport.get()); + } + + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) + { + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); + + bool msaa = mMultiviewFramebuffer->samples() > 1; + + if (!Stereo::getMultiview()) + { + auto eye = static_cast(Stereo::Manager::instance().getEye(cv)); + + if (msaa) + { + renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerMsaaFbo(eye)); + renderStage->setMultisampleResolveFramebufferObject(mMultiviewFramebuffer->layerFbo(eye)); + } + else + { + renderStage->setFrameBufferObject(mMultiviewFramebuffer->layerFbo(eye)); + } + } + + // OSG tries to do a horizontal split, but we want to render to separate framebuffers instead. + renderStage->setViewport(mViewport); + cv->pushStateSet(mViewportStateset.get()); + traverse(node, cv); + cv->popStateSet(); + } + + private: + Stereo::MultiviewFramebuffer* mMultiviewFramebuffer; + osg::ref_ptr mViewport; + osg::ref_ptr mViewportStateset; + }; + + MultiviewFramebuffer::MultiviewFramebuffer(int width, int height, int samples) + : mWidth(width) + , mHeight(height) + , mSamples(samples) + , mMultiview(getMultiview()) + , mMultiviewFbo{ new osg::FrameBufferObject } + , mLayerFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } + , mLayerMsaaFbo{ new osg::FrameBufferObject, new osg::FrameBufferObject } + { + } + + MultiviewFramebuffer::~MultiviewFramebuffer() + { + } + + void MultiviewFramebuffer::attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + if (mMultiview) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewColorTexture = createTextureArray(sourceFormat, sourceType, internalFormat); + mMultiviewFbo->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMultiviewColorTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); + for (unsigned i = 0; i < 2; i++) + { + mColorTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewColorTexture.get(), i); + mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); + } +#endif + } + else + { + for (unsigned i = 0; i < 2; i++) + { + if (mSamples > 1) + { + mMsaaColorTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); + mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mMsaaColorTexture[i])); + } + mColorTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); + mLayerFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER, osg::FrameBufferAttachment(mColorTexture[i])); + } + } + } + + void MultiviewFramebuffer::attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + if (mMultiview) + { +#ifdef OSG_HAS_MULTIVIEW + mMultiviewDepthTexture = createTextureArray(sourceFormat, sourceType, internalFormat); + mMultiviewFbo->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMultiviewDepthTexture, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0)); + for (unsigned i = 0; i < 2; i++) + { + mDepthTexture[i] = createTextureView_Texture2DFromTexture2DArray(mMultiviewDepthTexture.get(), i); + mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); + } +#endif + } + else + { + for (unsigned i = 0; i < 2; i++) + { + if (mSamples > 1) + { + mMsaaDepthTexture[i] = createTextureMsaa(sourceFormat, sourceType, internalFormat); + mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mMsaaDepthTexture[i])); + } + mDepthTexture[i] = createTexture(sourceFormat, sourceType, internalFormat); + mLayerFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(mDepthTexture[i])); + } + } + } + + osg::FrameBufferObject* MultiviewFramebuffer::multiviewFbo() + { + return mMultiviewFbo; + } + + osg::FrameBufferObject* MultiviewFramebuffer::layerFbo(int i) + { + return mLayerFbo[i]; + } + + osg::FrameBufferObject* MultiviewFramebuffer::layerMsaaFbo(int i) + { + return mLayerMsaaFbo[i]; + } + + osg::Texture2DArray* MultiviewFramebuffer::multiviewColorBuffer() + { + return mMultiviewColorTexture; + } + + osg::Texture2D* MultiviewFramebuffer::layerColorBuffer(int i) + { + return mColorTexture[i]; + } + + osg::Texture2D* MultiviewFramebuffer::layerDepthBuffer(int i) + { + return mDepthTexture[i]; + } + void MultiviewFramebuffer::attachTo(osg::Camera* camera) + { +#ifdef OSG_HAS_MULTIVIEW + if (mMultiview) + { + if (mMultiviewColorTexture) + { + camera->attach(osg::Camera::COLOR_BUFFER, mMultiviewColorTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._internalFormat = mMultiviewColorTexture->getInternalFormat(); + camera->getBufferAttachmentMap()[osg::Camera::COLOR_BUFFER]._mipMapGeneration = false; + } + if (mMultiviewDepthTexture) + { + camera->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, mMultiviewDepthTexture, 0, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, false, mSamples); + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._internalFormat = mMultiviewDepthTexture->getInternalFormat(); + camera->getBufferAttachmentMap()[osg::Camera::PACKED_DEPTH_STENCIL_BUFFER]._mipMapGeneration = false; + } + } +#endif + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + if (!mCullCallback) + mCullCallback = new UpdateRenderStagesCallback(this); + camera->addCullCallback(mCullCallback); + } + + void MultiviewFramebuffer::detachFrom(osg::Camera* camera) + { +#ifdef OSG_HAS_MULTIVIEW + if (mMultiview) + { + if (mMultiviewColorTexture) + { + camera->detach(osg::Camera::COLOR_BUFFER); + } + if (mMultiviewDepthTexture) + { + camera->detach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER); + } + } +#endif + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + if (mCullCallback) + camera->removeCullCallback(mCullCallback); + } + + osg::Texture2D* MultiviewFramebuffer::createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2D* texture = new osg::Texture2D; + texture->setTextureSize(mWidth, mHeight); + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setInternalFormat(internalFormat); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; + } + + osg::Texture2DMultisample* MultiviewFramebuffer::createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2DMultisample* texture = new osg::Texture2DMultisample; + texture->setTextureSize(mWidth, mHeight); + texture->setNumSamples(mSamples); + texture->setSourceFormat(sourceFormat); + texture->setSourceType(sourceType); + texture->setInternalFormat(internalFormat); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return texture; + } + + osg::Texture2DArray* MultiviewFramebuffer::createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat) + { + osg::Texture2DArray* textureArray = new osg::Texture2DArray; + textureArray->setTextureSize(mWidth, mHeight, 2); + textureArray->setSourceFormat(sourceFormat); + textureArray->setSourceType(sourceType); + textureArray->setInternalFormat(internalFormat); + textureArray->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + textureArray->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + textureArray->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + textureArray->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE); + return textureArray; + } +} diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp new file mode 100644 index 0000000000..298d042ffc --- /dev/null +++ b/components/stereo/multiview.hpp @@ -0,0 +1,85 @@ +#ifndef STEREO_MULTIVIEW_H +#define STEREO_MULTIVIEW_H + +#include +#include +#include + +#include +#include + +namespace osg +{ + class FrameBufferObject; + class Texture; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace Stereo +{ + class UpdateRenderStagesCallback; + + //! Check if TextureView is supported. Results are undefined if called before configureExtensions(). + bool getTextureViewSupported(); + + //! Check if Multiview should be used. Results are undefined if called before configureExtensions(). + bool getMultiview(); + + //! Use the provided context to check what extensions are supported and configure use of multiview based on extensions and settings. + void configureExtensions(unsigned int contextID); + + //! Sets the appropriate vertex buffer hint on OSG's display settings if needed + void setVertexBufferHint(); + + //! Creates a Texture2D as a texture view into a Texture2DArray + osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer); + + //! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling + class MultiviewFramebuffer + { + public: + MultiviewFramebuffer(int width, int height, int samples); + ~MultiviewFramebuffer(); + + void attachColorComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); + void attachDepthComponent(GLint sourceFormat, GLint sourceType, GLint internalFormat); + + osg::FrameBufferObject* multiviewFbo(); + osg::FrameBufferObject* layerFbo(int i); + osg::FrameBufferObject* layerMsaaFbo(int i); + osg::Texture2DArray* multiviewColorBuffer(); + osg::Texture2D* layerColorBuffer(int i); + osg::Texture2D* layerDepthBuffer(int i); + + void attachTo(osg::Camera* camera); + void detachFrom(osg::Camera* camera); + + int width() const { return mWidth; } + int height() const { return mHeight; } + int samples() const { return mSamples; }; + + private: + osg::Texture2D* createTexture(GLint sourceFormat, GLint sourceType, GLint internalFormat); + osg::Texture2DMultisample* createTextureMsaa(GLint sourceFormat, GLint sourceType, GLint internalFormat); + osg::Texture2DArray* createTextureArray(GLint sourceFormat, GLint sourceType, GLint internalFormat); + + int mWidth; + int mHeight; + int mSamples; + bool mMultiview; + osg::ref_ptr mCullCallback; + osg::ref_ptr mMultiviewFbo; + std::array, 2> mLayerFbo; + std::array, 2> mLayerMsaaFbo; + osg::ref_ptr mMultiviewColorTexture; + osg::ref_ptr mMultiviewDepthTexture; + std::array, 2> mColorTexture; + std::array, 2> mMsaaColorTexture; + std::array, 2> mDepthTexture; + std::array, 2> mMsaaDepthTexture; + }; +} + +#endif diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp new file mode 100644 index 0000000000..7214fe0031 --- /dev/null +++ b/components/stereo/stereomanager.cpp @@ -0,0 +1,459 @@ +#include "stereomanager.hpp" +#include "multiview.hpp" +#include "frustum.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +namespace Stereo +{ + // Update stereo view/projection during update + class StereoUpdateCallback final : public osg::Callback + { + public: + StereoUpdateCallback(Manager* stereoView) : stereoView(stereoView) {} + + bool run(osg::Object* object, osg::Object* data) override + { + auto b = traverse(object, data); + stereoView->update(); + return b; + } + + Manager* stereoView; + }; + + // Update states during cull + class BruteForceStereoStatesetUpdateCallback final : public SceneUtil::StateSetUpdater + { + public: + BruteForceStereoStatesetUpdateCallback(Manager* manager) + : mManager(manager) + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) override + { + stateset->addUniform(new osg::Uniform("projectionMatrix", osg::Matrixf{})); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override + { + } + + void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + osg::Matrix dummy; + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); + } + + void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override + { + osg::Matrix dummy; + auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); + if (uProjectionMatrix) + uProjectionMatrix->set(mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); + } + + private: + Manager* mManager; + }; + + // Update states during cull + class MultiviewStereoStatesetUpdateCallback : public SceneUtil::StateSetUpdater + { + public: + MultiviewStereoStatesetUpdateCallback(Manager* manager) + : mManager(manager) + { + } + + protected: + virtual void setDefaults(osg::StateSet* stateset) + { + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "viewMatrixMultiView", 2)); + stateset->addUniform(new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2)); + } + + virtual void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) + { + mManager->updateMultiviewStateset(stateset); + } + + private: + Manager* mManager; + }; + + static Manager* sInstance = nullptr; + + Manager& Manager::instance() + { + return *sInstance; + } + + struct CustomViewCallback : public Manager::UpdateViewCallback + { + public: + CustomViewCallback(); + + void updateView(View& left, View& right) override; + + private: + View mLeft; + View mRight; + }; + + Manager::Manager(osgViewer::Viewer* viewer) + : mViewer(viewer) + , mMainCamera(mViewer->getCamera()) + , mUpdateCallback(new StereoUpdateCallback(this)) + , mMasterProjectionMatrix(osg::Matrix::identity()) + , mEyeResolutionOverriden(false) + , mEyeResolutionOverride(0,0) + , mFrustumManager(nullptr) + , mUpdateViewCallback(nullptr) + { + if (sInstance) + throw std::logic_error("Double instance of Stereo::Manager"); + sInstance = this; + + if (Settings::Manager::getBool("use custom view", "Stereo")) + mUpdateViewCallback = std::make_shared(); + + if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) + { + osg::Vec2i eyeResolution = osg::Vec2i(); + eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); + eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); + overrideEyeResolution(eyeResolution); + } + } + + Manager::~Manager() + { + } + + void Manager::initializeStereo(osg::GraphicsContext* gc) + { + mMainCamera->addUpdateCallback(mUpdateCallback); + mFrustumManager = std::make_unique(mViewer->getCamera()); + + auto ci = gc->getState()->getContextID(); + configureExtensions(ci); + + if(getMultiview()) + setupOVRMultiView2Technique(); + else + setupBruteForceTechnique(); + + updateStereoFramebuffer(); + + } + + void Manager::shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const + { + if (getMultiview()) + { + defines["GLSLVersion"] = "330 compatibility"; + defines["useOVR_multiview"] = "1"; + defines["numViews"] = "2"; + } + else + { + defines["useOVR_multiview"] = "0"; + defines["numViews"] = "1"; + } + } + + void Manager::overrideEyeResolution(const osg::Vec2i& eyeResolution) + { + mEyeResolutionOverride = eyeResolution; + mEyeResolutionOverriden = true; + + if (mMultiviewFramebuffer) + updateStereoFramebuffer(); + } + + void Manager::screenResolutionChanged() + { + updateStereoFramebuffer(); + } + + osg::Vec2i Manager::eyeResolution() + { + if (mEyeResolutionOverriden) + return mEyeResolutionOverride; + auto width = mMainCamera->getViewport()->width() / 2; + auto height = mMainCamera->getViewport()->height(); + + return osg::Vec2i(width, height); + } + + void Manager::disableStereoForNode(osg::Node* node) + { + // Re-apply the main camera's full viewport to return to full screen rendering. + node->getOrCreateStateSet()->setAttribute(mMainCamera->getViewport()); + } + + void Manager::setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique) + { + if (mFrustumManager) + mFrustumManager->setShadowTechnique(shadowTechnique); + } + + void Manager::setupBruteForceTechnique() + { + auto* ds = osg::DisplaySettings::instance().get(); + ds->setStereo(true); + ds->setStereoMode(osg::DisplaySettings::StereoMode::HORIZONTAL_SPLIT); + ds->setUseSceneViewForStereoHint(true); + + mMainCamera->addCullCallback(new BruteForceStereoStatesetUpdateCallback(this)); + + struct ComputeStereoMatricesCallback : public osgUtil::SceneView::ComputeStereoMatricesCallback + { + ComputeStereoMatricesCallback(Manager* sv) + : mManager(sv) + { + + } + + osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const override + { + (void)projection; + return mManager->computeEyeProjection(0, false); + } + + osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const override + { + (void)view; + return mManager->computeEyeView(0); + } + + osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const override + { + (void)projection; + return mManager->computeEyeProjection(1, false); + } + + osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const override + { + (void)view; + return mManager->computeEyeView(1); + } + + Manager* mManager; + }; + + auto* renderer = static_cast(mMainCamera->getRenderer()); + for (auto* sceneView : { renderer->getSceneView(0), renderer->getSceneView(1) }) + { + sceneView->setComputeStereoMatricesCallback(new ComputeStereoMatricesCallback(this)); + + auto* cvMain = sceneView->getCullVisitor(); + auto* cvLeft = sceneView->getCullVisitorLeft(); + auto* cvRight = sceneView->getCullVisitorRight(); + if (!cvMain) + sceneView->setCullVisitor(cvMain = new osgUtil::CullVisitor()); + if (!cvLeft) + sceneView->setCullVisitor(cvLeft = cvMain->clone()); + if (!cvRight) + sceneView->setCullVisitor(cvRight = cvMain->clone()); + + // Osg by default gives cullVisitorLeft and cullVisitor the same identifier. + // So we make our own to avoid confusion + cvMain->setIdentifier(mIdentifierMain); + cvLeft->setIdentifier(mIdentifierLeft); + cvRight->setIdentifier(mIdentifierRight); + } + } + + void Manager::setupOVRMultiView2Technique() + { + auto* ds = osg::DisplaySettings::instance().get(); + ds->setStereo(false); + + mMainCamera->addCullCallback(new MultiviewStereoStatesetUpdateCallback(this)); + } + + void Manager::updateStereoFramebuffer() + { + auto samples = Settings::Manager::getInt("antialiasing", "Video"); + auto eyeRes = eyeResolution(); + + if (mMultiviewFramebuffer) + mMultiviewFramebuffer->detachFrom(mMainCamera); + mMultiviewFramebuffer = std::make_shared(static_cast(eyeRes.x()), static_cast(eyeRes.y()), samples); + mMultiviewFramebuffer->attachColorComponent(SceneUtil::Color::colorSourceFormat(), SceneUtil::Color::colorSourceType(), SceneUtil::Color::colorInternalFormat()); + mMultiviewFramebuffer->attachDepthComponent(SceneUtil::AutoDepth::depthSourceFormat(), SceneUtil::AutoDepth::depthSourceType(), SceneUtil::AutoDepth::depthInternalFormat()); + mMultiviewFramebuffer->attachTo(mMainCamera); + } + + void Manager::update() + { + double near_ = 1.f; + double far_ = 10000.f; + + near_ = Settings::Manager::getFloat("near clip", "Camera"); + far_ = Settings::Manager::getFloat("viewing distance", "Camera"); + auto projectionMatrix = mMainCamera->getProjectionMatrix(); + + if (mUpdateViewCallback) + { + mUpdateViewCallback->updateView(mView[0], mView[1]); + auto viewMatrix = mMainCamera->getViewMatrix(); + mViewOffsetMatrix[0] = mView[0].viewMatrix(true); + mViewOffsetMatrix[1] = mView[1].viewMatrix(true); + mViewMatrix[0] = viewMatrix * mViewOffsetMatrix[0]; + mViewMatrix[1] = viewMatrix * mViewOffsetMatrix[1]; + mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); + mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); + if (SceneUtil::AutoDepth::isReversed()) + { + mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); + mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); + } + + View masterView; + masterView.fov.angleDown = std::min(mView[0].fov.angleDown, mView[1].fov.angleDown); + masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); + masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); + masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); + projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); + mMainCamera->setProjectionMatrix(projectionMatrix); + } + else + { + auto* ds = osg::DisplaySettings::instance().get(); + auto viewMatrix = mMainCamera->getViewMatrix(); + mViewMatrix[0] = ds->computeLeftEyeViewImplementation(viewMatrix); + mViewMatrix[1] = ds->computeRightEyeViewImplementation(viewMatrix); + mViewOffsetMatrix[0] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[0]; + mViewOffsetMatrix[1] = osg::Matrix::inverse(viewMatrix) * mViewMatrix[1]; + mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix); + mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix); + if (SceneUtil::AutoDepth::isReversed()) + { + mProjectionMatrixReverseZ[0] = ds->computeLeftEyeProjectionImplementation(mMasterProjectionMatrix); + mProjectionMatrixReverseZ[1] = ds->computeRightEyeProjectionImplementation(mMasterProjectionMatrix); + } + } + + mFrustumManager->update( + { + mViewOffsetMatrix[0] * mProjectionMatrix[0], + mViewOffsetMatrix[1] * mProjectionMatrix[1] + }); + } + + void Manager::updateMultiviewStateset(osg::StateSet* stateset) + { + // Update stereo uniforms + auto * viewMatrixMultiViewUniform = stateset->getUniform("viewMatrixMultiView"); + auto * projectionMatrixMultiViewUniform = stateset->getUniform("projectionMatrixMultiView"); + + for (int view : {0, 1}) + { + viewMatrixMultiViewUniform->setElement(view, mViewOffsetMatrix[view]); + projectionMatrixMultiViewUniform->setElement(view, computeEyeProjection(view, SceneUtil::AutoDepth::isReversed())); + } + } + + void Manager::setUpdateViewCallback(std::shared_ptr cb) + { + mUpdateViewCallback = cb; + } + + void Manager::setCullCallback(osg::ref_ptr cb) + { + mMainCamera->setCullCallback(cb); + } + + osg::Matrixd Manager::computeEyeProjection(int view, bool reverseZ) const + { + return reverseZ ? mProjectionMatrixReverseZ[view] : mProjectionMatrix[view]; + } + + osg::Matrixd Manager::computeEyeView(int view) const + { + return mViewMatrix[view]; + } + + Eye Manager::getEye(const osgUtil::CullVisitor* cv) const + { + if (cv->getIdentifier() == mIdentifierMain) + return Eye::Center; + if (cv->getIdentifier() == mIdentifierLeft) + return Eye::Left; + if (cv->getIdentifier() == mIdentifierRight) + return Eye::Right; + return Eye::Center; + } + + bool getStereo() + { + static bool stereo = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); + return stereo; + } + + CustomViewCallback::CustomViewCallback() + { + mLeft.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); + mLeft.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); + mLeft.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); + mLeft.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); + mLeft.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); + mLeft.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); + mLeft.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); + mLeft.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); + mLeft.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); + mLeft.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); + mLeft.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); + + mRight.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); + mRight.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); + mRight.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); + mRight.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); + mRight.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); + mRight.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); + mRight.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); + mRight.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); + mRight.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); + mRight.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); + mRight.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); + } + + void CustomViewCallback::updateView(View& left, View& right) + { + left = mLeft; + right = mRight; + } +} diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp new file mode 100644 index 0000000000..fea5a8543f --- /dev/null +++ b/components/stereo/stereomanager.hpp @@ -0,0 +1,134 @@ +#ifndef STEREO_MANAGER_H +#define STEREO_MANAGER_H + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace osg +{ + class FrameBufferObject; + class Texture2D; + class Texture2DMultisample; + class Texture2DArray; +} + +namespace osgViewer +{ + class Viewer; +} + +namespace SceneUtil +{ + class MWShadowTechnique; +} + +namespace Stereo +{ + class MultiviewFramebuffer; + class StereoFrustumManager; + class MultiviewStereoStatesetUpdateCallback; + + bool getStereo(); + + //! Class that provides tools for managing stereo mode + class Manager + { + public: + struct UpdateViewCallback + { + virtual ~UpdateViewCallback() = default; + + //! Called during the update traversal of every frame to update stereo views. + virtual void updateView(View& left, View& right) = 0; + }; + + //! Gets the singleton instance + static Manager& instance(); + + Manager(osgViewer::Viewer* viewer); + ~Manager(); + + //! Called during update traversal + void update(); + + void initializeStereo(osg::GraphicsContext* gc); + + //! Callback that updates stereo configuration during the update pass + void setUpdateViewCallback(std::shared_ptr cb); + + //! Set the cull callback on the appropriate camera object + void setCullCallback(osg::ref_ptr cb); + + osg::Matrixd computeEyeProjection(int view, bool reverseZ) const; + osg::Matrixd computeEyeView(int view) const; + + //! Sets up any definitions necessary for stereo rendering + void shaderStereoDefines(Shader::ShaderManager::DefineMap& defines) const; + + const std::shared_ptr& multiviewFramebuffer() { return mMultiviewFramebuffer; }; + + //! Sets rendering resolution of each eye to eyeResolution. + //! Once set, there will no longer be any connection between rendering resolution and screen/window resolution. + void overrideEyeResolution(const osg::Vec2i& eyeResolution); + + //! Notify stereo manager that the screen/window resolution has changed. + void screenResolutionChanged(); + + //! Get current eye resolution + osg::Vec2i eyeResolution(); + + //! The projection intended for rendering. When reverse Z is enabled, this is not the same as the camera's projection matrix, + //! and therefore must be provided to the manager explicitly. + void setMasterProjectionMatrix(const osg::Matrix& projectionMatrix) { mMasterProjectionMatrix = projectionMatrix; } + + //! Causes the subgraph represented by the node to draw to the full viewport. + //! This has no effect if stereo is not enabled + void disableStereoForNode(osg::Node* node); + + void setShadowTechnique(SceneUtil::MWShadowTechnique* shadowTechnique); + + /// Determine which view the cull visitor belongs to + Eye getEye(const osgUtil::CullVisitor* cv) const; + + private: + friend class MultiviewStereoStatesetUpdateCallback; + void updateMultiviewStateset(osg::StateSet* stateset); + void updateStereoFramebuffer(); + void setupBruteForceTechnique(); + void setupOVRMultiView2Technique(); + + osg::ref_ptr mViewer; + osg::ref_ptr mMainCamera; + osg::ref_ptr mUpdateCallback; + std::string mError; + osg::Matrix mMasterProjectionMatrix; + std::shared_ptr mMultiviewFramebuffer; + bool mEyeResolutionOverriden; + osg::Vec2i mEyeResolutionOverride; + + std::array mView; + std::array mViewMatrix; + std::array mViewOffsetMatrix; + std::array mProjectionMatrix; + std::array mProjectionMatrixReverseZ; + + std::unique_ptr mFrustumManager; + std::shared_ptr mUpdateViewCallback; + + using Identifier = osgUtil::CullVisitor::Identifier; + osg::ref_ptr mIdentifierMain = new Identifier(); + osg::ref_ptr mIdentifierLeft = new Identifier(); + osg::ref_ptr mIdentifierRight = new Identifier(); + }; +} + +#endif diff --git a/components/stereo/types.cpp b/components/stereo/types.cpp new file mode 100644 index 0000000000..4829e32b55 --- /dev/null +++ b/components/stereo/types.cpp @@ -0,0 +1,168 @@ +#include "types.hpp" + +#include + +namespace Stereo +{ + + Pose Pose::operator+(const Pose& rhs) + { + Pose pose = *this; + pose.position += this->orientation * rhs.position; + pose.orientation = rhs.orientation * this->orientation; + return pose; + } + + const Pose& Pose::operator+=(const Pose& rhs) + { + *this = *this + rhs; + return *this; + } + + Pose Pose::operator*(float scalar) + { + Pose pose = *this; + pose.position *= scalar; + return pose; + } + + const Pose& Pose::operator*=(float scalar) + { + *this = *this * scalar; + return *this; + } + + Pose Pose::operator/(float scalar) + { + Pose pose = *this; + pose.position /= scalar; + return pose; + } + const Pose& Pose::operator/=(float scalar) + { + *this = *this / scalar; + return *this; + } + + bool Pose::operator==(const Pose& rhs) const + { + return position == rhs.position && orientation == rhs.orientation; + } + + osg::Matrix View::viewMatrix(bool useGLConventions) + { + auto position = pose.position; + auto orientation = pose.orientation; + + if (useGLConventions) + { + // When applied as an offset to an existing view matrix, + // that view matrix will already convert points to a camera space + // with opengl conventions. So we need to convert offsets to opengl + // conventions. + float y = position.y(); + float z = position.z(); + position.y() = z; + position.z() = -y; + + y = orientation.y(); + z = orientation.z(); + orientation.y() = z; + orientation.z() = -y; + + osg::Matrix viewMatrix; + viewMatrix.setTrans(-position); + viewMatrix.postMultRotate(orientation.conj()); + return viewMatrix; + } + else + { + osg::Vec3d forward = orientation * osg::Vec3d(0, 1, 0); + osg::Vec3d up = orientation * osg::Vec3d(0, 0, 1); + osg::Matrix viewMatrix; + viewMatrix.makeLookAt(position, position + forward, up); + + return viewMatrix; + } + } + + osg::Matrix View::perspectiveMatrix(float near, float far, bool reverseZ) + { + const float tanLeft = tanf(fov.angleLeft); + const float tanRight = tanf(fov.angleRight); + const float tanDown = tanf(fov.angleDown); + const float tanUp = tanf(fov.angleUp); + + const float tanWidth = tanRight - tanLeft; + const float tanHeight = tanUp - tanDown; + + float matrix[16] = {}; + + matrix[0] = 2 / tanWidth; + matrix[4] = 0; + matrix[8] = (tanRight + tanLeft) / tanWidth; + matrix[12] = 0; + + matrix[1] = 0; + matrix[5] = 2 / tanHeight; + matrix[9] = (tanUp + tanDown) / tanHeight; + matrix[13] = 0; + + if (reverseZ) { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = (2.f * near) / (far - near); + matrix[14] = ((2.f * near) * far) / (far - near); + } + else { + matrix[2] = 0; + matrix[6] = 0; + matrix[10] = -(far + near) / (far - near); + matrix[14] = -(far * (2.f * near)) / (far - near); + } + + matrix[3] = 0; + matrix[7] = 0; + matrix[11] = -1; + matrix[15] = 0; + + return osg::Matrix(matrix); + } + + bool FieldOfView::operator==(const FieldOfView& rhs) const + { + return angleDown == rhs.angleDown + && angleUp == rhs.angleUp + && angleLeft == rhs.angleLeft + && angleRight == rhs.angleRight; + } + + bool View::operator==(const View& rhs) const + { + return pose == rhs.pose && fov == rhs.fov; + } + + std::ostream& operator <<( + std::ostream& os, + const Pose& pose) + { + os << "position=" << pose.position << ", orientation=" << pose.orientation; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const FieldOfView& fov) + { + os << "left=" << fov.angleLeft << ", right=" << fov.angleRight << ", down=" << fov.angleDown << ", up=" << fov.angleUp; + return os; + } + + std::ostream& operator <<( + std::ostream& os, + const View& view) + { + os << "pose=< " << view.pose << " >, fov=< " << view.fov << " >"; + return os; + } +} diff --git a/components/stereo/types.hpp b/components/stereo/types.hpp new file mode 100644 index 0000000000..9f2aa074d5 --- /dev/null +++ b/components/stereo/types.hpp @@ -0,0 +1,61 @@ +#ifndef STEREO_TYPES_H +#define STEREO_TYPES_H + +#include +#include + + +namespace Stereo +{ + enum class Eye + { + Left = 0, + Right = 1, + Center = 2 + }; + + struct Pose + { + //! Position in space + osg::Vec3 position{ 0,0,0 }; + //! Orientation in space. + osg::Quat orientation{ 0,0,0,1 }; + + //! Add one pose to another + Pose operator+(const Pose& rhs); + const Pose& operator+=(const Pose& rhs); + + //! Scale a pose (does not affect orientation) + Pose operator*(float scalar); + const Pose& operator*=(float scalar); + Pose operator/(float scalar); + const Pose& operator/=(float scalar); + + bool operator==(const Pose& rhs) const; + }; + + struct FieldOfView { + float angleLeft{ 0.f }; + float angleRight{ 0.f }; + float angleUp{ 0.f }; + float angleDown{ 0.f }; + + bool operator==(const FieldOfView& rhs) const; + }; + + struct View + { + Pose pose; + FieldOfView fov; + bool operator==(const View& rhs) const; + + osg::Matrix viewMatrix(bool useGLConventions); + osg::Matrix perspectiveMatrix(float near, float far, bool reverseZ); + }; + + std::ostream& operator <<(std::ostream& os, const Pose& pose); + std::ostream& operator <<(std::ostream& os, const FieldOfView& fov); + std::ostream& operator <<(std::ostream& os, const View& view); +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 6ae1a970d3..8e892a08c8 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -251,6 +252,8 @@ namespace Terrain defineMap["specularMap"] = it->mSpecular ? "1" : "0"; defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + Stereo::Manager::instance().shaderStereoDefines(defineMap); + osg::ref_ptr vertexShader = shaderManager->getShader("terrain_vertex.glsl", defineMap, osg::Shader::VERTEX); osg::ref_ptr fragmentShader = shaderManager->getShader("terrain_fragment.glsl", defineMap, osg::Shader::FRAGMENT); if (!vertexShader || !fragmentShader) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 50214668d1..ec03720dfe 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1129,3 +1129,60 @@ lua debug = false # Set the maximum number of threads used for Lua scripts. # If zero, Lua scripts are processed in the main thread. lua num threads = 1 + +[Stereo] +# Enable/disable stereo view. This setting is ignored in VR. +stereo enabled = false + +# If enabled, OpenMW will use the GL_OVR_MultiView and GL_OVR_MultiView2 extensions where possible. +multiview = false + +# May accelerate the BruteForce method when shadows are enabled +shared shadow maps = true + +# If false, OpenMW-VR will disable display lists when using multiview. Necessary on some buggy drivers, but may incur a slight performance penalty. +allow display lists for multiview = false + +# If false, the default OSG horizontal split will be used for stereo +# If true, the config defined in the [Stereo View] settings category will be used +# Note: This option is ignored in VR, and exists primarily for debugging purposes +use custom view = false + +# If true, overrides rendering resolution for each eye. +# Note: This option is ignored in VR, and exists primarily for debugging purposes +use custom eye resolution = false + +[Stereo View] +# The default values are based on an HP Reverb G2 HMD +eye resolution x = 3128 +eye resolution y = 3060 + +# Left eye offset from center, expressed in MW units (1 meter = ~70) +left eye offset x = -2.35 +left eye offset y = 0.0 +left eye offset z = 0.0 +# Left eye orientation, expressed as a quaternion +left eye orientation x = 0.0 +left eye orientation y = 0.0 +left eye orientation z = 0.0 +left eye orientation w = 1.0 +# Left eye field of view, expressed in radians +left eye fov left = -0.86 +left eye fov right = 0.78 +left eye fov up = 0.8 +left eye fov down = -0.8 + +# Left eye offset from center, expressed in MW units (1 meter = ~70) +right eye offset x = 2.35 +right eye offset y = 0.0 +right eye offset z = 0.0 +# Left eye orientation, expressed as a quaternion +right eye orientation x = 0.0 +right eye orientation y = 0.0 +right eye orientation z = 0.0 +right eye orientation w = 1.0 +# Left eye field of view +right eye fov left = -0.78 +right eye fov right = 0.86 +right eye fov up = 0.8 +right eye fov down = -0.8 \ No newline at end of file diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index c873ada12f..30909d4311 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -40,6 +40,8 @@ set(SHADER_FILES gui_fragment.glsl debug_vertex.glsl debug_fragment.glsl + multiview_fragment.glsl + multiview_vertex.glsl sky_vertex.glsl sky_fragment.glsl skypasses.glsl diff --git a/files/shaders/debug_fragment.glsl b/files/shaders/debug_fragment.glsl index 1b25510d66..ab6ad813ee 100644 --- a/files/shaders/debug_fragment.glsl +++ b/files/shaders/debug_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "vertexcolors.glsl" diff --git a/files/shaders/debug_vertex.glsl b/files/shaders/debug_vertex.glsl index fd41a6ff48..5331859122 100644 --- a/files/shaders/debug_vertex.glsl +++ b/files/shaders/debug_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl" diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index d669634190..c7e6ec36aa 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/groundcover_vertex.glsl b/files/shaders/groundcover_vertex.glsl index e707954e81..660a4624f0 100644 --- a/files/shaders/groundcover_vertex.glsl +++ b/files/shaders/groundcover_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/gui_fragment.glsl b/files/shaders/gui_fragment.glsl index a8c9434711..7c0a3b4811 100644 --- a/files/shaders/gui_fragment.glsl +++ b/files/shaders/gui_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion uniform sampler2D diffuseMap; diff --git a/files/shaders/gui_vertex.glsl b/files/shaders/gui_vertex.glsl index b378b097bd..670de5b6e3 100644 --- a/files/shaders/gui_vertex.glsl +++ b/files/shaders/gui_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 diffuseMapUV; varying vec4 passColor; diff --git a/files/shaders/multiview_fragment.glsl b/files/shaders/multiview_fragment.glsl new file mode 100644 index 0000000000..662dbe5f55 --- /dev/null +++ b/files/shaders/multiview_fragment.glsl @@ -0,0 +1,48 @@ +#ifndef MULTIVIEW_FRAGMENT +#define MULTIVIEW_FRAGMENT + +// This file either enables or disables GL_OVR_multiview2 related code. +// For use in fragment shaders + +// REQUIREMENT: +// GLSL version: 330 or greater +// GLSL profile: compatibility +// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. +// +// This file provides symbols for sampling stereo-aware textures. Without multiview, these texture uniforms are sampler2D, +// while in stereo the same uniforms are sampler2DArray instead. The symbols defined in this file mask this difference, allowing +// the same code to work in both cases. Use mw_stereoAwareSampler2D and mw_stereoAwareTexture2D, where you otherwise would use +// sampler2D and texture2D() +// +// USAGE: +// For stereo-aware textures, such as reflections, use the mw_stereoAwareSampler2D sampler and mw_stereoAwareTexture2D method +// instead of the usual sampler2D and texture2D. +// +// Using water reflection as an example, the old code for these textures changes from +// uniform sampler2D reflectionMap; +// ... +// vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; +// +// to +// uniform mw_stereoAwareSampler2D reflectionMap; +// ... +// vec3 reflection = mw_stereoAwareTexture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb; +// + +#if @useOVR_multiview + +#extension GL_OVR_multiview : require +#extension GL_OVR_multiview2 : require +#extension GL_EXT_texture_array : require + +#define mw_stereoAwareSampler2D sampler2DArray +#define mw_stereoAwareTexture2D(texture, uv) texture2DArray(texture, vec3((uv), gl_ViewID_OVR)) + +#else // useOVR_multiview + +#define mw_stereoAwareSampler2D sampler2D +#define mw_stereoAwareTexture2D(texture, uv) texture2D(texture, uv) + +#endif // useOVR_multiview + +#endif // MULTIVIEW_FRAGMENT \ No newline at end of file diff --git a/files/shaders/multiview_vertex.glsl b/files/shaders/multiview_vertex.glsl new file mode 100644 index 0000000000..0cabeb84a2 --- /dev/null +++ b/files/shaders/multiview_vertex.glsl @@ -0,0 +1,80 @@ +#ifndef MULTIVIEW_VERTEX +#define MULTIVIEW_VERTEX + +// This file either enables or disables GL_OVR_multiview related code. +// For use in vertex shaders + +// REQUIREMENT: +// GLSL version: 330 or greater +// GLSL profile: compatibility +// NOTE: If stereo is enabled using Misc::StereoView::shaderStereoDefines, version 330 compatibility (or greater) will be set. + +// USAGE: +// To create a stereo-aware vertex shader, use the matrix accessor functions defined in this .glsl file to compute gl_Position. +// For the vertex stage, usually only gl_Position needs to be computed with stereo awareness, while other variables such as viewPos +// should be computed in the center camera's view space and take no stereo awareness. +// +// A typical gl_Position line will look like the following: +// gl_Position = mw_stereoAwareProjectionMatrix() * (mw_stereoAwareModelViewMatrix() * gl_Vertex); +// +// If you need to perform intermediate computations before determining the final values of gl_Position and viewPos, +// your code might look more like the following: +// vec4 intermediateViewPos = gl_ModelViewMatrix * gl_Vertex; +// vec4 viewPos = myWhateverCode(intermediateViewPos); +// gl_Position = mw_stereoAwareProjectionMatrix() * mw_stereoAwareViewPosition(viewPos); +// + +#if @useOVR_multiview + +#extension GL_OVR_multiview : require + +#ifndef MULTIVIEW_FRAGMENT +// Layout cannot be used in the fragment shader +layout(num_views = @numViews) in; +#endif + +uniform mat4 projectionMatrixMultiView[@numViews]; +uniform mat4 viewMatrixMultiView[@numViews]; + +// NOTE: +// stereo-aware inverse view matrices and normal matrices have not been implemented. +// Some effects like specular highlights would need stereo aware normal matrices to be 100% correct. +// But the difference is not likely to be noticeable unless you're actively looking for it. + +mat4 mw_stereoAwareProjectionMatrix() +{ + return projectionMatrixMultiView[gl_ViewID_OVR]; +} + +mat4 mw_stereoAwareModelViewMatrix() +{ + return viewMatrixMultiView[gl_ViewID_OVR] * gl_ModelViewMatrix; +} + +vec4 mw_stereoAwareViewPosition(vec4 viewPos) +{ + return viewMatrixMultiView[gl_ViewID_OVR] * viewPos; +} + +#else // useOVR_multiview + +uniform mat4 projectionMatrix; + +mat4 mw_stereoAwareProjectionMatrix() +{ + return projectionMatrix; +} + +mat4 mw_stereoAwareModelViewMatrix() +{ + return gl_ModelViewMatrix; +} + +vec4 mw_stereoAwareViewPosition(vec4 viewPos) +{ + return viewPos; +} + +#endif // useOVR_multiview + +#endif // MULTIVIEW_VERTEX \ No newline at end of file diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index ff81a2b94d..e4d8046662 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/nv_default_vertex.glsl b/files/shaders/nv_default_vertex.glsl index c0d28f3e34..2554677018 100644 --- a/files/shaders/nv_default_vertex.glsl +++ b/files/shaders/nv_default_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 7c4f4737e0..116677318a 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useGPUShader4 diff --git a/files/shaders/nv_nolighting_vertex.glsl b/files/shaders/nv_nolighting_vertex.glsl index 7b1f6961b4..a3d884d026 100644 --- a/files/shaders/nv_nolighting_vertex.glsl +++ b/files/shaders/nv_nolighting_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl" diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 85950c7468..7e7ef647cc 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #pragma import_defines(FORCE_OPAQUE) #if @useUBO diff --git a/files/shaders/objects_vertex.glsl b/files/shaders/objects_vertex.glsl index e8976692a2..4764e63ce4 100644 --- a/files/shaders/objects_vertex.glsl +++ b/files/shaders/objects_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - #if @diffuseMap varying vec2 diffuseMapUV; #endif diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index f52e1478ee..f5f623c828 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 uv; uniform samplerCube cubeMap; diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index ad96620c3f..375dcda0ba 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 uv; diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 07fad047e1..186934fec2 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index e36f21a4de..25d5c402d7 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion varying vec2 diffuseMapUV; diff --git a/files/shaders/sky_fragment.glsl b/files/shaders/sky_fragment.glsl index cfa3650c02..0f2ff938c2 100644 --- a/files/shaders/sky_fragment.glsl +++ b/files/shaders/sky_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "skypasses.glsl" diff --git a/files/shaders/sky_vertex.glsl b/files/shaders/sky_vertex.glsl index 8ff9c0f156..5b6a99be01 100644 --- a/files/shaders/sky_vertex.glsl +++ b/files/shaders/sky_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #include "openmw_vertex.h.glsl" @@ -9,6 +11,20 @@ uniform int pass; varying vec4 passColor; varying vec2 diffuseMapUV; +mat4 selectModelViewMatrix() +{ +#if @useOVR_multiview + mat4 viewOffsetMatrix = viewMatrixMultiView[gl_ViewID_OVR]; + // Sky geometries aren't actually all that distant. So delete view translation to keep them looking distant. + viewOffsetMatrix[3][0] = 0; + viewOffsetMatrix[3][1] = 0; + viewOffsetMatrix[3][2] = 0; + return viewOffsetMatrix * gl_ModelViewMatrix; +#else + return gl_ModelViewMatrix; +#endif +} + void main() { gl_Position = mw_modelToClip(gl_Vertex); diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index d9d4a6dc30..e5532f9bce 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/terrain_vertex.glsl b/files/shaders/terrain_vertex.glsl index 5519001219..abceb019b7 100644 --- a/files/shaders/terrain_vertex.glsl +++ b/files/shaders/terrain_vertex.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_vertex.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -9,7 +11,6 @@ #endif #include "openmw_vertex.h.glsl" - varying vec2 uv; varying float euclideanDepth; varying float linearDepth; diff --git a/files/shaders/water_fragment.glsl b/files/shaders/water_fragment.glsl index bf35ca78ca..87645f2135 100644 --- a/files/shaders/water_fragment.glsl +++ b/files/shaders/water_fragment.glsl @@ -1,4 +1,6 @@ -#version 120 +#version @GLSLVersion + +#include "multiview_fragment.glsl" #if @useUBO #extension GL_ARB_uniform_buffer_object : require diff --git a/files/shaders/water_vertex.glsl b/files/shaders/water_vertex.glsl index b09d3b54ae..e3dd29d2a0 100644 --- a/files/shaders/water_vertex.glsl +++ b/files/shaders/water_vertex.glsl @@ -1,4 +1,4 @@ -#version 120 +#version @GLSLVersion #include "openmw_vertex.h.glsl"