diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 3fea60f216..581209eddb 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -130,11 +130,24 @@ namespace MWRender state.pushStateSet(mFallbackStateSet); state.apply(); + + if (Stereo::getMultiview() && mMultiviewResolveProgram) + { + state.pushStateSet(mMultiviewResolveStateSet); + state.apply(); + } + state.applyTextureAttribute(0, bufferData.sceneTex); resolveViewport->apply(state); drawGeometry(renderInfo); state.popStateSet(); + + if (Stereo::getMultiview() && mMultiviewResolveProgram) + { + state.popStateSet(); + } + return; } diff --git a/apps/openmw/mwrender/pingpongcull.cpp b/apps/openmw/mwrender/pingpongcull.cpp index 7c3f774a61..035f0c3011 100644 --- a/apps/openmw/mwrender/pingpongcull.cpp +++ b/apps/openmw/mwrender/pingpongcull.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -12,6 +14,7 @@ namespace MWRender { + PingPongCull::PingPongCull(PostProcessor* pp) : mViewportStateset(nullptr) , mPostProcessor(pp) @@ -66,6 +69,11 @@ namespace MWRender { renderStage->setMultisampleResolveFramebufferObject(postProcessor->getFbo(PostProcessor::FBO_Primary, frameId)); renderStage->setFrameBufferObject(postProcessor->getFbo(PostProcessor::FBO_Multisample, frameId)); + + // The MultiView patch has a bug where it does not update resolve layers if the resolve framebuffer is changed. + // So we do blit manually in this case + if (Stereo::getMultiview() && !renderStage->getDrawCallback()) + Stereo::setMultiviewMSAAResolveCallback(renderStage); } if (mViewportStateset) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index fd3b25ae2c..2a046cb1ac 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -86,6 +86,14 @@ namespace 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"); } @@ -233,7 +241,7 @@ namespace MWRender mUBO = ext && ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); - if (!SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing) + if (!Stereo::getStereo() && !SceneUtil::AutoDepth::isReversed() && !mSoftParticles && !mUsePostProcessing) return; enable(mUsePostProcessing); diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index c5963632ce..0011b93687 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -7,6 +7,10 @@ #include #include +#ifdef OSG_HAS_MULTIVIEW +#include +#endif + #include #include #include @@ -242,6 +246,97 @@ namespace Stereo 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 + } + class UpdateRenderStagesCallback : public SceneUtil::NodeCallback { public: diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index cfdc721d1a..3212ecc02c 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -16,6 +16,11 @@ namespace osg class Texture2DArray; } +namespace osgUtil +{ + class RenderStage; +} + namespace Stereo { class UpdateRenderStagesCallback; @@ -35,6 +40,9 @@ namespace Stereo //! Creates a Texture2D as a texture view into a Texture2DArray osg::ref_ptr createTextureView_Texture2DFromTexture2DArray(osg::Texture2DArray* textureArray, int layer); + //! Sets up a draw callback on the render stage that performs the MSAA resolve operation + void setMultiviewMSAAResolveCallback(osgUtil::RenderStage* renderStage); + //! Class that manages the specifics of GL_OVR_Multiview aware framebuffers, separating the layers into separate framebuffers, and disabling class MultiviewFramebuffer { @@ -77,6 +85,7 @@ namespace Stereo std::array, 2> mColorTexture; std::array, 2> mDepthTexture; }; + } #endif diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index 645fc59f29..829cb6cc9c 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -69,7 +70,6 @@ namespace Stereo void applyLeft(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { - osg::Matrix dummy; auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeViewOffset(0) * mManager->computeEyeProjection(0, SceneUtil::AutoDepth::isReversed())); @@ -77,7 +77,6 @@ namespace Stereo void applyRight(osg::StateSet* stateset, osgUtil::CullVisitor* nv) override { - osg::Matrix dummy; auto* uProjectionMatrix = stateset->getUniform("projectionMatrix"); if (uProjectionMatrix) uProjectionMatrix->set(mManager->computeEyeViewOffset(1) * mManager->computeEyeProjection(1, SceneUtil::AutoDepth::isReversed())); @@ -135,7 +134,7 @@ namespace Stereo : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) - , mMasterProjectionMatrix(osg::Matrix::identity()) + , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0,0) , mFrustumManager(nullptr) @@ -322,7 +321,6 @@ namespace Stereo near_ = Settings::Manager::getFloat("near clip", "Camera"); far_ = Settings::Manager::getFloat("viewing distance", "Camera"); - auto projectionMatrix = mMainCamera->getProjectionMatrix(); if (mUpdateViewCallback) { @@ -342,15 +340,25 @@ namespace Stereo 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); + auto projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else { auto* ds = osg::DisplaySettings::instance().get(); auto viewMatrix = mMainCamera->getViewMatrix(); - mViewOffsetMatrix[0] = osg::Matrix::inverse(viewMatrix) * ds->computeLeftEyeViewImplementation(viewMatrix); - mViewOffsetMatrix[1] = osg::Matrix::inverse(viewMatrix) * ds->computeRightEyeViewImplementation(viewMatrix); + auto projectionMatrix = mMainCamera->getProjectionMatrix(); + auto s = ds->getEyeSeparation() * Constants::UnitsPerMeter; + mViewOffsetMatrix[0] = osg::Matrixd( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + s, 0.0, 0.0, 1.0); + mViewOffsetMatrix[1] = osg::Matrixd( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + -s, 0.0, 0.0, 1.0); mProjectionMatrix[0] = ds->computeLeftEyeProjectionImplementation(projectionMatrix); mProjectionMatrix[1] = ds->computeRightEyeProjectionImplementation(projectionMatrix); if (SceneUtil::AutoDepth::isReversed()) diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 48af043fd7..6a873e00f3 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -87,7 +87,7 @@ namespace Stereo //! 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; } + void setMasterProjectionMatrix(const osg::Matrixd& 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 @@ -109,15 +109,15 @@ namespace Stereo osg::ref_ptr mMainCamera; osg::ref_ptr mUpdateCallback; std::string mError; - osg::Matrix mMasterProjectionMatrix; + osg::Matrixd mMasterProjectionMatrix; std::shared_ptr mMultiviewFramebuffer; bool mEyeResolutionOverriden; osg::Vec2i mEyeResolutionOverride; std::array mView; - std::array mViewOffsetMatrix; - std::array mProjectionMatrix; - std::array mProjectionMatrixReverseZ; + std::array mViewOffsetMatrix; + std::array mProjectionMatrix; + std::array mProjectionMatrixReverseZ; std::unique_ptr mFrustumManager; std::shared_ptr mUpdateViewCallback;