#include "postprocessor.hpp" #include #include #include #include #include #include #include #include #include #include #include "vismask.hpp" #include "renderingmanager.hpp" namespace { osg::ref_ptr createFullScreenTri() { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; verts->push_back(osg::Vec3f(-1, -1, 0)); verts->push_back(osg::Vec3f(-1, 3, 0)); verts->push_back(osg::Vec3f(3, -1, 0)); geom->setVertexArray(verts); geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); return geom; } class CullCallback : public SceneUtil::NodeCallback { public: CullCallback() : mLastFrameNumber(0) { } void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); unsigned int frame = cv->getTraversalNumber(); if (frame != mLastFrameNumber) { 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()); } } traverse(node, cv); } private: unsigned int mLastFrameNumber; }; struct ResizedCallback : osg::GraphicsContext::ResizedCallback { ResizedCallback(MWRender::PostProcessor* postProcessor) : mPostProcessor(postProcessor) { } void resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height) override { gc->resizedImplementation(x, y, width, height); mPostProcessor->resize(width, height); } MWRender::PostProcessor* mPostProcessor; }; } namespace MWRender { PostProcessor::PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode) : mViewer(viewer) , mRootNode(new osg::Group) , mDepthFormat(GL_DEPTH_COMPONENT24) , mRendering(rendering) { if (!SceneUtil::getReverseZ()) 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: "; if (!ext->isFrameBufferObjectSupported) { Log(Debug::Warning) << errPreamble << "FrameBufferObject unsupported."; return; } if (Settings::Manager::getInt("antialiasing", "Video") > 1 && !ext->isRenderbufferMultisampleSupported()) { Log(Debug::Warning) << errPreamble << "RenderBufferMultiSample unsupported. Disabling antialiasing will resolve this issue."; return; } if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float")) mDepthFormat = GL_DEPTH_COMPONENT32F; else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float")) mDepthFormat = GL_DEPTH_COMPONENT32F_NV; else { // 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."; return; } int width = viewer->getCamera()->getViewport()->width(); int height = viewer->getCamera()->getViewport()->height(); createTexturesAndCamera(width, height); resize(width, height); mRootNode->addChild(mHUDCamera); 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); mViewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, mDepthTex); mViewer->getCamera()->getGraphicsContext()->setResizedCallback(new ResizedCallback(this)); mViewer->getCamera()->setUserData(this); } void PostProcessor::resize(int width, int height) { mDepthTex->setTextureSize(width, height); mSceneTex->setTextureSize(width, height); mDepthTex->dirtyTextureObject(); mSceneTex->dirtyTextureObject(); int samples = Settings::Manager::getInt("antialiasing", "Video"); mFbo = new osg::FrameBufferObject; mFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(mSceneTex)); mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(mDepthTex)); // When MSAA is enabled we must first render to a render buffer, then // blit the result to the FBO which is either passed to the main frame // buffer for display or used as the entry point for a post process chain. if (samples > 1) { mMsaaFbo = new osg::FrameBufferObject; osg::ref_ptr colorRB = new osg::RenderBuffer(width, height, mSceneTex->getInternalFormat(), samples); osg::ref_ptr depthRB = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); mMsaaFbo->setAttachment(osg::Camera::COLOR_BUFFER0, osg::FrameBufferAttachment(colorRB)); mMsaaFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthRB)); } if (const auto depthProxy = std::getenv("OPENMW_ENABLE_DEPTH_CLEAR_PROXY")) mFirstPersonDepthRBProxy = new osg::RenderBuffer(width, height, mDepthTex->getInternalFormat(), samples); mViewer->getCamera()->resize(width, height); mHUDCamera->resize(width, height); mRendering.updateProjectionMatrix(); } void PostProcessor::createTexturesAndCamera(int width, int height) { mDepthTex = new osg::Texture2D; mDepthTex->setTextureSize(width, height); mDepthTex->setSourceFormat(GL_DEPTH_COMPONENT); mDepthTex->setSourceType(SceneUtil::isFloatingPointDepthFormat(getDepthFormat()) ? GL_FLOAT : GL_UNSIGNED_INT); mDepthTex->setInternalFormat(mDepthFormat); 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); mDepthTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mDepthTex->setResizeNonPowerOfTwoHint(false); mSceneTex = new osg::Texture2D; mSceneTex->setTextureSize(width, height); mSceneTex->setSourceFormat(GL_RGB); mSceneTex->setSourceType(GL_UNSIGNED_BYTE); mSceneTex->setInternalFormat(GL_RGB); 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); mSceneTex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); mSceneTex->setResizeNonPowerOfTwoHint(false); mHUDCamera = new osg::Camera; 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->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); mHUDCamera->setAllowEventFocus(false); mHUDCamera->setViewport(0, 0, width, height); // Shaders calculate correct UV coordinates for our fullscreen triangle constexpr char vertSrc[] = R"GLSL( #version 120 varying vec2 uv; void main() { gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0); uv = gl_Position.xy * 0.5 + 0.5; } )GLSL"; constexpr char fragSrc[] = R"GLSL( #version 120 varying vec2 uv; uniform sampler2D sceneTex; void main() { gl_FragData[0] = texture2D(sceneTex, 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 program = new osg::Program; program->addShader(vertShader); program->addShader(fragShader); 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); } }