diff --git a/apps/openmw/mwvr/vrsession.cpp b/apps/openmw/mwvr/vrsession.cpp index b99f60ddc..e22a6b8ae 100644 --- a/apps/openmw/mwvr/vrsession.cpp +++ b/apps/openmw/mwvr/vrsession.cpp @@ -163,7 +163,6 @@ namespace MWVR { if (frameMeta->mShouldRender) { - viewer.blit(gc); gc->swapBuffersImplementation(); std::array layerStack{}; layerStack[(int)Side::LEFT_SIDE].subImage = viewer.subImage(Side::LEFT_SIDE); diff --git a/apps/openmw/mwvr/vrviewer.cpp b/apps/openmw/mwvr/vrviewer.cpp index f5b321bf2..66ae94b43 100644 --- a/apps/openmw/mwvr/vrviewer.cpp +++ b/apps/openmw/mwvr/vrviewer.cpp @@ -10,6 +10,9 @@ #include "../mwrender/vismask.hpp" #include +#include +#include +#include #include @@ -44,6 +47,7 @@ namespace MWVR : mViewer(viewer) , mPreDraw(new PredrawCallback(this)) , mPostDraw(new PostdrawCallback(this)) + , mFinalDraw(new FinaldrawCallback(this)) , mUpdateViewCallback(new UpdateViewCallback(this)) , mMsaaResolveTexture{} , mMirrorTexture{ nullptr } @@ -126,13 +130,13 @@ namespace MWVR mSwapchainConfig[i].selectedWidth = parseResolution(xConfString[i], mSwapchainConfig[i].recommendedWidth, mSwapchainConfig[i].maxWidth); mSwapchainConfig[i].selectedHeight = parseResolution(yConfString[i], mSwapchainConfig[i].recommendedHeight, mSwapchainConfig[i].maxHeight); - mSwapchainConfig[i].selectedSamples = + mSwapchainConfig[i].selectedSamples = std::max(1, // OpenXR requires a non-zero value - std::min(mSwapchainConfig[i].maxSamples, + std::min(mSwapchainConfig[i].maxSamples, Settings::Manager::getInt("antialiasing", "Video") ) ); - + Log(Debug::Verbose) << name << " resolution: Recommended x=" << mSwapchainConfig[i].recommendedWidth << ", y=" << mSwapchainConfig[i].recommendedHeight; Log(Debug::Verbose) << name << " resolution: Max x=" << mSwapchainConfig[i].maxWidth << ", y=" << mSwapchainConfig[i].maxHeight; Log(Debug::Verbose) << name << " resolution: Selected x=" << mSwapchainConfig[i].selectedWidth << ", y=" << mSwapchainConfig[i].selectedHeight; @@ -152,10 +156,10 @@ namespace MWVR int height = std::max(mSubImages[0].height, mSubImages[1].height); int samples = std::max(mSwapchainConfig[0].selectedSamples, mSwapchainConfig[1].selectedSamples); - mFramebuffer.reset(new VRFramebuffer(gc->getState(),width,height, samples)); + mFramebuffer.reset(new VRFramebuffer(gc->getState(), width, height, samples)); mFramebuffer->createColorBuffer(gc); mFramebuffer->createDepthBuffer(gc); - mMsaaResolveTexture.reset(new VRFramebuffer(gc->getState(),width,height,0)); + mMsaaResolveTexture.reset(new VRFramebuffer(gc->getState(), width, height, 0)); mMsaaResolveTexture->createColorBuffer(gc); mGammaResolveTexture.reset(new VRFramebuffer(gc->getState(), width, height, 0)); mGammaResolveTexture->createColorBuffer(gc); @@ -179,6 +183,7 @@ namespace MWVR Misc::StereoView::instance().setInitialDrawCallback(new InitialDrawCallback(this)); Misc::StereoView::instance().setPredrawCallback(mPreDraw); Misc::StereoView::instance().setPostdrawCallback(mPostDraw); + Misc::StereoView::instance().setFinaldrawCallback(mFinalDraw); //auto cullMask = Misc::StereoView::instance().getCullMask(); auto cullMask = ~(MWRender::VisMask::Mask_UpdateVisitor | MWRender::VisMask::Mask_SimpleWater); cullMask &= ~MWRender::VisMask::Mask_GUI; @@ -257,108 +262,106 @@ namespace MWVR return shader; } - static bool applyGamma(osg::GraphicsContext* gc, VRFramebuffer& target, VRFramebuffer& source) + static bool applyGamma(osg::RenderInfo& info, VRFramebuffer& target, VRFramebuffer& source) { - // TODO: Temporary solution for applying gamma and contrast modifications - // When OpenMW implements post processing, this will be performed there instead. - // I'm just throwing things into static locals since this is temporary code that will be trashed later. + osg::State* state = info.getState(); + static const char* vSource = "#version 120\n varying vec2 uv; void main(){ gl_Position = vec4(gl_Vertex.xy*2.0 - 1, 0, 1); uv = gl_Vertex.xy;}"; + static const char* fSource = "#version 120\n varying vec2 uv; uniform sampler2D t; uniform float gamma; uniform float contrast;" + "void main() {" + "vec4 color1 = texture2D(t, uv);" + "vec3 rgb = color1.rgb;" + "rgb = (rgb - 0.5f) * contrast + 0.5f;" + "rgb = pow(rgb, vec3(1.0/gamma));" + "gl_FragColor = vec4(rgb, color1.a);" + "}"; + static bool first = true; - static GLuint vShader = 0; - static GLuint fShader = 0; - static GLuint program = 0; - static GLuint vbo = 0; - static GLuint gammaUniform = 0; - static GLuint contrastUniform = 0; - - auto* state = gc->getState(); - auto* gl = osg::GLExtensions::Get(state->getContextID(), false); + static osg::ref_ptr program = nullptr; + static osg::ref_ptr vShader = nullptr; + static osg::ref_ptr fShader = nullptr; + static osg::ref_ptr gammaUniform = nullptr; + static GLint gammaUniformLocation = 0; + static osg::ref_ptr contrastUniform = nullptr; + static GLint contrastUniformLocation = 0; + osg::Viewport* viewport = nullptr; + static osg::ref_ptr stateset = nullptr; + static osg::ref_ptr geometry = nullptr; + static osg::ref_ptr texture = nullptr; + static osg::ref_ptr textureObject = nullptr; + + static std::vector vertices = + { + {0, 0, 0, 0}, + {1, 0, 0, 0}, + {1, 1, 0, 0}, + {0, 0, 0, 0}, + {1, 1, 0, 0}, + {0, 1, 0, 0} + }; + static osg::ref_ptr vertexArray = new osg::Vec4Array(vertices.begin(), vertices.end()); if (first) { - first = false; - - const char* vSource = "#version 120\n varying vec2 uv; void main(){ gl_Position = vec4(gl_Vertex.xy*2.0 - 1, 0, 1); uv = gl_Vertex.xy;}"; - const char* fSource = "#version 120\n varying vec2 uv; uniform sampler2D t; uniform float gamma; uniform float contrast;" - "void main() {" - "vec4 color1 = texture2D(t, uv);" - "vec3 rgb = color1.rgb;" - "rgb = (rgb - 0.5f) * contrast + 0.5f;" - "rgb = pow(rgb, vec3(1.0/gamma));" - "gl_FragColor = vec4(rgb, color1.a);" - "}"; - - vShader = createShader(gl, vSource, GL_VERTEX_SHADER); - fShader = createShader(gl, fSource, GL_FRAGMENT_SHADER); - - program = gl->glCreateProgram(); - gl->glAttachShader(program, vShader); - gl->glAttachShader(program, fShader); - gl->glLinkProgram(program); - - GLint isCompiled = 0; - gl->glGetProgramiv(program, GL_LINK_STATUS, &isCompiled); - if (isCompiled == GL_FALSE) - { - GLint maxLength = 0; - gl->glGetProgramInfoLog(program, 0, &maxLength, nullptr); - std::vector infoLog(maxLength); - gl->glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]); - gl->glDeleteProgram(program); - program = 0; - Log(Debug::Error) << "Failed to link program: " << infoLog.data(); - } + geometry = new osg::Geometry(); + geometry->setVertexArray(vertexArray); + geometry->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES, 0, 6)); + geometry->setUseDisplayList(false); + stateset = geometry->getOrCreateStateSet(); + + vShader = new osg::Shader(osg::Shader::Type::VERTEX, vSource); + fShader = new osg::Shader(osg::Shader::Type::FRAGMENT, fSource); + program = new osg::Program(); + program->addShader(vShader); + program->addShader(fShader); + program->compileGLObjects(*state); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + texture = new osg::Texture2D(); + texture->setName("diffuseMap"); + textureObject = new osg::Texture::TextureObject(texture, source.colorBuffer(), GL_TEXTURE_2D); + texture->setTextureObject(state->getContextID(), textureObject); + stateset->setTextureAttributeAndModes(0, texture, osg::StateAttribute::PROTECTED); + stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::PROTECTED); + + gammaUniform = new osg::Uniform("gamma", Settings::Manager::getFloat("gamma", "Video")); + contrastUniform = new osg::Uniform("contrast", Settings::Manager::getFloat("contrast", "Video")); + stateset->addUniform(gammaUniform); + stateset->addUniform(contrastUniform); + + geometry->compileGLObjects(info); - if (program) - { - GLfloat vertices[] = - { - 0, 0, 0, 0, - 1, 0, 0, 0, - 1, 1, 0, 0, - 0, 0, 0, 0, - 1, 1, 0, 0, - 0, 1, 0, 0 - }; - - gl->glGenBuffers(1, &vbo); - gl->glBindBuffer(GL_ARRAY_BUFFER_ARB, vbo); - gl->glBufferData(GL_ARRAY_BUFFER_ARB, sizeof(vertices), vertices, GL_STATIC_DRAW); - - gammaUniform = gl->glGetUniformLocation(program, "gamma"); - contrastUniform = gl->glGetUniformLocation(program, "contrast"); - } + first = false; } - target.bindFramebuffer(gc, GL_FRAMEBUFFER_EXT); + target.bindFramebuffer(state->getGraphicsContext(), GL_FRAMEBUFFER_EXT); if (program > 0) { - gl->glUseProgram(program); - gl->glBindVertexArray(0); - glViewport(0, 0, target.width(), target.height()); + // OSG does not pop statesets until after the final draw callback. Unrelated statesets may therefore still be on the stack at this point. + // Pop these to avoid inheriting arbitrary state from these. They will not be used more in this frame. + state->popAllStateSets(); + state->apply(); - gl->glUniform1f(gammaUniform, Settings::Manager::getFloat("gamma", "Video")); - gl->glUniform1f(contrastUniform, Settings::Manager::getFloat("contrast", "Video")); + gammaUniform->set(Settings::Manager::getFloat("gamma", "Video")); + contrastUniform->set(Settings::Manager::getFloat("contrast", "Video")); + state->pushStateSet(stateset); + state->apply(); - gl->glBindBuffer(GL_ARRAY_BUFFER_ARB, vbo); - glVertexPointer(4, GL_FLOAT, 0, 0); - gl->glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, source.colorBuffer()); - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - glDisable(GL_BLEND); + if(!viewport) + viewport = new osg::Viewport(0, 0, target.width(), target.height()); + viewport->setViewport(0, 0, target.width(), target.height()); + viewport->apply(*state); - glDrawArrays(GL_TRIANGLES, 0, 6); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + geometry->draw(info); - gl->glUseProgram(0); - glBindTexture(GL_TEXTURE_2D, 0); - glEnable(GL_BLEND); - gl->glBindBuffer(GL_ARRAY_BUFFER_ARB, 0); + state->popStateSet(); return true; } return false; } - void VRViewer::blit(osg::GraphicsContext* gc) + void VRViewer::blit(osg::RenderInfo& info) { if (mMirrorTextureShouldBeCleanedUp) { @@ -368,21 +371,23 @@ namespace MWVR if (!mMirrorTextureEnabled) return; + auto* state = info.getState(); + auto* gc = state->getGraphicsContext(); + auto* gl = osg::GLExtensions::Get(state->getContextID(), false); + auto* traits = SDLUtil::GraphicsWindowSDL2::findContext(*mViewer)->getTraits(); int screenWidth = traits->width; int screenHeight = traits->height; if (!mMirrorTexture) { ; - mMirrorTexture.reset(new VRFramebuffer(gc->getState(), + mMirrorTexture.reset(new VRFramebuffer(state, screenWidth, screenHeight, 0)); mMirrorTexture->createColorBuffer(gc); } - auto* state = gc->getState(); - auto* gl = osg::GLExtensions::Get(state->getContextID(), false); int mirrorWidth = screenWidth / mMirrorTextureViews.size(); @@ -393,7 +398,7 @@ namespace MWVR mFramebuffer->blit(gc, 0, 0, mFramebuffer->width(), mFramebuffer->height(), 0, 0, mMsaaResolveTexture->width(), mMsaaResolveTexture->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); bool shouldDoGamma = Settings::Manager::getBool("gamma postprocessing", "VR Debug"); - if (!shouldDoGamma || !applyGamma(gc, *mGammaResolveTexture, *mMsaaResolveTexture)) + if (!shouldDoGamma || !applyGamma(info, *mGammaResolveTexture, *mMsaaResolveTexture)) { mGammaResolveTexture->bindFramebuffer(gc, GL_FRAMEBUFFER_EXT); mMsaaResolveTexture->blit(gc, 0, 0, mMsaaResolveTexture->width(), mMsaaResolveTexture->height(), 0, 0, mGammaResolveTexture->width(), mGammaResolveTexture->height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); @@ -484,6 +489,20 @@ namespace MWVR } } + void VRViewer::finalDrawCallback(osg::RenderInfo& info) + { + auto* session = Environment::get().getSession(); + auto* frameMeta = session->getFrame(VRSession::FramePhase::Draw).get(); + + if (frameMeta->mShouldSyncFrameLoop) + { + if (frameMeta->mShouldRender) + { + blit(info); + } + } + } + void VRViewer::swapBuffersCallback(osg::GraphicsContext* gc) { auto* session = Environment::get().getSession(); @@ -508,4 +527,10 @@ namespace MWVR { mViewer->updateView(left, right); } + + void VRViewer::FinaldrawCallback::operator()(osg::RenderInfo& info, Misc::StereoView::StereoDrawCallback::View view) const + { + if (view != Misc::StereoView::StereoDrawCallback::View::Left) + mViewer->finalDrawCallback(info); + } } diff --git a/apps/openmw/mwvr/vrviewer.hpp b/apps/openmw/mwvr/vrviewer.hpp index 1e0adc0c9..fbc9f682c 100644 --- a/apps/openmw/mwvr/vrviewer.hpp +++ b/apps/openmw/mwvr/vrviewer.hpp @@ -90,6 +90,20 @@ namespace MWVR VRViewer* mViewer; }; + class FinaldrawCallback : public Misc::StereoView::StereoDrawCallback + { + public: + FinaldrawCallback(VRViewer* viewer) + : mViewer(viewer) + {} + + void operator()(osg::RenderInfo& info, Misc::StereoView::StereoDrawCallback::View view) const override; + + private: + + VRViewer* mViewer; + }; + static const std::array sViewNames; enum class MirrorTextureEye { @@ -108,7 +122,8 @@ namespace MWVR void initialDrawCallback(osg::RenderInfo& info); void preDrawCallback(osg::RenderInfo& info); void postDrawCallback(osg::RenderInfo& info); - void blit(osg::GraphicsContext* gc); + void finalDrawCallback(osg::RenderInfo& info); + void blit(osg::RenderInfo& gc); void configureXR(osg::GraphicsContext* gc); void configureCallbacks(); void setupMirrorTexture(); @@ -128,6 +143,7 @@ namespace MWVR osg::ref_ptr mViewer = nullptr; osg::ref_ptr mPreDraw{ nullptr }; osg::ref_ptr mPostDraw{ nullptr }; + osg::ref_ptr mFinalDraw{ nullptr }; std::shared_ptr mUpdateViewCallback{ nullptr }; bool mRenderingReady{ false }; diff --git a/components/misc/stereo.cpp b/components/misc/stereo.cpp index 44cc718b8..4e81eccdf 100644 --- a/components/misc/stereo.cpp +++ b/components/misc/stereo.cpp @@ -461,11 +461,13 @@ namespace Misc auto initialDrawCB = mInitialDrawCallback; auto predrawCB = mPreDrawCallback; auto postDrawCB = mPostDrawCallback; + auto finalDrawCB = mFinalDrawCallback; setCullCallback(nullptr); setInitialDrawCallback(nullptr); setPostdrawCallback(nullptr); setPredrawCallback(nullptr); + setFinaldrawCallback(nullptr); disableStereo(); mTechnique = technique; @@ -475,6 +477,7 @@ namespace Misc setInitialDrawCallback(initialDrawCB); setPostdrawCallback(predrawCB); setPredrawCallback(postDrawCB); + setFinaldrawCallback(finalDrawCB); } void StereoView::update() @@ -705,6 +708,12 @@ namespace Misc mMainCamera->setPostDrawCallback(cb); } + void StereoView::setFinaldrawCallback(osg::ref_ptr cb) + { + mFinalDrawCallback = cb; + mMainCamera->setFinalDrawCallback(cb); + } + void StereoView::setCullCallback(osg::ref_ptr cb) { mMainCamera->setCullCallback(cb); @@ -746,4 +755,47 @@ namespace Misc { return mRightCamera->getViewMatrix(); } + void StereoView::StereoDrawCallback::operator()(osg::RenderInfo& info) const + { + // OSG does not give any information about stereo in these callbacks so i have to infer this myself. + // And hopefully OSG won't change this behaviour. + + View view = View::Both; + + auto camera = info.getCurrentCamera(); + auto viewport = camera->getViewport(); + + // Find the current scene view. + osg::GraphicsOperation* graphicsOperation = info.getCurrentCamera()->getRenderer(); + osgViewer::Renderer* renderer = dynamic_cast(graphicsOperation); + for (int i = 0; i < 2; i++) // OSG alternates between two sceneviews. + { + auto* sceneView = renderer->getSceneView(i); + // The render info argument is a member of scene view, allowing me to identify it. + if (&sceneView->getRenderInfo() == &info) + { + // Now i can simply examine the viewport. + auto activeViewport = static_cast(sceneView->getLocalStateSet()->getAttribute(osg::StateAttribute::Type::VIEWPORT, 0)); + if (activeViewport) + { + if (activeViewport->width() == viewport->width() && activeViewport->height() == viewport->height()) + view = View::Both; + else if (activeViewport->x() == viewport->x() && activeViewport->y() == viewport->y()) + view = View::Left; + else + view = View::Right; + } + else + { + // OSG always sets a viewport in the local stateset if osg's stereo is enabled. + // If it isn't, assume both. + view = View::Both; + } + + break; + } + } + + operator()(info, view); + } } diff --git a/components/misc/stereo.hpp b/components/misc/stereo.hpp index 2993d7433..b807fa262 100644 --- a/components/misc/stereo.hpp +++ b/components/misc/stereo.hpp @@ -89,6 +89,29 @@ namespace Misc GeometryShader_IndexedViewports, //!< Frustum camera culls and draws stereo into indexed viewports using an automatically generated geometry shader. }; + //! A draw callback that adds stereo information to the operator. + //! The stereo information is an enum describing which of the views the callback concerns. + //! With some stereo methods, there is only one callback, in which case the enum will be 'Both'. + //! + //! A typical use case of this callback is to prevent firing callbacks twice and correctly identifying the last/first callback. + struct StereoDrawCallback : public osg::Camera::DrawCallback + { + public: + enum class View + { + Both, Left, Right + }; + public: + StereoDrawCallback() + {} + + void operator()(osg::RenderInfo& info) const override; + + virtual void operator()(osg::RenderInfo& info, View view) const = 0; + + private: + }; + static StereoView& instance(); //! Adds two cameras in stereo to the mainCamera. @@ -121,6 +144,9 @@ namespace Misc //! Set the postdraw callback on the appropriate camera object void setPostdrawCallback(osg::ref_ptr cb); + //! Set the final draw callback on the appropriate camera object + void setFinaldrawCallback(osg::ref_ptr cb); + //! Set the cull callback on the appropriate camera object void setCullCallback(osg::ref_ptr cb); @@ -180,6 +206,7 @@ namespace Misc osg::ref_ptr mInitialDrawCallback{ nullptr }; osg::ref_ptr mPreDrawCallback{ nullptr }; osg::ref_ptr mPostDrawCallback{ nullptr }; + osg::ref_ptr mFinalDrawCallback{ nullptr }; }; //! Overrides all stereo-related states/uniforms to disable stereo for the scene rendered by camera