#include "multiview.hpp" #include #include #include #include #include #include #include #ifdef OSG_HAS_MULTIVIEW #include #endif #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 (!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) { if (getStereo() && enableMultiview) { 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; } 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 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->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(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(); 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(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(attachment.getTexture()); mMsaaLayers[i]->setAttachment(component, osg::FrameBufferAttachment(texture, i)); mWidth = texture->getTextureWidth(); mHeight = texture->getTextureHeight(); } } } osg::ref_ptr mFbo; osg::ref_ptr mMsaaFbo; osg::ref_ptr mLayers[2]; osg::ref_ptr 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& 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(tex)->setTextureSize(w, h); break; case GL_TEXTURE_2D_ARRAY: static_cast(tex)->setTextureSize(w, h, 2); break; case GL_TEXTURE_2D_MULTISAMPLE: static_cast(tex)->setTextureSize(w, h); break; #ifdef OSG_HAS_MULTIVIEW case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: static_cast(tex)->setTextureSize(w, h, 2); break; #endif default: throw std::logic_error("Invalid texture type received"); } } osg::ref_ptr 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(tex); return osg::FrameBufferAttachment(tex2d); } case GL_TEXTURE_2D_MULTISAMPLE: { auto* tex2dMsaa = static_cast(tex); return osg::FrameBufferAttachment(tex2dMsaa); } #ifdef OSG_HAS_MULTIVIEW case GL_TEXTURE_2D_ARRAY: { auto* tex2dArray = static_cast(tex); return osg::FrameBufferAttachment(tex2dArray, osg::Camera::FACE_CONTROLLED_BY_MULTIVIEW_SHADER, 0); } case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: { auto* tex2dMsaaArray = static_cast(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 { 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) 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(tex), layer, 0); #ifdef OSG_HAS_MULTIVIEW if (tex->getTextureTarget() == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) return osg::FrameBufferAttachment(static_cast(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(); 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 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 camera; if (mCamera.lock(camera)) camera->setInitialFrustumCallback(nullptr); #endif } }