mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 17:56:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			847 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			847 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "multiview.hpp"
 | |
| 
 | |
| #include <osg/FrameBufferObject>
 | |
| #include <osg/GLExtensions>
 | |
| #include <osg/Texture2D>
 | |
| #include <osg/Texture2DArray>
 | |
| #include <osg/Texture2DMultisample>
 | |
| #include <osgUtil/CullVisitor>
 | |
| #include <osgUtil/RenderStage>
 | |
| 
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
| #include <osg/Texture2DMultisampleArray>
 | |
| #endif
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/sceneutil/nodecallback.hpp>
 | |
| #include <components/settings/settings.hpp>
 | |
| #include <components/stereo/stereomanager.hpp>
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| 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 (!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;
 | |
|         }
 | |
| 
 | |
|         static bool sMultiview = false;
 | |
| 
 | |
|         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, bool enableMultiview)
 | |
|     {
 | |
|         getTextureViewSupported(contextID);
 | |
|         getMultiviewSupported(contextID);
 | |
| 
 | |
|         if (enableMultiview)
 | |
|         {
 | |
|             sMultiview = getMultiview(contextID);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             Log(Debug::Verbose) << "Disabling Multiview (disabled by config)";
 | |
|             sMultiview = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview)
 | |
|     {
 | |
|         if (getStereo() && enableMultiview)
 | |
|         {
 | |
|             auto* ds = osg::DisplaySettings::instance().get();
 | |
|             if (!allowDisplayListsForMultiview
 | |
|                 && 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<osg::Texture2DArray> 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;
 | |
|         }
 | |
| 
 | |
|         osg::Texture::TextureObject* const targetTextureObject = texture.getTextureObject(contextId);
 | |
|         if (targetTextureObject == nullptr)
 | |
|         {
 | |
|             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<osg::Texture2D> 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<osg::Texture2D> texture2d = new osg::Texture2D;
 | |
|         texture2d->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
 | |
|         texture2d->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
 | |
|         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;
 | |
|     }
 | |
| 
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|     //! Draw callback that, if set on a RenderStage, resolves MSAA after draw. Needed when using custom fbo/resolve fbos
 | |
|     //! on renderstages in combination with multiview.
 | |
|     struct MultiviewMSAAResolveCallback : public osgUtil::RenderBin::DrawCallback
 | |
|     {
 | |
|         void drawImplementation(
 | |
|             osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override
 | |
|         {
 | |
|             osgUtil::RenderStage* stage = static_cast<osgUtil::RenderStage*>(bin);
 | |
|             auto msaaFbo = stage->getFrameBufferObject();
 | |
|             auto resolveFbo = stage->getMultisampleResolveFramebufferObject();
 | |
|             if (msaaFbo != mMsaaFbo)
 | |
|             {
 | |
|                 mMsaaFbo = msaaFbo;
 | |
|                 setupMsaaLayers();
 | |
|             }
 | |
|             if (resolveFbo != mFbo)
 | |
|             {
 | |
|                 mFbo = resolveFbo;
 | |
|                 setupLayers();
 | |
|             }
 | |
| 
 | |
|             // Null the resolve framebuffer to keep osg from doing redundant work.
 | |
|             stage->setMultisampleResolveFramebufferObject(nullptr);
 | |
| 
 | |
|             // Do the actual render work
 | |
|             bin->drawImplementation(renderInfo, previous);
 | |
| 
 | |
|             // Blit layers
 | |
|             osg::State& state = *renderInfo.getState();
 | |
|             osg::GLExtensions* ext = state.get<osg::GLExtensions>();
 | |
|             for (int i = 0; i < 2; i++)
 | |
|             {
 | |
|                 mLayers[i]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER);
 | |
|                 mMsaaLayers[i]->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER);
 | |
|                 ext->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight,
 | |
|                     GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, GL_NEAREST);
 | |
|             }
 | |
|             msaaFbo->apply(state, osg::FrameBufferObject::READ_DRAW_FRAMEBUFFER);
 | |
|         }
 | |
| 
 | |
|         void setupLayers()
 | |
|         {
 | |
|             const auto& attachments = mFbo->getAttachmentMap();
 | |
|             for (int i = 0; i < 2; i++)
 | |
|             {
 | |
|                 mLayers[i] = new osg::FrameBufferObject;
 | |
|                 // Intentionally not using ref& so attachment can be non-const
 | |
|                 for (auto [component, attachment] : attachments)
 | |
|                 {
 | |
|                     osg::Texture2DArray* texture = static_cast<osg::Texture2DArray*>(attachment.getTexture());
 | |
|                     mLayers[i]->setAttachment(component, osg::FrameBufferAttachment(texture, i));
 | |
|                     mWidth = texture->getTextureWidth();
 | |
|                     mHeight = texture->getTextureHeight();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void setupMsaaLayers()
 | |
|         {
 | |
|             const auto& attachments = mMsaaFbo->getAttachmentMap();
 | |
|             for (int i = 0; i < 2; i++)
 | |
|             {
 | |
|                 mMsaaLayers[i] = new osg::FrameBufferObject;
 | |
|                 // Intentionally not using ref& so attachment can be non-const
 | |
|                 for (auto [component, attachment] : attachments)
 | |
|                 {
 | |
|                     osg::Texture2DMultisampleArray* texture
 | |
|                         = static_cast<osg::Texture2DMultisampleArray*>(attachment.getTexture());
 | |
|                     mMsaaLayers[i]->setAttachment(component, osg::FrameBufferAttachment(texture, i));
 | |
|                     mWidth = texture->getTextureWidth();
 | |
|                     mHeight = texture->getTextureHeight();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         osg::ref_ptr<osg::FrameBufferObject> mFbo;
 | |
|         osg::ref_ptr<osg::FrameBufferObject> mMsaaFbo;
 | |
|         osg::ref_ptr<osg::FrameBufferObject> mLayers[2];
 | |
|         osg::ref_ptr<osg::FrameBufferObject> mMsaaLayers[2];
 | |
|         int mWidth;
 | |
|         int mHeight;
 | |
|     };
 | |
| #endif
 | |
| 
 | |
|     void setMultiviewMSAAResolveCallback(osgUtil::RenderStage* renderStage)
 | |
|     {
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         if (Stereo::getMultiview())
 | |
|         {
 | |
|             renderStage->setDrawCallback(new MultiviewMSAAResolveCallback);
 | |
|         }
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     void setMultiviewMatrices(
 | |
|         osg::StateSet* stateset, const std::array<osg::Matrix, 2>& projection, bool createInverseMatrices)
 | |
|     {
 | |
|         auto* projUniform = stateset->getUniform("projectionMatrixMultiView");
 | |
|         if (!projUniform)
 | |
|         {
 | |
|             projUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "projectionMatrixMultiView", 2);
 | |
|             stateset->addUniform(projUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
 | |
|         }
 | |
| 
 | |
|         projUniform->setElement(0, projection[0]);
 | |
|         projUniform->setElement(1, projection[1]);
 | |
| 
 | |
|         if (createInverseMatrices)
 | |
|         {
 | |
|             auto* invUniform = stateset->getUniform("invProjectionMatrixMultiView");
 | |
|             if (!invUniform)
 | |
|             {
 | |
|                 invUniform = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "invProjectionMatrixMultiView", 2);
 | |
|                 stateset->addUniform(invUniform, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
 | |
|             }
 | |
| 
 | |
|             invUniform->setElement(0, osg::Matrix::inverse(projection[0]));
 | |
|             invUniform->setElement(1, osg::Matrix::inverse(projection[1]));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void setMultiviewCompatibleTextureSize(osg::Texture* tex, int w, int h)
 | |
|     {
 | |
|         switch (tex->getTextureTarget())
 | |
|         {
 | |
|             case GL_TEXTURE_2D:
 | |
|                 static_cast<osg::Texture2D*>(tex)->setTextureSize(w, h);
 | |
|                 break;
 | |
|             case GL_TEXTURE_2D_ARRAY:
 | |
|                 static_cast<osg::Texture2DArray*>(tex)->setTextureSize(w, h, 2);
 | |
|                 break;
 | |
|             case GL_TEXTURE_2D_MULTISAMPLE:
 | |
|                 static_cast<osg::Texture2DMultisample*>(tex)->setTextureSize(w, h);
 | |
|                 break;
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|             case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
 | |
|                 static_cast<osg::Texture2DMultisampleArray*>(tex)->setTextureSize(w, h, 2);
 | |
|                 break;
 | |
| #endif
 | |
|             default:
 | |
|                 throw std::logic_error("Invalid texture type received");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Texture> createMultiviewCompatibleTexture(int width, int height, int samples)
 | |
|     {
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         if (Stereo::getMultiview())
 | |
|         {
 | |
|             if (samples > 1)
 | |
|             {
 | |
|                 auto tex = new osg::Texture2DMultisampleArray();
 | |
|                 tex->setTextureSize(width, height, 2);
 | |
|                 tex->setNumSamples(samples);
 | |
|                 return tex;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 auto tex = new osg::Texture2DArray();
 | |
|                 tex->setTextureSize(width, height, 2);
 | |
|                 return tex;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
| #endif
 | |
|         {
 | |
|             if (samples > 1)
 | |
|             {
 | |
|                 auto tex = new osg::Texture2DMultisample();
 | |
|                 tex->setTextureSize(width, height);
 | |
|                 tex->setNumSamples(samples);
 | |
|                 tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
 | |
|                 tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
 | |
|                 return tex;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 auto tex = new osg::Texture2D();
 | |
|                 tex->setTextureSize(width, height);
 | |
|                 tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
 | |
|                 tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
 | |
|                 return tex;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     osg::FrameBufferAttachment createMultiviewCompatibleAttachment(osg::Texture* tex)
 | |
|     {
 | |
|         switch (tex->getTextureTarget())
 | |
|         {
 | |
|             case GL_TEXTURE_2D:
 | |
|             {
 | |
|                 auto* tex2d = static_cast<osg::Texture2D*>(tex);
 | |
|                 return osg::FrameBufferAttachment(tex2d);
 | |
|             }
 | |
|             case GL_TEXTURE_2D_MULTISAMPLE:
 | |
|             {
 | |
|                 auto* tex2dMsaa = static_cast<osg::Texture2DMultisample*>(tex);
 | |
|                 return osg::FrameBufferAttachment(tex2dMsaa);
 | |
|             }
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|             case GL_TEXTURE_2D_ARRAY:
 | |
|             {
 | |
|                 auto* tex2dArray = static_cast<osg::Texture2DArray*>(tex);
 | |
|                 return osg::FrameBufferAttachment(tex2dArray, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0);
 | |
|             }
 | |
|             case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
 | |
|             {
 | |
|                 auto* tex2dMsaaArray = static_cast<osg::Texture2DMultisampleArray*>(tex);
 | |
|                 return osg::FrameBufferAttachment(tex2dMsaaArray, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0);
 | |
|             }
 | |
| #endif
 | |
|             default:
 | |
|                 throw std::logic_error("Invalid texture type received");
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     unsigned int osgFaceControlledByMultiviewShader()
 | |
|     {
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         return osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER;
 | |
| #else
 | |
|         return 0;
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     class UpdateRenderStagesCallback
 | |
|         : public SceneUtil::NodeCallback<UpdateRenderStagesCallback, osg::Node*, osgUtil::CullVisitor*>
 | |
|     {
 | |
|     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<int>(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<osg::Viewport> mViewport;
 | |
|         osg::ref_ptr<osg::StateSet> 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)
 | |
|                     mLayerMsaaFbo[i]->setAttachment(osg::Camera::COLOR_BUFFER,
 | |
|                         osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
 | |
|                 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)
 | |
|                     mLayerMsaaFbo[i]->setAttachment(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER,
 | |
|                         osg::FrameBufferAttachment(new osg::RenderBuffer(mWidth, mHeight, internalFormat, mSamples)));
 | |
|                 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::Texture2DArray* MultiviewFramebuffer::multiviewDepthBuffer()
 | |
|     {
 | |
|         return mMultiviewDepthTexture;
 | |
|     }
 | |
| 
 | |
|     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::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;
 | |
|     }
 | |
| 
 | |
|     osg::FrameBufferAttachment makeSingleLayerAttachmentFromMultilayerAttachment(
 | |
|         osg::FrameBufferAttachment attachment, int layer)
 | |
|     {
 | |
|         osg::Texture* tex = attachment.getTexture();
 | |
| 
 | |
|         if (tex->getTextureTarget() == GL_TEXTURE_2D_ARRAY)
 | |
|             return osg::FrameBufferAttachment(static_cast<osg::Texture2DArray*>(tex), layer, 0);
 | |
| 
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         if (tex->getTextureTarget() == GL_TEXTURE_2D_MULTISAMPLE_ARRAY)
 | |
|             return osg::FrameBufferAttachment(static_cast<osg::Texture2DMultisampleArray*>(tex), layer, 0);
 | |
| #endif
 | |
| 
 | |
|         Log(Debug::Error) << "Attempted to extract a layer from an unlayered texture";
 | |
| 
 | |
|         return osg::FrameBufferAttachment();
 | |
|     }
 | |
| 
 | |
|     MultiviewFramebufferResolve::MultiviewFramebufferResolve(
 | |
|         osg::FrameBufferObject* msaaFbo, osg::FrameBufferObject* resolveFbo, GLbitfield blitMask)
 | |
|         : mResolveFbo(resolveFbo)
 | |
|         , mMsaaFbo(msaaFbo)
 | |
|         , mBlitMask(blitMask)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     void MultiviewFramebufferResolve::setResolveFbo(osg::FrameBufferObject* resolveFbo)
 | |
|     {
 | |
|         if (resolveFbo != mResolveFbo)
 | |
|             dirty();
 | |
|         mResolveFbo = resolveFbo;
 | |
|     }
 | |
| 
 | |
|     void MultiviewFramebufferResolve::setMsaaFbo(osg::FrameBufferObject* msaaFbo)
 | |
|     {
 | |
|         if (msaaFbo != mMsaaFbo)
 | |
|             dirty();
 | |
|         mMsaaFbo = msaaFbo;
 | |
|     }
 | |
| 
 | |
|     void MultiviewFramebufferResolve::resolveImplementation(osg::State& state)
 | |
|     {
 | |
|         if (mDirtyLayers)
 | |
|             setupLayers();
 | |
| 
 | |
|         osg::GLExtensions* ext = state.get<osg::GLExtensions>();
 | |
| 
 | |
|         for (int view : { 0, 1 })
 | |
|         {
 | |
|             mResolveLayers[view]->apply(state, osg::FrameBufferObject::BindTarget::DRAW_FRAMEBUFFER);
 | |
|             mMsaaLayers[view]->apply(state, osg::FrameBufferObject::BindTarget::READ_FRAMEBUFFER);
 | |
|             ext->glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
 | |
|         }
 | |
|     }
 | |
|     void MultiviewFramebufferResolve::setupLayers()
 | |
|     {
 | |
|         mDirtyLayers = false;
 | |
|         std::vector<osg::FrameBufferObject::BufferComponent> components;
 | |
|         if (mBlitMask & GL_DEPTH_BUFFER_BIT)
 | |
|             components.push_back(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER);
 | |
|         if (mBlitMask & GL_COLOR_BUFFER_BIT)
 | |
|             components.push_back(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER);
 | |
| 
 | |
|         mMsaaLayers = { new osg::FrameBufferObject, new osg::FrameBufferObject };
 | |
|         mResolveLayers = { new osg::FrameBufferObject, new osg::FrameBufferObject };
 | |
|         for (auto component : components)
 | |
|         {
 | |
|             const auto& msaaAttachment = mMsaaFbo->getAttachment(component);
 | |
|             mMsaaLayers[0]->setAttachment(
 | |
|                 component, makeSingleLayerAttachmentFromMultilayerAttachment(msaaAttachment, 0));
 | |
|             mMsaaLayers[1]->setAttachment(
 | |
|                 component, makeSingleLayerAttachmentFromMultilayerAttachment(msaaAttachment, 1));
 | |
| 
 | |
|             const auto& resolveAttachment = mResolveFbo->getAttachment(component);
 | |
|             mResolveLayers[0]->setAttachment(
 | |
|                 component, makeSingleLayerAttachmentFromMultilayerAttachment(resolveAttachment, 0));
 | |
|             mResolveLayers[1]->setAttachment(
 | |
|                 component, makeSingleLayerAttachmentFromMultilayerAttachment(resolveAttachment, 1));
 | |
| 
 | |
|             mWidth = msaaAttachment.getTexture()->getTextureWidth();
 | |
|             mHeight = msaaAttachment.getTexture()->getTextureHeight();
 | |
|         }
 | |
|     }
 | |
| 
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|     namespace
 | |
|     {
 | |
|         struct MultiviewFrustumCallback final : public osg::CullSettings::InitialFrustumCallback
 | |
|         {
 | |
|             MultiviewFrustumCallback(Stereo::InitialFrustumCallback* ifc)
 | |
|                 : mIfc(ifc)
 | |
|             {
 | |
|             }
 | |
| 
 | |
|             void setInitialFrustum(osg::CullStack& cullStack, osg::Polytope& frustum) const override
 | |
|             {
 | |
|                 bool nearCulling = false;
 | |
|                 bool farCulling = false;
 | |
|                 osg::BoundingBoxd bb;
 | |
|                 mIfc->setInitialFrustum(cullStack, bb, nearCulling, farCulling);
 | |
|                 frustum.setToBoundingBox(bb, nearCulling, farCulling);
 | |
|             }
 | |
| 
 | |
|             Stereo::InitialFrustumCallback* mIfc;
 | |
|         };
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     InitialFrustumCallback::InitialFrustumCallback(osg::Camera* camera)
 | |
|         : mCamera(camera)
 | |
|     {
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         camera->setInitialFrustumCallback(new MultiviewFrustumCallback(this));
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     InitialFrustumCallback::~InitialFrustumCallback()
 | |
|     {
 | |
| #ifdef OSG_HAS_MULTIVIEW
 | |
|         osg::ref_ptr<osg::Camera> camera;
 | |
|         if (mCamera.lock(camera))
 | |
|             camera->setInitialFrustumCallback(nullptr);
 | |
| #endif
 | |
|     }
 | |
| }
 |