From ce2bcba5d41631a63ae24d0491578f0ddf187cd2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 16 Dec 2020 23:44:15 +0000 Subject: [PATCH 01/33] Replace deprecated alpha test in shader visitor --- components/CMakeLists.txt | 2 +- components/sceneutil/mwshadowtechnique.cpp | 19 ++- components/sceneutil/mwshadowtechnique.hpp | 4 +- components/sceneutil/shadowsbin.cpp | 57 ++++++- components/sceneutil/shadowsbin.hpp | 27 ++-- components/shader/removedalphafunc.cpp | 27 ++++ components/shader/removedalphafunc.hpp | 40 +++++ components/shader/shadervisitor.cpp | 178 ++++++++++++++++----- components/shader/shadervisitor.hpp | 4 + files/shaders/CMakeLists.txt | 1 + files/shaders/alpha.glsl | 38 +++++ files/shaders/objects_fragment.glsl | 4 + files/shaders/shadowcasting_fragment.glsl | 7 +- 13 files changed, 345 insertions(+), 63 deletions(-) create mode 100644 components/shader/removedalphafunc.cpp create mode 100644 components/shader/removedalphafunc.hpp create mode 100644 files/shaders/alpha.glsl diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 3c18990375..45864a6e83 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,7 +46,7 @@ add_component_dir (resource ) add_component_dir (shader - shadermanager shadervisitor + shadermanager shadervisitor removedalphafunc ) add_component_dir (sceneutil diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c49a147776..31267f75ce 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -278,7 +278,7 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) static osg::ref_ptr ss; if (!ss) { - ShadowsBinAdder adder("ShadowsBin"); + ShadowsBinAdder adder("ShadowsBin", _vdsm->getCastingPrograms()); ss = new osg::StateSet; ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); } @@ -882,11 +882,15 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) { // This can't be part of the constructor as OSG mandates that there be a trivial constructor available - - _castingProgram = new osg::Program(); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); - _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); + osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) + { + auto& program = _castingPrograms[alphaFunc - GL_NEVER]; + program = new osg::Program(); + program->addShader(castingVertexShader); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)} }, osg::Shader::FRAGMENT)); + } } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) @@ -1604,10 +1608,11 @@ void MWShadowTechnique::createShaders() } - if (!_castingProgram) + if (!_castingPrograms[GL_ALWAYS - GL_NEVER]) OSG_NOTICE << "Shadow casting shader has not been set up. Remember to call setupCastingShader(Shader::ShaderManager &)" << std::endl; - _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + // Always use the GL_ALWAYS shader as the shadows bin will change it if necessary + _shadowCastingStateSet->setAttributeAndModes(_castingPrograms[GL_ALWAYS - GL_NEVER], osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 5125247dda..96b1c7d0ed 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -215,6 +215,8 @@ namespace SceneUtil { virtual void createShaders(); + virtual std::array, GL_ALWAYS - GL_NEVER> getCastingPrograms() const { return _castingPrograms; } + virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; virtual osg::Polytope computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight); @@ -288,7 +290,7 @@ namespace SceneUtil { }; osg::ref_ptr _debugHud; - osg::ref_ptr _castingProgram; + std::array, GL_ALWAYS - GL_NEVER> _castingPrograms; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 520ad0362f..9e6d461ae7 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -1,7 +1,9 @@ #include "shadowsbin.hpp" #include #include +#include #include +#include #include using namespace osgUtil; @@ -40,6 +42,10 @@ namespace namespace SceneUtil { +std::array, GL_ALWAYS - GL_NEVER> ShadowsBin::sCastingPrograms = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr +}; + ShadowsBin::ShadowsBin() { mNoTestStateSet = new osg::StateSet; @@ -49,6 +55,12 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + + for (size_t i = 0; i < sCastingPrograms.size(); ++i) + { + mAlphaFuncShaders[i] = new osg::StateSet; + mAlphaFuncShaders[i]->setAttribute(sCastingPrograms[i], osg::StateAttribute::ON | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); + } } StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache) @@ -71,7 +83,6 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un continue; accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND); - accumulateModeState(ss, state.mAlphaTest, state.mAlphaTestOverride, GL_ALPHA_TEST); const osg::StateSet::AttributeList& attributes = ss->getAttributeList(); osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0)); @@ -83,6 +94,14 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un state.mMaterial = nullptr; } + found = attributes.find(std::make_pair(osg::StateAttribute::ALPHAFUNC, 0)); + if (found != attributes.end()) + { + // As force shaders is on, we know this is really a RemovedAlphaFunc + const osg::StateSet::RefAttributePair& rap = found->second; + accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); + } + // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); if (found != attributes.end()) @@ -113,16 +132,44 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un leaf->_parent = sg_new; sg_new->_leaves.push_back(leaf); } - return sg_new; + sg = sg_new; } + + // GL_ALWAYS is set by default by mwshadowtechnique + if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) + { + sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); + for (RenderLeaf* leaf : sg->_leaves) + { + leaf->_parent = sg_new; + sg_new->_leaves.push_back(leaf); + } + sg = sg_new; + } + return sg; } +void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms) +{ + sCastingPrograms = castingPrograms; + osg::ref_ptr bin(new ShadowsBin); + osgUtil::RenderBin::addRenderBinPrototype(name, bin); +} + +inline bool ShadowsBin::State::needTexture() const +{ + return mAlphaBlend || (mAlphaFunc && mAlphaFunc->getFunction() != GL_ALWAYS); +} + bool ShadowsBin::State::needShadows() const { - if (!mMaterial) - return true; - return materialNeedShadows(mMaterial); + if (mAlphaFunc && mAlphaFunc->getFunction() == GL_NEVER) + return false; + // other alpha func + material combinations might be skippable + if (mAlphaBlend && mMaterial) + return materialNeedShadows(mMaterial); + return true; } void ShadowsBin::sortImplementation() diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index cc6fd3525c..7247a9af4e 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -1,11 +1,13 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H #define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H +#include #include #include namespace osg { class Material; + class AlphaFunc; } namespace SceneUtil @@ -15,8 +17,12 @@ namespace SceneUtil class ShadowsBin : public osgUtil::RenderBin { private: + static std::array, GL_ALWAYS - GL_NEVER> sCastingPrograms; + osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; + + std::array, GL_ALWAYS - GL_NEVER> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); @@ -24,6 +30,7 @@ namespace SceneUtil : osgUtil::RenderBin(rhs, copyop) , mNoTestStateSet(rhs.mNoTestStateSet) , mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet) + , mAlphaFuncShaders(rhs.mAlphaFuncShaders) {} void sortImplementation() override; @@ -33,8 +40,8 @@ namespace SceneUtil State() : mAlphaBlend(false) , mAlphaBlendOverride(false) - , mAlphaTest(false) - , mAlphaTestOverride(false) + , mAlphaFunc(nullptr) + , mAlphaFuncOverride(false) , mMaterial(nullptr) , mMaterialOverride(false) , mImportantState(false) @@ -42,33 +49,29 @@ namespace SceneUtil bool mAlphaBlend; bool mAlphaBlendOverride; - bool mAlphaTest; - bool mAlphaTestOverride; + osg::AlphaFunc* mAlphaFunc; + bool mAlphaFuncOverride; osg::Material* mMaterial; bool mMaterialOverride; bool mImportantState; - bool needTexture() const { return mAlphaBlend || mAlphaTest; } + bool needTexture() const; bool needShadows() const; // A state is interesting if there's anything about it that might affect whether we can optimise child state bool interesting() const { - return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaTestOverride || mMaterialOverride || mImportantState; + return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaFuncOverride || mMaterialOverride || mImportantState; } }; osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); - static void addPrototype(const std::string& name) - { - osg::ref_ptr bin (new ShadowsBin); - osgUtil::RenderBin::addRenderBinPrototype(name, bin); - } + static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms); }; class ShadowsBinAdder { public: - ShadowsBinAdder(const std::string& name){ ShadowsBin::addPrototype(name); } + ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp new file mode 100644 index 0000000000..15d0fb0f19 --- /dev/null +++ b/components/shader/removedalphafunc.cpp @@ -0,0 +1,27 @@ +#include "removedalphafunc.hpp" + +#include + +#include + +namespace Shader +{ + std::array, GL_ALWAYS - GL_NEVER> RemovedAlphaFunc::sInstances{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + + osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) + { + assert(func >= GL_NEVER && func <= GL_ALWAYS); + if (!sInstances[func - GL_NEVER]) + sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); + return sInstances[func - GL_NEVER]; + } + + void RemovedAlphaFunc::apply(osg::State & state) const + { + // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it + if (!state.getGlobalDefaultAttribute(ALPHAFUNC)->getType() != getType()) + state.setGlobalDefaultAttribute(static_cast(cloneType())); + } +} diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp new file mode 100644 index 0000000000..a8165b0dc7 --- /dev/null +++ b/components/shader/removedalphafunc.hpp @@ -0,0 +1,40 @@ +#ifndef OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H +#define OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H + +#include + +#include + +namespace Shader +{ + // State attribute used when shader visitor replaces the deprecated alpha function with a shader + // Prevents redundant glAlphaFunc calls and lets the shadowsbin know the stateset had alpha testing + class RemovedAlphaFunc : public osg::AlphaFunc + { + public: + // Get a singleton-like instance with the right func (but a default threshold) + static osg::ref_ptr getInstance(GLenum func); + + RemovedAlphaFunc() + : osg::AlphaFunc() + {} + + RemovedAlphaFunc(ComparisonFunction func, float ref) + : osg::AlphaFunc(func, ref) + {} + + RemovedAlphaFunc(const RemovedAlphaFunc& raf, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY) + : osg::AlphaFunc(raf, copyop) + {} + + META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); + + void apply(osg::State& state) const override; + + protected: + virtual ~RemovedAlphaFunc() = default; + + static std::array, GL_ALWAYS - GL_NEVER> sInstances; + }; +} +#endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e908b6aaa8..aeec391c67 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,5 +1,6 @@ #include "shadervisitor.hpp" +#include #include #include #include @@ -13,6 +14,7 @@ #include #include +#include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader @@ -22,6 +24,9 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) + , mAlphaTestOverridden(false) + , mAlphaFunc(GL_ALWAYS) + , mAlphaRef(1.0) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -76,6 +81,34 @@ namespace Shader return newStateSet.get(); } + osg::UserDataContainer* getWritableUserDataContainer(osg::Object& object) + { + if (!object.getUserDataContainer()) + return object.getOrCreateUserDataContainer(); + + osg::ref_ptr newUserData = static_cast(object.getUserDataContainer()->clone(osg::CopyOp::SHALLOW_COPY)); + object.setUserDataContainer(newUserData); + return newUserData.get(); + } + + osg::StateSet* getRemovedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); + } + + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + { + unsigned int index = userData.getUserObjectIndex("removedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, stateSet); + else + userData.addUserObject(stateSet); + stateSet->setName("removedState"); + } + const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; bool isTextureNameRecognized(const std::string& name) { @@ -234,49 +267,67 @@ namespace Shader } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); - for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) + osg::StateSet::AttributeList removedAttributes; + osg::ref_ptr removedState; + if (removedState = getRemovedState(*stateset)) + removedAttributes = removedState->getAttributeList(); + for (const auto& attributeMap : { attributes, removedAttributes }) { - if (it->first.first == osg::StateAttribute::MATERIAL) + for (osg::StateSet::AttributeList::const_iterator it = attributeMap.begin(); it != attributeMap.end(); ++it) { - // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define - if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) + if (it->first.first == osg::StateAttribute::MATERIAL) { - if (it->second.second & osg::StateAttribute::OVERRIDE) - mRequirements.back().mMaterialOverridden = true; - - const osg::Material* mat = static_cast(it->second.first.get()); - - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - - int colorMode; - switch (mat->getColorMode()) + // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define + if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) { - case osg::Material::OFF: - colorMode = 0; - break; - case osg::Material::EMISSION: - colorMode = 1; - break; - default: - case osg::Material::AMBIENT_AND_DIFFUSE: - colorMode = 2; - break; - case osg::Material::AMBIENT: - colorMode = 3; - break; - case osg::Material::DIFFUSE: - colorMode = 4; - break; - case osg::Material::SPECULAR: - colorMode = 5; - break; - } + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mMaterialOverridden = true; - mRequirements.back().mColorMode = colorMode; + const osg::Material* mat = static_cast(it->second.first.get()); + + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + + int colorMode; + switch (mat->getColorMode()) + { + case osg::Material::OFF: + colorMode = 0; + break; + case osg::Material::EMISSION: + colorMode = 1; + break; + default: + case osg::Material::AMBIENT_AND_DIFFUSE: + colorMode = 2; + break; + case osg::Material::AMBIENT: + colorMode = 3; + break; + case osg::Material::DIFFUSE: + colorMode = 4; + break; + case osg::Material::SPECULAR: + colorMode = 5; + break; + } + + mRequirements.back().mColorMode = colorMode; + } + } + else if (it->first.first == osg::StateAttribute::ALPHAFUNC) + { + if (!mRequirements.back().mAlphaTestOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaTestOverridden = true; + + const osg::AlphaFunc* alpha = static_cast(it->second.first.get()); + mRequirements.back().mAlphaFunc = alpha->getFunction(); + mRequirements.back().mAlphaRef = alpha->getReferenceValue(); + } } } - // Eventually, move alpha testing to discard in shader adn remove deprecated state here } } @@ -322,6 +373,42 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); + if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) + { + writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + + // back up removed state in case recreateShaders gets rid of the shader later + osg::ref_ptr removedState; + if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) + removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); + if (!removedState) + removedState = new osg::StateSet(); + + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); + // This disables the deprecated fixed-function alpha test + writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); + + const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); + if (alphaFunc) + removedState->setAttribute(alphaFunc->first, alphaFunc->second); + // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test + writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateRemovedState(*writableUserData, removedState); + } + } + osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT)); @@ -347,6 +434,25 @@ namespace Shader writableStateSet = getWritableStateSet(node); writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + + osg::ref_ptr removedState; + if (removedState = getRemovedState(*writableStateSet)) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + unsigned int index = writableUserData->getUserObjectIndex("removedState"); + writableUserData->removeUserObject(index); + + for (const auto& [mode, value] : removedState->getModeList()) + writableStateSet->setMode(mode, value); + + for (const auto& attribute : removedState->getAttributeList()) + writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 6031dbfe6c..2ba18107b5 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -79,6 +79,10 @@ namespace Shader int mColorMode; bool mMaterialOverridden; + bool mAlphaTestOverridden; + + GLenum mAlphaFunc; + float mAlphaRef; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 8012c2bc10..16aedd16db 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -10,6 +10,7 @@ set(SHADER_FILES water_vertex.glsl water_fragment.glsl water_nm.png + alpha.glsl objects_vertex.glsl objects_fragment.glsl terrain_vertex.glsl diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl new file mode 100644 index 0000000000..9dee7bd314 --- /dev/null +++ b/files/shaders/alpha.glsl @@ -0,0 +1,38 @@ + +#define FUNC_NEVER 0x0200 +#define FUNC_LESS 0x0201 +#define FUNC_EQUAL 0x0202 +#define FUNC_LEQUAL 0x0203 +#define FUNC_GREATER 0x0204 +#define FUNC_NOTEQUAL 0x0205 +#define FUNC_GEQUAL 0x0206 +#define FUNC_ALWAYS 0x0207 + +#if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER +uniform float alphaRef; +#endif + +void alphaTest() +{ + #if @alphaFunc == FUNC_NEVER + discard; + #elif @alphaFunc == FUNC_LESS + if (gl_FragData[0].a > alphaRef) + discard; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + if (gl_FragData[0].a >= alphaRef) + discard; + #elif @alphaFunc == FUNC_GREATER + if (gl_FragData[0].a < alphaRef) + discard; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + if (gl_FragData[0].a <= alphaRef) + discard; + #endif +} \ No newline at end of file diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index d5716c378b..ac631308cb 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -67,6 +67,7 @@ varying vec3 passNormal; #include "shadows_fragment.glsl" #include "lighting.glsl" #include "parallax.glsl" +#include "alpha.glsl" void main() { @@ -108,6 +109,7 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); + alphaTest(); #else gl_FragData[0] = vec4(1.0); #endif @@ -164,6 +166,8 @@ void main() gl_FragData[0] *= doLighting(passViewPos, normalize(viewNormal), passColor, shadowing); #endif + alphaTest(); + #if @envMap && !@preLightEnv gl_FragData[0].xyz += texture2D(envMap, envTexCoordGen).xyz * envMapColor.xyz * envLuma; #endif diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index a5410d0089..8c53c542bd 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -8,6 +8,8 @@ varying float alphaPassthrough; uniform bool useDiffuseMapForShadowAlpha; uniform bool alphaTestShadows; +#include "alpha.glsl" + void main() { gl_FragData[0].rgb = vec3(1.0); @@ -16,7 +18,10 @@ void main() else gl_FragData[0].a = alphaPassthrough; - // Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff. + alphaTest(); + + // Prevent translucent things casting shadow (including the player using an invisibility effect). + // This replaces alpha blending, which obviously doesn't work with depth buffers if (alphaTestShadows && gl_FragData[0].a <= 0.5) discard; } From a0800715887135576ff0f863df00c897481b28b8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 00:02:51 +0000 Subject: [PATCH 02/33] Set default state sensibly --- apps/openmw/mwrender/renderingmanager.cpp | 5 +++++ components/shader/removedalphafunc.cpp | 7 ------- components/shader/removedalphafunc.hpp | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 217f0b73ca..9cb5146dc3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -32,6 +32,8 @@ #include #include #include + +#include #include #include @@ -376,6 +378,9 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("far", mViewDistance)); mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); + // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it + mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_GREATER)); + mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); updateProjectionMatrix(); diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp index 15d0fb0f19..0da36ab48f 100644 --- a/components/shader/removedalphafunc.cpp +++ b/components/shader/removedalphafunc.cpp @@ -17,11 +17,4 @@ namespace Shader sInstances[func - GL_NEVER] = new RemovedAlphaFunc(static_cast(func), 1.0); return sInstances[func - GL_NEVER]; } - - void RemovedAlphaFunc::apply(osg::State & state) const - { - // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it - if (!state.getGlobalDefaultAttribute(ALPHAFUNC)->getType() != getType()) - state.setGlobalDefaultAttribute(static_cast(cloneType())); - } } diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp index a8165b0dc7..1aa9978cfe 100644 --- a/components/shader/removedalphafunc.hpp +++ b/components/shader/removedalphafunc.hpp @@ -29,7 +29,7 @@ namespace Shader META_StateAttribute(Shader, RemovedAlphaFunc, ALPHAFUNC); - void apply(osg::State& state) const override; + void apply(osg::State& state) const override {} protected: virtual ~RemovedAlphaFunc() = default; From a4f32a469efbc7744146b3ab7d2c75d6e2fa91e9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 01:36:20 +0000 Subject: [PATCH 03/33] Work around Nvidia driver bug https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.1.20.pdf Section 4.1.3 says that hexadecimal integer literals are supported, but Nvidia have never read a specification since their founding, so their engineers didn't know that hexadecimal integer literals are requires to be supported to advertise support OpenGL versions with GLSL support. --- files/shaders/alpha.glsl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 9dee7bd314..0c69df0ab2 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -1,12 +1,12 @@ -#define FUNC_NEVER 0x0200 -#define FUNC_LESS 0x0201 -#define FUNC_EQUAL 0x0202 -#define FUNC_LEQUAL 0x0203 -#define FUNC_GREATER 0x0204 -#define FUNC_NOTEQUAL 0x0205 -#define FUNC_GEQUAL 0x0206 -#define FUNC_ALWAYS 0x0207 +#define FUNC_NEVER 512 // 0x0200 +#define FUNC_LESS 513 // 0x0201 +#define FUNC_EQUAL 514 // 0x0202 +#define FUNC_LEQUAL 515 // 0x0203 +#define FUNC_GREATER 516 // 0x0204 +#define FUNC_NOTEQUAL 517 // 0x0205 +#define FUNC_GEQUAL 518 // 0x0206 +#define FUNC_ALWAYS 519 // 0x0207 #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER uniform float alphaRef; From 05ad44d0b1527339f8da788898cf9fa1ab92ba84 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 01:44:46 +0000 Subject: [PATCH 04/33] Set correct array size --- components/sceneutil/mwshadowtechnique.hpp | 4 ++-- components/sceneutil/shadowsbin.cpp | 6 +++--- components/sceneutil/shadowsbin.hpp | 8 ++++---- components/shader/removedalphafunc.cpp | 4 ++-- components/shader/removedalphafunc.hpp | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 96b1c7d0ed..7ffe687b47 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -215,7 +215,7 @@ namespace SceneUtil { virtual void createShaders(); - virtual std::array, GL_ALWAYS - GL_NEVER> getCastingPrograms() const { return _castingPrograms; } + virtual std::array, GL_ALWAYS - GL_NEVER + 1> getCastingPrograms() const { return _castingPrograms; } virtual bool selectActiveLights(osgUtil::CullVisitor* cv, ViewDependentData* vdd) const; @@ -290,7 +290,7 @@ namespace SceneUtil { }; osg::ref_ptr _debugHud; - std::array, GL_ALWAYS - GL_NEVER> _castingPrograms; + std::array, GL_ALWAYS - GL_NEVER + 1> _castingPrograms; }; } diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 9e6d461ae7..de778f14b5 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -42,8 +42,8 @@ namespace namespace SceneUtil { -std::array, GL_ALWAYS - GL_NEVER> ShadowsBin::sCastingPrograms = { - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr +std::array, GL_ALWAYS - GL_NEVER + 1> ShadowsBin::sCastingPrograms = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; ShadowsBin::ShadowsBin() @@ -150,7 +150,7 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un return sg; } -void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms) +void ShadowsBin::addPrototype(const std::string & name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms) { sCastingPrograms = castingPrograms; osg::ref_ptr bin(new ShadowsBin); diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index 7247a9af4e..d8bdd86071 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -17,12 +17,12 @@ namespace SceneUtil class ShadowsBin : public osgUtil::RenderBin { private: - static std::array, GL_ALWAYS - GL_NEVER> sCastingPrograms; + static std::array, GL_ALWAYS - GL_NEVER + 1> sCastingPrograms; osg::ref_ptr mNoTestStateSet; osg::ref_ptr mShaderAlphaTestStateSet; - std::array, GL_ALWAYS - GL_NEVER> mAlphaFuncShaders; + std::array, GL_ALWAYS - GL_NEVER + 1> mAlphaFuncShaders; public: META_Object(SceneUtil, ShadowsBin) ShadowsBin(); @@ -65,13 +65,13 @@ namespace SceneUtil osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); - static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms); + static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; class ShadowsBinAdder { public: - ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } + ShadowsBinAdder(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms){ ShadowsBin::addPrototype(name, castingPrograms); } }; } diff --git a/components/shader/removedalphafunc.cpp b/components/shader/removedalphafunc.cpp index 0da36ab48f..6b701b8900 100644 --- a/components/shader/removedalphafunc.cpp +++ b/components/shader/removedalphafunc.cpp @@ -6,8 +6,8 @@ namespace Shader { - std::array, GL_ALWAYS - GL_NEVER> RemovedAlphaFunc::sInstances{ - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + std::array, GL_ALWAYS - GL_NEVER + 1> RemovedAlphaFunc::sInstances{ + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; osg::ref_ptr RemovedAlphaFunc::getInstance(GLenum func) diff --git a/components/shader/removedalphafunc.hpp b/components/shader/removedalphafunc.hpp index 1aa9978cfe..c744391e56 100644 --- a/components/shader/removedalphafunc.hpp +++ b/components/shader/removedalphafunc.hpp @@ -34,7 +34,7 @@ namespace Shader protected: virtual ~RemovedAlphaFunc() = default; - static std::array, GL_ALWAYS - GL_NEVER> sInstances; + static std::array, GL_ALWAYS - GL_NEVER + 1> sInstances; }; } #endif //OPENMW_COMPONENTS_REMOVEDALPHAFUNC_H From 0b5d5eab4c997789b1a3eab5b9da8406f6fc1552 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 02:11:51 +0000 Subject: [PATCH 05/33] Move is faster --- components/sceneutil/shadowsbin.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index de778f14b5..312d4226b5 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -127,11 +127,9 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un if (state.mAlphaBlend) { sg_new = sg->find_or_insert(mShaderAlphaTestStateSet); - for (RenderLeaf* leaf : sg->_leaves) - { + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; - sg_new->_leaves.push_back(leaf); - } sg = sg_new; } @@ -139,11 +137,9 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un if (state.mAlphaFunc && state.mAlphaFunc->getFunction() != GL_ALWAYS) { sg_new = sg->find_or_insert(mAlphaFuncShaders[state.mAlphaFunc->getFunction() - GL_NEVER]); - for (RenderLeaf* leaf : sg->_leaves) - { + sg_new->_leaves = std::move(sg->_leaves); + for (RenderLeaf* leaf : sg_new->_leaves) leaf->_parent = sg_new; - sg_new->_leaves.push_back(leaf); - } sg = sg_new; } From 2ecd00b6db190476f4f8d713044fd3c42dd5f220 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 18 Dec 2020 16:31:21 +0000 Subject: [PATCH 06/33] Don't accidentally enable alpha testing for all shadow casting --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9cb5146dc3..736e4f2ee1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -379,7 +379,7 @@ namespace MWRender mRootNode->getOrCreateStateSet()->addUniform(new osg::Uniform("simpleWater", false)); // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it - mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_GREATER)); + mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); From cc2ce9fa3e49b86fe1501eb2fe3b26e80918c995 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 19 Dec 2020 21:57:42 +0000 Subject: [PATCH 07/33] Explicitly default-construct array The docs seem to imply this is automatic when the array contains a class-type, which osg::ref_ptr is, but I got a crash log that doesn't make sense if that's true. --- components/sceneutil/mwshadowtechnique.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 31267f75ce..39e47b0ea3 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -782,13 +782,15 @@ void MWShadowTechnique::ViewDependentData::releaseGLObjects(osg::State* state) c MWShadowTechnique::MWShadowTechnique(): ShadowTechnique(), _enableShadows(false), - _debugHud(nullptr) + _debugHud(nullptr), + _castingPrograms{ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr } { _shadowRecievingPlaceholderStateSet = new osg::StateSet; } MWShadowTechnique::MWShadowTechnique(const MWShadowTechnique& vdsm, const osg::CopyOp& copyop): ShadowTechnique(vdsm,copyop) + , _castingPrograms(vdsm._castingPrograms) { _shadowRecievingPlaceholderStateSet = new osg::StateSet; _enableShadows = vdsm._enableShadows; From 0e4e8eb0f3af47ea236dac8c7372da97eb39dbdb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:22:14 +0000 Subject: [PATCH 08/33] Add glDebugGroup support --- components/debug/gldebug.cpp | 330 +++++++++++++++++++++++------------ components/debug/gldebug.hpp | 60 +++++++ 2 files changed, 279 insertions(+), 111 deletions(-) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 3c5ec728ac..22d7bbe682 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -38,127 +38,235 @@ either expressed or implied, of the FreeBSD Project. // OpenGL constants not provided by OSG: #include -void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +namespace Debug { + + void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) + { #ifdef GL_DEBUG_OUTPUT - std::string srcStr; - switch (source) - { - case GL_DEBUG_SOURCE_API: - srcStr = "API"; - break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: - srcStr = "WINDOW_SYSTEM"; - break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: - srcStr = "SHADER_COMPILER"; - break; - case GL_DEBUG_SOURCE_THIRD_PARTY: - srcStr = "THIRD_PARTY"; - break; - case GL_DEBUG_SOURCE_APPLICATION: - srcStr = "APPLICATION"; - break; - case GL_DEBUG_SOURCE_OTHER: - srcStr = "OTHER"; - break; - default: - srcStr = "UNDEFINED"; - break; - } + std::string srcStr; + switch (source) + { + case GL_DEBUG_SOURCE_API: + srcStr = "API"; + break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + srcStr = "WINDOW_SYSTEM"; + break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: + srcStr = "SHADER_COMPILER"; + break; + case GL_DEBUG_SOURCE_THIRD_PARTY: + srcStr = "THIRD_PARTY"; + break; + case GL_DEBUG_SOURCE_APPLICATION: + srcStr = "APPLICATION"; + break; + case GL_DEBUG_SOURCE_OTHER: + srcStr = "OTHER"; + break; + default: + srcStr = "UNDEFINED"; + break; + } - std::string typeStr; + std::string typeStr; - Debug::Level logSeverity = Debug::Warning; - switch (type) - { - case GL_DEBUG_TYPE_ERROR: - typeStr = "ERROR"; - logSeverity = Debug::Error; - break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: - typeStr = "DEPRECATED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: - typeStr = "UNDEFINED_BEHAVIOR"; - break; - case GL_DEBUG_TYPE_PORTABILITY: - typeStr = "PORTABILITY"; - break; - case GL_DEBUG_TYPE_PERFORMANCE: - typeStr = "PERFORMANCE"; - break; - case GL_DEBUG_TYPE_OTHER: - typeStr = "OTHER"; - break; - default: - typeStr = "UNDEFINED"; - break; - } + Level logSeverity = Warning; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: + typeStr = "ERROR"; + logSeverity = Error; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + typeStr = "DEPRECATED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + typeStr = "UNDEFINED_BEHAVIOR"; + break; + case GL_DEBUG_TYPE_PORTABILITY: + typeStr = "PORTABILITY"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: + typeStr = "PERFORMANCE"; + break; + case GL_DEBUG_TYPE_OTHER: + typeStr = "OTHER"; + break; + default: + typeStr = "UNDEFINED"; + break; + } - Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; + Log(logSeverity) << "OpenGL " << typeStr << " [" << srcStr << "]: " << message; #endif -} + } -void enableGLDebugExtension(unsigned int contextID) -{ + class PushDebugGroup + { + public: + static std::unique_ptr sInstance; + + void (GL_APIENTRY * glPushDebugGroup) (GLenum source, GLuint id, GLsizei length, const GLchar * message); + + void (GL_APIENTRY * glPopDebugGroup) (void); + + bool valid() + { + return glPushDebugGroup && glPopDebugGroup; + } + }; + + std::unique_ptr PushDebugGroup::sInstance{ std::make_unique() }; + + EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) + { + } + + void EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) + { #ifdef GL_DEBUG_OUTPUT - typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); - typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); - typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); - - GLDebugMessageControlFunction glDebugMessageControl = nullptr; - GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; + OpenThreads::ScopedLock lock(mMutex); - if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) - { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); - } - else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) - { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); - } - else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) - { - osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); - osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); - } + unsigned int contextID = graphicsContext->getState()->getContextID(); - if (glDebugMessageCallback && glDebugMessageControl) - { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); - glDebugMessageCallback(debugCallback, nullptr); + typedef void (GL_APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam); + typedef void (GL_APIENTRY *GLDebugMessageControlFunction)(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); + typedef void (GL_APIENTRY *GLDebugMessageCallbackFunction)(DEBUGPROC, const void* userParam); - Log(Debug::Info) << "OpenGL debug callback attached."; - } - else + GLDebugMessageControlFunction glDebugMessageControl = nullptr; + GLDebugMessageCallbackFunction glDebugMessageCallback = nullptr; + + if (osg::isGLExtensionSupported(contextID, "GL_KHR_debug")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallback"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControl"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPushDebugGroup, "glPushDebugGroup"); + osg::setGLExtensionFuncPtr(PushDebugGroup::sInstance->glPopDebugGroup, "glPopDebugGroup"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_ARB_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackARB"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlARB"); + } + else if (osg::isGLExtensionSupported(contextID, "GL_AMD_debug_output")) + { + osg::setGLExtensionFuncPtr(glDebugMessageCallback, "glDebugMessageCallbackAMD"); + osg::setGLExtensionFuncPtr(glDebugMessageControl, "glDebugMessageControlAMD"); + } + + if (glDebugMessageCallback && glDebugMessageControl) + { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, true); + glDebugMessageCallback(debugCallback, nullptr); + + Log(Info) << "OpenGL debug callback attached."; + } + else #endif - Log(Debug::Error) << "Unable to attach OpenGL debug callback."; -} - -Debug::EnableGLDebugOperation::EnableGLDebugOperation() : osg::GraphicsOperation("EnableGLDebugOperation", false) -{ -} - -void Debug::EnableGLDebugOperation::operator()(osg::GraphicsContext* graphicsContext) -{ - OpenThreads::ScopedLock lock(mMutex); - - unsigned int contextID = graphicsContext->getState()->getContextID(); - enableGLDebugExtension(contextID); -} - -bool Debug::shouldDebugOpenGL() -{ - const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); - if (!env) - return false; - std::string str(env); - if (str.length() == 0) - return true; - - return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; + Log(Error) << "Unable to attach OpenGL debug callback."; + } + + bool shouldDebugOpenGL() + { + const char* env = std::getenv("OPENMW_DEBUG_OPENGL"); + if (!env) + return false; + std::string str(env); + if (str.length() == 0) + return true; + + return str.find("OFF") == std::string::npos && str.find("0") == std::string::npos && str.find("NO") == std::string::npos; + } + + DebugGroup::DebugGroup(const std::string & message, GLuint id) + : mSource(GL_DEBUG_SOURCE_APPLICATION) + , mId(id) + , mMessage(message) + { + } + + DebugGroup::DebugGroup(const DebugGroup & debugGroup, const osg::CopyOp & copyop) + : osg::StateAttribute(debugGroup, copyop) + , mSource(debugGroup.mSource) + , mId(debugGroup.mId) + , mMessage(debugGroup.mMessage) + { + } + + void DebugGroup::apply(osg::State & state) const + { + if (!PushDebugGroup::sInstance->valid()) + { + Log(Error) << "OpenGL debug groups not supported on this system, or OPENMW_DEBUG_OPENGL environment variable not set."; + return; + } + + auto& attributeVec = state.getAttributeVec(this); + auto& lastAppliedStack = sLastAppliedStack[state.getContextID()]; + + size_t firstNonMatch = 0; + while (firstNonMatch < lastAppliedStack.size() + && ((firstNonMatch < attributeVec.size() && lastAppliedStack[firstNonMatch] == attributeVec[firstNonMatch].first) + || lastAppliedStack[firstNonMatch] == this)) + firstNonMatch++; + + for (size_t i = lastAppliedStack.size(); i > firstNonMatch; --i) + lastAppliedStack[i - 1]->pop(state); + lastAppliedStack.resize(firstNonMatch); + + lastAppliedStack.reserve(attributeVec.size()); + for (size_t i = firstNonMatch; i < attributeVec.size(); ++i) + { + const DebugGroup* group = static_cast(attributeVec[i].first); + group->push(state); + lastAppliedStack.push_back(group); + } + if (!(lastAppliedStack.back() == this)) + { + push(state); + lastAppliedStack.push_back(this); + } + } + + int DebugGroup::compare(const StateAttribute & sa) const + { + COMPARE_StateAttribute_Types(DebugGroup, sa); + + COMPARE_StateAttribute_Parameter(mSource); + COMPARE_StateAttribute_Parameter(mId); + COMPARE_StateAttribute_Parameter(mMessage); + + return 0; + } + + void DebugGroup::releaseGLObjects(osg::State * state) const + { + if (state) + sLastAppliedStack.erase(state->getContextID()); + else + sLastAppliedStack.clear(); + } + + bool DebugGroup::isValid() const + { + return mSource || mId || mMessage.length(); + } + + void DebugGroup::push(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPushDebugGroup(mSource, mId, mMessage.size(), mMessage.c_str()); + } + + void DebugGroup::pop(osg::State & state) const + { + if (isValid()) + PushDebugGroup::sInstance->glPopDebugGroup(); + } + + std::map> DebugGroup::sLastAppliedStack{}; + } diff --git a/components/debug/gldebug.hpp b/components/debug/gldebug.hpp index 8be747afe8..b6f32c9cff 100644 --- a/components/debug/gldebug.hpp +++ b/components/debug/gldebug.hpp @@ -17,5 +17,65 @@ namespace Debug }; bool shouldDebugOpenGL(); + + + /* + Debug groups allow rendering to be annotated, making debugging via APITrace/CodeXL/NSight etc. much clearer. + + Because I've not thought of a quick and clean way of doing it without incurring a small performance cost, + there are no uses of this class checked in. For now, add annotations locally when you need them. + + To use this class, add it to a StateSet just like any other StateAttribute. Prefer the string-only constructor. + You'll need OPENMW_DEBUG_OPENGL set to true, or shouldDebugOpenGL() redefined to just return true as otherwise + the extension function pointers won't get set up. That can maybe be cleaned up in the future. + + Beware that consecutive identical debug groups (i.e. pointers match) won't always get applied due to OSG thinking + it's already applied them. Either avoid nesting the same object, add dummy groups so they're not consecutive, or + ensure the leaf group isn't identical to its parent. + */ + class DebugGroup : public osg::StateAttribute + { + public: + DebugGroup() + : mSource(0) + , mId(0) + , mMessage("") + {} + + DebugGroup(GLenum source, GLuint id, const std::string& message) + : mSource(source) + , mId(id) + , mMessage(message) + {} + + DebugGroup(const std::string& message, GLuint id = 0); + + DebugGroup(const DebugGroup& debugGroup, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + META_StateAttribute(Debug, DebugGroup, osg::StateAttribute::Type(101)); + + void apply(osg::State& state) const override; + + int compare(const StateAttribute& sa) const override; + + void releaseGLObjects(osg::State* state = nullptr) const override; + + virtual bool isValid() const; + + protected: + virtual ~DebugGroup() = default; + + virtual void push(osg::State& state) const; + + virtual void pop(osg::State& state) const; + + GLenum mSource; + GLuint mId; + std::string mMessage; + + static std::map> sLastAppliedStack; + + friend EnableGLDebugOperation; + }; } #endif From 657da50d996d304cca4ee27884c46a290bb05223 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:36:34 +0000 Subject: [PATCH 09/33] Ensure GL_BLEND is disabled when drawing shadow maps --- components/sceneutil/shadowsbin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 312d4226b5..8b5c788dd0 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -54,7 +54,7 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); - mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) { From 7e045cff7560a16015ab60b984d1713fb549c42e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 20 Dec 2020 01:51:45 +0000 Subject: [PATCH 10/33] #include --- components/debug/gldebug.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/debug/gldebug.cpp b/components/debug/gldebug.cpp index 22d7bbe682..ee7dd608e4 100644 --- a/components/debug/gldebug.cpp +++ b/components/debug/gldebug.cpp @@ -32,6 +32,7 @@ either expressed or implied, of the FreeBSD Project. #include "gldebug.hpp" #include +#include #include From a36ed5f129ff4cb008c29413d1683da4fac59e9a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 23 Dec 2020 00:23:49 +0000 Subject: [PATCH 11/33] Optimise out redundant call We already had the results --- components/sceneutil/shadowsbin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 8b5c788dd0..a2424fe363 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -27,9 +27,9 @@ namespace osg::StateSet::ModeList::const_iterator mf = l.find(mode); if (mf == l.end()) return; - int flags = mf->second; + unsigned int flags = mf->second; bool newValue = flags & osg::StateAttribute::ON; - accumulateState(currentValue, newValue, isOverride, ss->getMode(mode)); + accumulateState(currentValue, newValue, isOverride, flags); } inline bool materialNeedShadows(osg::Material* m) From 11b4af49ce2166057c68994bf8258a253e504137 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 23 Dec 2020 01:24:15 +0000 Subject: [PATCH 12/33] Allow shadowsbin to optimise clockwise-wound meshes when face culling is off --- components/sceneutil/mwshadowtechnique.cpp | 6 +++++ components/sceneutil/shadowsbin.cpp | 31 +++++++++++++++++----- components/sceneutil/shadowsbin.hpp | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 39e47b0ea3..cfc94ba863 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -870,7 +870,10 @@ void SceneUtil::MWShadowTechnique::enableFrontFaceCulling() _useFrontFaceCulling = true; if (_shadowCastingStateSet) + { _shadowCastingStateSet->setAttribute(new osg::CullFace(osg::CullFace::FRONT), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + } } void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() @@ -878,7 +881,10 @@ void SceneUtil::MWShadowTechnique::disableFrontFaceCulling() _useFrontFaceCulling = false; if (_shadowCastingStateSet) + { + _shadowCastingStateSet->removeAttribute(osg::StateAttribute::CULLFACE); _shadowCastingStateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } } void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & shaderManager) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index a2424fe363..26cbd58c31 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -63,7 +63,7 @@ ShadowsBin::ShadowsBin() } } -StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache) +StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set& uninterestingCache, bool cullFaceOverridden) { std::vector return_path; State state; @@ -102,10 +102,13 @@ StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::un accumulateState(state.mAlphaFunc, static_cast(rap.first.get()), state.mAlphaFuncOverride, rap.second); } - // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it. - found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); - if (found != attributes.end()) - state.mImportantState = true; + if (!cullFaceOverridden) + { + // osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it unless GL_CULL_FACE is off or we flip face culling. + found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0)); + if (found != attributes.end()) + state.mImportantState = true; + } if ((*itr) != sg && !state.interesting()) uninterestingCache.insert(*itr); @@ -188,7 +191,21 @@ void ShadowsBin::sortImplementation() return; } StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get()); - // root is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state + + bool cullFaceOverridden = false; + while (root = root->_parent) + { + if (!root->getStateSet()) + continue; + unsigned int cullFaceFlags = root->getStateSet()->getMode(GL_CULL_FACE); + if (cullFaceFlags & osg::StateAttribute::OVERRIDE && !(cullFaceFlags & osg::StateAttribute::ON)) + { + cullFaceOverridden = true; + break; + } + } + noTestRoot->_leaves.reserve(_stateGraphList.size()); StateGraphList newList; std::unordered_set uninterestingCache; @@ -197,7 +214,7 @@ void ShadowsBin::sortImplementation() // Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList. // Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList. // Graphs containing other leaves need to be in newList. - StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache); + StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache, cullFaceOverridden); if (graphToAdd) newList.push_back(graphToAdd); } diff --git a/components/sceneutil/shadowsbin.hpp b/components/sceneutil/shadowsbin.hpp index d8bdd86071..1c63caf4bb 100644 --- a/components/sceneutil/shadowsbin.hpp +++ b/components/sceneutil/shadowsbin.hpp @@ -63,7 +63,7 @@ namespace SceneUtil } }; - osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting); + osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set& uninteresting, bool cullFaceOverridden); static void addPrototype(const std::string& name, const std::array, GL_ALWAYS - GL_NEVER + 1>& castingPrograms); }; From 8c3a786e5408cc8d2e857afc02ff1590c0ca8ef9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 24 Dec 2020 00:32:15 +0000 Subject: [PATCH 13/33] Unconditionally disable alpha testing when shaders are used --- components/shader/shadervisitor.cpp | 45 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index aeec391c67..7db9933169 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -374,39 +374,40 @@ namespace Shader writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); + + // back up removed state in case recreateShaders gets rid of the shader later + osg::ref_ptr removedState; + if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) + removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); + if (!removedState) + removedState = new osg::StateSet(); + if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); - // back up removed state in case recreateShaders gets rid of the shader later - osg::ref_ptr removedState; - if ((removedState = getRemovedState(*writableStateSet)) && !mAllowedToModifyStateSets) - removedState = new osg::StateSet(*removedState, osg::CopyOp::SHALLOW_COPY); - if (!removedState) - removedState = new osg::StateSet(); - - if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) - removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); - // This disables the deprecated fixed-function alpha test - writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); - const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); if (alphaFunc) removedState->setAttribute(alphaFunc->first, alphaFunc->second); // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } - if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) - { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; - if (mAllowedToModifyStateSets) - writableUserData = writableStateSet->getOrCreateUserDataContainer(); - else - writableUserData = getWritableUserDataContainer(*writableStateSet); + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + removedState->setMode(GL_ALPHA_TEST, writableStateSet->getMode(GL_ALPHA_TEST)); + // This disables the deprecated fixed-function alpha test + writableStateSet->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED); - updateRemovedState(*writableUserData, removedState); - } + if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) + { + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getOrCreateUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + updateRemovedState(*writableUserData, removedState); } osg::ref_ptr vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX)); From 8f4b856b449f4ad15e002dbb2afa88cd10ace588 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 26 Dec 2020 22:45:53 +0000 Subject: [PATCH 14/33] Initial A2C implementation --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/resource/scenemanager.cpp | 6 ++ components/resource/scenemanager.hpp | 3 + components/sceneutil/mwshadowtechnique.cpp | 2 +- components/shader/shadervisitor.cpp | 13 ++++ components/shader/shadervisitor.hpp | 4 ++ .../reference/modding/settings/shaders.rst | 11 ++++ files/settings-default.cfg | 5 ++ files/shaders/alpha.glsl | 60 +++++++++++++------ 9 files changed, 86 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 736e4f2ee1..a60077a169 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -219,6 +219,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2630cd4536..5f7116722e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -290,6 +290,11 @@ namespace Resource mApplyLightingToEnvMaps = apply; } + void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + SceneManager::~SceneManager() { // this has to be defined in the .cpp file as we can't delete incomplete types @@ -769,6 +774,7 @@ namespace Resource shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); + shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); return shaderVisitor; } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index fd75070a18..aae7d143f2 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -75,6 +75,8 @@ namespace Resource void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void setShaderPath(const std::string& path); /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded @@ -159,6 +161,7 @@ namespace Resource bool mAutoUseSpecularMaps; std::string mSpecularMapPattern; bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; osg::ref_ptr mInstanceCache; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index cfc94ba863..c781318faf 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -897,7 +897,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); - program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)} }, osg::Shader::FRAGMENT)); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"} }, osg::Shader::FRAGMENT)); } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7db9933169..4762549a7a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -382,6 +383,7 @@ namespace Shader if (!removedState) removedState = new osg::StateSet(); + defineMap["alphaToCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); @@ -391,6 +393,12 @@ namespace Shader removedState->setAttribute(alphaFunc->first, alphaFunc->second); // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + + if (mConvertAlphaTestToAlphaToCoverage) + { + writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); + defineMap["alphaToCoverage"] = "1"; + } } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) @@ -581,4 +589,9 @@ namespace Shader mApplyLightingToEnvMaps = apply; } + void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) + { + mConvertAlphaTestToAlphaToCoverage = convert; + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 2ba18107b5..606e06df97 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -40,6 +40,8 @@ namespace Shader void setApplyLightingToEnvMaps(bool apply); + void setConvertAlphaTestToAlphaToCoverage(bool convert); + void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -63,6 +65,8 @@ namespace Shader bool mApplyLightingToEnvMaps; + bool mConvertAlphaTestToAlphaToCoverage; + ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index e23cc3d548..49ada897e4 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -146,3 +146,14 @@ radial fog By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. + +convert alpha test to alpha-to-coverage +--------------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +When MSAA is off, this setting will have no visible effect, but might have a performance cost. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index c8805faa39..6629cb503e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -442,6 +442,11 @@ apply lighting to environment maps = false # This makes fogging independent from the viewing angle. Shaders will be used to render all objects. radial fog = false +# Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +# This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. +# When MSAA is off, this setting will have no visible effect, but might have a performance cost. +convert alpha test to alpha-to-coverage = false + [Input] # Capture control of the cursor prevent movement outside the window. diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 0c69df0ab2..b1c7ed149e 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -14,25 +14,49 @@ uniform float alphaRef; void alphaTest() { - #if @alphaFunc == FUNC_NEVER - discard; - #elif @alphaFunc == FUNC_LESS - if (gl_FragData[0].a > alphaRef) + #if @alphaToCoverage + float coverageAlpha = (gl_FragData[0].a - alphaRef) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; + + // Some functions don't make sense with A2C or are a pain to think about and no meshes use them anyway + // Use regular alpha testing in such cases until someone complains. + #if @alphaFunc == FUNC_NEVER discard; - #elif @alphaFunc == FUNC_EQUAL - if (gl_FragData[0].a != alphaRef) - discard; - #elif @alphaFunc == FUNC_LEQUAL - if (gl_FragData[0].a >= alphaRef) - discard; - #elif @alphaFunc == FUNC_GREATER - if (gl_FragData[0].a < alphaRef) - discard; - #elif @alphaFunc == FUNC_NOTEQUAL - if (gl_FragData[0].a == alphaRef) - discard; - #elif @alphaFunc == FUNC_GEQUAL - if (gl_FragData[0].a <= alphaRef) + #elif @alphaFunc == FUNC_LESS + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + gl_FragData[0].a = 1.0 - coverageAlpha; + #elif @alphaFunc == FUNC_GREATER + gl_FragData[0].a = coverageAlpha; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + gl_FragData[0].a = coverageAlpha; + #endif + #else + #if @alphaFunc == FUNC_NEVER discard; + #elif @alphaFunc == FUNC_LESS + if (gl_FragData[0].a > alphaRef) + discard; + #elif @alphaFunc == FUNC_EQUAL + if (gl_FragData[0].a != alphaRef) + discard; + #elif @alphaFunc == FUNC_LEQUAL + if (gl_FragData[0].a >= alphaRef) + discard; + #elif @alphaFunc == FUNC_GREATER + if (gl_FragData[0].a < alphaRef) + discard; + #elif @alphaFunc == FUNC_NOTEQUAL + if (gl_FragData[0].a == alphaRef) + discard; + #elif @alphaFunc == FUNC_GEQUAL + if (gl_FragData[0].a <= alphaRef) + discard; + #endif #endif } \ No newline at end of file From 54d465e3dcb5e5f98cbe12b8f85e6abc394968ea Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 27 Dec 2020 03:07:04 +0000 Subject: [PATCH 15/33] Do all alpha testing early and only once --- files/shaders/objects_fragment.glsl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c79ad5e196..995c2adaf3 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -109,11 +109,14 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); - alphaTest(); #else gl_FragData[0] = vec4(1.0); #endif + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + alphaTest(); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif @@ -152,10 +155,6 @@ void main() #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; - alphaTest(); - float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; #if !PER_PIXEL_LIGHTING From 54853380cdbdeb76195244a5f85ac75a956dd005 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 28 Dec 2020 22:39:09 +0000 Subject: [PATCH 16/33] Enable multisampling in RTTs to support A2C --- apps/openmw/mwrender/characterpreview.cpp | 3 ++- apps/openmw/mwrender/localmap.cpp | 12 +++++++++++- apps/openmw/mwrender/water.cpp | 24 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 89db3e5f46..061f4d5094 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -135,7 +136,7 @@ namespace MWRender mCamera->setProjectionMatrixAsPerspective(fovYDegrees, sizeX/static_cast(sizeY), 0.1f, 10000.f); // zNear and zFar are autocomputed mCamera->setViewport(0, 0, sizeX, sizeY); mCamera->setRenderOrder(osg::Camera::PRE_RENDER); - mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture); + mCamera->attach(osg::Camera::COLOR_BUFFER, mTexture, 0, 0, false, Settings::Manager::getInt("antialiasing", "Video")); mCamera->setName("CharacterPreview"); mCamera->setComputeNearFarMode(osg::Camera::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); mCamera->setCullMask(~(Mask_UpdateVisitor)); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 401e21ae46..90c85f39fa 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -233,7 +233,17 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - camera->attach(osg::Camera::COLOR_BUFFER, texture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + camera->attach(osg::Camera::COLOR_BUFFER, texture, 0, 0, false, samples, colourSamples); camera->addChild(mSceneRoot); mRoot->addChild(camera); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index b9018e0a27..3d3ec4ef6e 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -271,7 +271,17 @@ public: mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - attach(osg::Camera::COLOR_BUFFER, mRefractionTexture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + attach(osg::Camera::COLOR_BUFFER, mRefractionTexture, 0, 0, false, samples, colourSamples); mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture->setTextureSize(rttSize, rttSize); @@ -356,7 +366,17 @@ public: mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - attach(osg::Camera::COLOR_BUFFER, mReflectionTexture); + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + attach(osg::Camera::COLOR_BUFFER, mReflectionTexture, 0, 0, false, samples, colourSamples); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace (new osg::FrontFace); From 9b99c76032bb96002fae6a5d67a41423a935119f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 1 Jan 2021 16:01:33 +0000 Subject: [PATCH 17/33] Clamp alpha reference to avoid degenerate case --- files/shaders/alpha.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index b1c7ed149e..cb38831c5b 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -15,7 +15,7 @@ uniform float alphaRef; void alphaTest() { #if @alphaToCoverage - float coverageAlpha = (gl_FragData[0].a - alphaRef) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; + float coverageAlpha = (gl_FragData[0].a - clamp(alphaRef, 0.0001, 0.9999)) / max(fwidth(gl_FragData[0].a), 0.0001) + 0.5; // Some functions don't make sense with A2C or are a pain to think about and no meshes use them anyway // Use regular alpha testing in such cases until someone complains. From c75d7ceada68c67ba62cb6111a850d9f34aa2f19 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 02:50:19 +0000 Subject: [PATCH 18/33] Ensure alpha test is off by default --- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a60077a169..567cd88ac6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -381,6 +381,8 @@ namespace MWRender // Hopefully, anything genuinely requiring the default alpha func of GL_ALWAYS explicitly sets it mRootNode->getOrCreateStateSet()->setAttribute(Shader::RemovedAlphaFunc::getInstance(GL_ALWAYS)); + // The transparent renderbin sets alpha testing on because that was faster on old GPUs. It's now slower and breaks things. + mRootNode->getOrCreateStateSet()->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); mUniformNear = mRootNode->getOrCreateStateSet()->getUniform("near"); mUniformFar = mRootNode->getOrCreateStateSet()->getUniform("far"); From 69386df03602b6c1f1b4acc0e72d31f84dcb538d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 18:27:25 +0000 Subject: [PATCH 19/33] Correct alpha testing functions --- files/shaders/alpha.glsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index cb38831c5b..2551a02052 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -40,22 +40,22 @@ void alphaTest() #if @alphaFunc == FUNC_NEVER discard; #elif @alphaFunc == FUNC_LESS - if (gl_FragData[0].a > alphaRef) + if (gl_FragData[0].a >= alphaRef) discard; #elif @alphaFunc == FUNC_EQUAL if (gl_FragData[0].a != alphaRef) discard; #elif @alphaFunc == FUNC_LEQUAL - if (gl_FragData[0].a >= alphaRef) + if (gl_FragData[0].a > alphaRef) discard; #elif @alphaFunc == FUNC_GREATER - if (gl_FragData[0].a < alphaRef) + if (gl_FragData[0].a <= alphaRef) discard; #elif @alphaFunc == FUNC_NOTEQUAL if (gl_FragData[0].a == alphaRef) discard; #elif @alphaFunc == FUNC_GEQUAL - if (gl_FragData[0].a <= alphaRef) + if (gl_FragData[0].a < alphaRef) discard; #endif #endif From f2eed5594a1ebd46909312eb6bf76742214853ab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 18:27:46 +0000 Subject: [PATCH 20/33] Don't use A2C when MSAA is off --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/water.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 90c85f39fa..28b7c816bc 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -235,7 +235,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 567cd88ac6..17a6527fc9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -219,7 +219,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 3d3ec4ef6e..8955248810 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,7 +273,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. @@ -368,7 +368,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders")) + if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. From e3fd5efcfefc22d9323463c7f274252b895c1b01 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 19:09:06 +0000 Subject: [PATCH 21/33] Disable A2C for alpha-blended drawables --- components/shader/shadervisitor.cpp | 14 +++++++++++++- components/shader/shadervisitor.hpp | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 4762549a7a..165be6745d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -26,8 +26,10 @@ namespace Shader , mColorMode(0) , mMaterialOverridden(false) , mAlphaTestOverridden(false) + , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) + , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -330,6 +332,15 @@ namespace Shader } } } + + unsigned int alphaBlend = stateset->getMode(GL_BLEND); + if (alphaBlend != osg::StateAttribute::INHERIT && (!mRequirements.back().mAlphaBlendOverridden || alphaBlend & osg::StateAttribute::PROTECTED)) + { + if (alphaBlend & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaBlendOverridden = true; + + mRequirements.back().mAlphaBlend = alphaBlend & osg::StateAttribute::ON; + } } void ShaderVisitor::pushRequirements(osg::Node& node) @@ -394,7 +405,8 @@ namespace Shader // This prevents redundant glAlphaFunc calls while letting the shadows bin still see the test writableStateSet->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - if (mConvertAlphaTestToAlphaToCoverage) + // Blending won't work with A2C as we use the alpha channel for coverage. gl_SampleCoverage from ARB_sample_shading would save the day, but requires GLSL 130 + if (mConvertAlphaTestToAlphaToCoverage && !reqs.mAlphaBlend) { writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); defineMap["alphaToCoverage"] = "1"; diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 606e06df97..9daeab29ba 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -84,9 +84,11 @@ namespace Shader bool mMaterialOverridden; bool mAlphaTestOverridden; + bool mAlphaBlendOverridden; GLenum mAlphaFunc; float mAlphaRef; + bool mAlphaBlend; bool mNormalHeight; // true if normal map has height info in alpha channel From d061ae809601ae2b258fe6627d456c236f93ffc9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 2 Jan 2021 23:27:28 +0000 Subject: [PATCH 22/33] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f3d18161..214159db16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ Feature #2404: Levelled List can not be placed into a container Feature #2686: Timestamps in openmw.log Feature #4894: Consider actors as obstacles for pathfinding + Feature #4899: Alpha-To-Coverage Anti-Aliasing for alpha testing Feature #5043: Head Bobbing Feature #5199: Improve Scene Colors Feature #5297: Add a search function to the "Datafiles" tab of the OpenMW launcher From d13459ecf98c4dfe0949d10ad4b5dec73817ab62 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 5 Jan 2021 22:21:54 +0000 Subject: [PATCH 23/33] Scale mipmap alpha to preserve coverage --- files/shaders/alpha.glsl | 23 +++++++++++++++++++++++ files/shaders/objects_fragment.glsl | 3 +++ files/shaders/shadowcasting_fragment.glsl | 2 ++ 3 files changed, 28 insertions(+) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 2551a02052..6ead9e6ca2 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -12,6 +12,29 @@ uniform float alphaRef; #endif +float mipmapLevel(vec2 scaleduv) +{ + vec2 dUVdx = dFdx(scaleduv); + vec2 dUVdy = dFdy(scaleduv); + float maxDUVSquared = max(dot(dUVdx, dUVdx), dot(dUVdy, dUVdy)); + return max(0.0, 0.5 * log2(maxDUVSquared)); +} + +float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) +{ + #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + vec2 textureSize; + #ifdef GL_EXT_gpu_shader4 + textureSize = textureSize2D(diffuseMap, 0); + #else + textureSize = 256.0; + #endif + return 1.0 + mipmapLevel(uv * textureSize) * 0.25; + #else + return 1.0; + #endif +} + void alphaTest() { #if @alphaToCoverage diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 995c2adaf3..c5f397789a 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,5 +1,7 @@ #version 120 +#extension EXT_gpu_shader4: enable + #if @diffuseMap uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; @@ -109,6 +111,7 @@ void main() #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); #else gl_FragData[0] = vec4(1.0); #endif diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 8c53c542bd..ea8a63313e 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,5 +1,7 @@ #version 120 +#extension EXT_gpu_shader4: enable + uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; From 4ed32520018dff5debf8d66ff7e1d7aa367a6c3b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Jan 2021 18:13:51 +0000 Subject: [PATCH 24/33] Check for EXT_gpu_shader4 CPU-side Mesa lies and always defines GL_EXT_gpu_shader4 even when the extension isn't present. --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/sceneutil/mwshadowtechnique.cpp | 7 ++++++- components/shader/shadervisitor.cpp | 7 +++++++ files/shaders/alpha.glsl | 4 ++-- files/shaders/objects_fragment.glsl | 4 +++- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 17a6527fc9..95e98f55b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -252,6 +252,7 @@ namespace MWRender globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0"; globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0"; globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0"; + globalDefines["useGPUShader4"] = "0"; // It is unnecessary to stop/start the viewer as no frames are being rendered yet. mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index c781318faf..41e64e1247 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -892,12 +892,17 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting_vertex.glsl", {}, osg::Shader::VERTEX); + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; program = new osg::Program(); program->addShader(castingVertexShader); - program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"} }, osg::Shader::FRAGMENT)); + program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, + {"alphaToCoverage", "0"}, + {"useGPUShader4", useGPUShader4} + }, osg::Shader::FRAGMENT)); } } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 165be6745d..d719daec28 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -411,6 +412,12 @@ namespace Shader writableStateSet->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB, osg::StateAttribute::ON); defineMap["alphaToCoverage"] = "1"; } + + // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size + osg::ref_ptr exts = osg::GLExtensions::Get(0, false); + if (exts && exts->isGpuShader4Supported) + defineMap["useGPUShader4"] = "1"; + // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 6ead9e6ca2..05be801e93 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -24,10 +24,10 @@ float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) { #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER vec2 textureSize; - #ifdef GL_EXT_gpu_shader4 + #if @useGPUShader4 textureSize = textureSize2D(diffuseMap, 0); #else - textureSize = 256.0; + textureSize = vec2(256.0); #endif return 1.0 + mipmapLevel(uv * textureSize) * 0.25; #else diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c5f397789a..37cca273fc 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,6 +1,8 @@ #version 120 -#extension EXT_gpu_shader4: enable +#if @useGPUShader4 + #extension EXT_gpu_shader4: require +#endif #if @diffuseMap uniform sampler2D diffuseMap; From 0068c7bb252428571c1f6ae4ca57220873d0c701 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Jan 2021 17:32:15 +0000 Subject: [PATCH 25/33] Spec says GL_ --- files/shaders/objects_fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 37cca273fc..693c045847 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,7 +1,7 @@ #version 120 #if @useGPUShader4 - #extension EXT_gpu_shader4: require + #extension GL_EXT_gpu_shader4: require #endif #if @diffuseMap From b8ee32e3173c9226d92cf0d9f60a07fe1c75a479 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 Feb 2021 23:32:04 +0000 Subject: [PATCH 26/33] Support A2C for groundcover --- files/shaders/groundcover_fragment.glsl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index 77fd32e58b..c3339ba763 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -30,11 +30,7 @@ centroid varying vec3 shadowDiffuseLighting; #include "shadows_fragment.glsl" #include "lighting.glsl" - -float calc_coverage(float a, float alpha_ref, float falloff_rate) -{ - return clamp(falloff_rate * (a - alpha_ref) + alpha_ref, 0.0, 1.0); -} +#include "alpha.glsl" void main() { @@ -55,12 +51,13 @@ void main() gl_FragData[0] = vec4(1.0); #endif - gl_FragData[0].a = calc_coverage(gl_FragData[0].a, 128.0/255.0, 4.0); - - float shadowing = unshadowedLightRatio(linearDepth); if (euclideanDepth > @groundcoverFadeStart) gl_FragData[0].a *= 1.0-smoothstep(@groundcoverFadeStart, @groundcoverFadeEnd, euclideanDepth); + alphaTest(); + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 lighting; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; From 9be258d26009ed7a3a1d03ca7d0d66455fd7beca Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 19 Feb 2021 19:59:48 +0000 Subject: [PATCH 27/33] Make it possible to reinstate FFP state easily --- apps/openmw/mwrender/groundcover.cpp | 2 ++ components/resource/scenemanager.cpp | 6 +++++ components/resource/scenemanager.hpp | 7 ++++- components/shader/shadervisitor.cpp | 39 ++++++++++++++++++++++++++++ components/shader/shadervisitor.hpp | 11 ++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 049118c904..4390dae22c 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -258,6 +258,8 @@ namespace MWRender // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); + mSceneManager->reinstateRemovedState(node); + InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9d36f1caeb..e46ce20168 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -257,6 +257,12 @@ namespace Resource node->accept(*shaderVisitor); } + void SceneManager::reinstateRemovedState(osg::ref_ptr node) + { + osg::ref_ptr reinstateRemovedStateVisitor = new Shader::ReinstateRemovedStateVisitor(false); + node->accept(*reinstateRemovedStateVisitor); + } + void SceneManager::setClampLighting(bool clamp) { mClampLighting = clamp; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 0648ce06fe..bf69a8c4be 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -75,9 +75,14 @@ namespace Resource Shader::ShaderManager& getShaderManager(); - /// Re-create shaders for this node, need to call this if texture stages or vertex color mode have changed. + /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); + /// Applying shaders to a node may replace some fixed-function state. + /// This restores it. + /// When editing such state, it should be reinstated before the edits, and shaders should be recreated afterwards. + void reinstateRemovedState(osg::ref_ptr node); + /// @see ShaderVisitor::setForceShaders void setForceShaders(bool force); bool getForceShaders() const; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a79e24f8be..782ffa016d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -614,4 +614,43 @@ namespace Shader mTranslucentFramebuffer = translucent; } + ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mAllowedToModifyStateSets(allowedToModifyStateSets) + { + } + + void ReinstateRemovedStateVisitor::apply(osg::Node& node) + { + if (node.getStateSet()) + { + osg::ref_ptr removedState = getRemovedState(*node.getStateSet()); + if (removedState) + { + osg::ref_ptr writableStateSet; + if (mAllowedToModifyStateSets) + writableStateSet = node.getStateSet(); + else + writableStateSet = getWritableStateSet(node); + + // user data is normally shallow copied so shared with the original stateset + osg::ref_ptr writableUserData; + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + unsigned int index = writableUserData->getUserObjectIndex("removedState"); + writableUserData->removeUserObject(index); + + for (const auto&[mode, value] : removedState->getModeList()) + writableStateSet->setMode(mode, value); + + for (const auto& attribute : removedState->getAttributeList()) + writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + } + } + + traverse(node); + } + } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index daf42ff265..f7c6f83127 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -112,6 +112,17 @@ namespace Shader bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); }; + class ReinstateRemovedStateVisitor : public osg::NodeVisitor + { + public: + ReinstateRemovedStateVisitor(bool allowedToModifyStateSets); + + void apply(osg::Node& node) override; + + private: + bool mAllowedToModifyStateSets; + }; + } #endif From 153ab57ae38f4930d8e60312b1edd437b8ea0163 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 24 Feb 2021 17:45:44 +0000 Subject: [PATCH 28/33] Make assignment in while loop condition obviously intentional --- components/sceneutil/shadowsbin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 26cbd58c31..5a4096f5c3 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -185,7 +185,7 @@ void ShadowsBin::sortImplementation() root = root->_parent; const osg::StateSet* ss = root->getStateSet(); if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp - || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertargets sg just in case + || ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertarget's sg just in case break; if (!root->_parent) return; @@ -194,7 +194,7 @@ void ShadowsBin::sortImplementation() // noTestRoot is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state bool cullFaceOverridden = false; - while (root = root->_parent) + while ((root = root->_parent)) { if (!root->getStateSet()) continue; From 4ed67d8597520a0a4e24213125550e7b5e1b2ef9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 24 Feb 2021 18:01:06 +0000 Subject: [PATCH 29/33] Improve A2C setting name --- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/water.cpp | 4 ++-- docs/source/reference/modding/settings/shaders.rst | 4 ++-- files/settings-default.cfg | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index f4845fa2ba..a487fb1e94 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -239,7 +239,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) + if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4770746c7e..9f89d1bff2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -213,7 +213,7 @@ namespace MWRender resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders")); - resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); + resourceSystem->getSceneManager()->setConvertAlphaTestToAlphaToCoverage(Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1); osg::ref_ptr sceneRoot = new SceneUtil::LightManager; sceneRoot->setLightingMask(Mask_Lighting); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index ed1fcb98f0..0ab3de7efd 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -273,7 +273,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) + if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. @@ -368,7 +368,7 @@ public: unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("convert alpha test to alpha-to-coverage", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) + if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 657f7bfc63..acc8482991 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -148,13 +148,13 @@ By default, the fog becomes thicker proportionally to your distance from the cli This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV. Note that the rendering will act as if you have 'force shaders' option enabled with this on, which means that shaders will be used to render all objects and the terrain. -convert alpha test to alpha-to-coverage +antialias alpha test --------------------------------------- :Type: boolean :Range: True/False :Default: False -Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. +Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage when :ref:`antialiasing` is on. This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. When MSAA is off, this setting will have no visible effect, but might have a performance cost. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9c88356463..d439c46351 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -445,7 +445,7 @@ radial fog = false # Convert the alpha test (cutout/punchthrough alpha) to alpha-to-coverage. # This allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. # When MSAA is off, this setting will have no visible effect, but might have a performance cost. -convert alpha test to alpha-to-coverage = false +antialias alpha test = false [Input] From f5a87ee46def4892ee736790fced2435cfe63dd4 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 26 Feb 2021 19:01:27 +0000 Subject: [PATCH 30/33] Refactor out duplicated RTT setup code --- apps/openmw/mwrender/localmap.cpp | 13 ++----------- apps/openmw/mwrender/water.cpp | 25 +++---------------------- components/sceneutil/util.cpp | 16 ++++++++++++++++ components/sceneutil/util.hpp | 4 ++++ 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index a487fb1e94..64931aa880 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" @@ -237,17 +238,7 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - unsigned int samples = 0; - unsigned int colourSamples = 0; - if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) - { - // Alpha-to-coverage requires a multisampled framebuffer. - // OSG will set that up automatically and resolve it to the specified single-sample texture for us. - // For some reason, two samples are needed, at least with some drivers. - samples = 2; - colourSamples = 1; - } - camera->attach(osg::Camera::COLOR_BUFFER, texture, 0, 0, false, samples, colourSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(camera, osg::Camera::COLOR_BUFFER, texture); camera->addChild(mSceneRoot); mRoot->addChild(camera); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 0ab3de7efd..71b1b49855 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -271,17 +272,7 @@ public: mRefractionTexture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); mRefractionTexture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - unsigned int samples = 0; - unsigned int colourSamples = 0; - if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) - { - // Alpha-to-coverage requires a multisampled framebuffer. - // OSG will set that up automatically and resolve it to the specified single-sample texture for us. - // For some reason, two samples are needed, at least with some drivers. - samples = 2; - colourSamples = 1; - } - attach(osg::Camera::COLOR_BUFFER, mRefractionTexture, 0, 0, false, samples, colourSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mRefractionTexture); mRefractionDepthTexture = new osg::Texture2D; mRefractionDepthTexture->setTextureSize(rttSize, rttSize); @@ -366,17 +357,7 @@ public: mReflectionTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); mReflectionTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - unsigned int samples = 0; - unsigned int colourSamples = 0; - if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) - { - // Alpha-to-coverage requires a multisampled framebuffer. - // OSG will set that up automatically and resolve it to the specified single-sample texture for us. - // For some reason, two samples are needed, at least with some drivers. - samples = 2; - colourSamples = 1; - } - attach(osg::Camera::COLOR_BUFFER, mReflectionTexture, 0, 0, false, samples, colourSamples); + SceneUtil::attachAlphaToCoverageFriendlyFramebufferToCamera(this, osg::Camera::COLOR_BUFFER, mReflectionTexture); // XXX: should really flip the FrontFace on each renderable instead of forcing clockwise. osg::ref_ptr frontFace (new osg::FrontFace); diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 2c0d8efa0c..8a381681b1 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace SceneUtil { @@ -260,4 +261,19 @@ osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resourc return glowUpdater; } +bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture * texture, unsigned int level, unsigned int face, bool mipMapGeneration) +{ + unsigned int samples = 0; + unsigned int colourSamples = 0; + if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) + { + // Alpha-to-coverage requires a multisampled framebuffer. + // OSG will set that up automatically and resolve it to the specified single-sample texture for us. + // For some reason, two samples are needed, at least with some drivers. + samples = 2; + colourSamples = 1; + } + camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples); +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 303d609f57..8103ed87a8 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -60,6 +61,9 @@ namespace SceneUtil bool hasUserDescription(const osg::Node* node, const std::string pattern); osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1); + + // Alpha-to-coverage requires a multisampled framebuffer, so we need to set that up for RTTs + bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg::Camera::BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face = 0, bool mipMapGeneration = false); } #endif From 46a1715d8a63230c04755a39c34cddae8f6e6fb2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 26 Feb 2021 20:10:58 +0000 Subject: [PATCH 31/33] Actually return something --- components/sceneutil/util.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 8a381681b1..fa3c7d26da 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -265,7 +265,8 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: { unsigned int samples = 0; unsigned int colourSamples = 0; - if (Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1) + bool addMSAAIntermediateTarget = Settings::Manager::getBool("antialias alpha test", "Shaders") && Settings::Manager::getInt("antialiasing", "Video") > 1; + if (addMSAAIntermediateTarget) { // Alpha-to-coverage requires a multisampled framebuffer. // OSG will set that up automatically and resolve it to the specified single-sample texture for us. @@ -274,6 +275,7 @@ bool attachAlphaToCoverageFriendlyFramebufferToCamera(osg::Camera* camera, osg:: colourSamples = 1; } camera->attach(buffer, texture, level, face, mipMapGeneration, samples, colourSamples); + return addMSAAIntermediateTarget; } } From f09b0fc1bd63aed88020ccf1ea575307848deed0 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Mar 2021 17:21:36 +0000 Subject: [PATCH 32/33] Put groundcover alphafunc where shader visitor can see it I'd already made this change so don't know why it disappeared instead of being included in b8ee32e3 --- apps/openmw/mwrender/groundcover.cpp | 4 ++++ apps/openmw/mwrender/renderingmanager.cpp | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 4390dae22c..0baa85c52a 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,5 +1,6 @@ #include "groundcover.hpp" +#include #include #include @@ -265,6 +266,9 @@ namespace MWRender group->addChild(node); } + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); group->getBound(); group->setNodeMask(Mask_Groundcover); mSceneManager->recreateShaders(group, "groundcover", false, true); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9f89d1bff2..fd912aa2d3 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -312,10 +311,6 @@ namespace MWRender groundcoverRoot->setName("Groundcover Root"); sceneRoot->addChild(groundcoverRoot); - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f/255.f); - groundcoverRoot->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - mGroundcoverUpdater = new GroundcoverUpdater; groundcoverRoot->addUpdateCallback(mGroundcoverUpdater); From 31032664064b36709db603bd055316d5fc780edd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Mar 2021 01:31:35 +0000 Subject: [PATCH 33/33] Add correct preprocessor check when enabling extension --- files/shaders/groundcover_fragment.glsl | 4 ++++ files/shaders/shadowcasting_fragment.glsl | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/files/shaders/groundcover_fragment.glsl b/files/shaders/groundcover_fragment.glsl index c3339ba763..bc84ded619 100644 --- a/files/shaders/groundcover_fragment.glsl +++ b/files/shaders/groundcover_fragment.glsl @@ -1,5 +1,9 @@ #version 120 +#if @useGPUShader4 + #extension EXT_gpu_shader4: require +#endif + #define GROUNDCOVER #if @diffuseMap diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index ea8a63313e..a83e155ca3 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -1,6 +1,8 @@ #version 120 -#extension EXT_gpu_shader4: enable +#if @useGPUShader4 + #extension EXT_gpu_shader4: require +#endif uniform sampler2D diffuseMap; varying vec2 diffuseMapUV;