diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index ab25fd744..702ab3366 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2011,6 +2011,11 @@ namespace NifOsg { osg::ref_ptr blendFunc (new osg::BlendFunc(getBlendMode((alphaprop->flags>>1)&0xf), getBlendMode((alphaprop->flags>>5)&0xf))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 88d44d2f6..745d93daf 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -904,6 +904,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh program->addShader(castingVertexShader); program->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", { {"alphaFunc", std::to_string(alphaFunc)}, {"alphaToCoverage", "0"}, + {"adjustCoverage", "1"}, {"useGPUShader4", useGPUShader4} }, osg::Shader::FRAGMENT)); } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index c21593f35..ebf9d40c4 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,6 +22,68 @@ namespace Shader { + class AddedState : public osg::Object + { + public: + AddedState() = default; + AddedState(const AddedState& rhs, const osg::CopyOp& copyOp) + : osg::Object(rhs, copyOp) + , mUniforms(rhs.mUniforms) + , mModes(rhs.mModes) + , mAttributes(rhs.mAttributes) + { + } + + void addUniform(const std::string& name) { mUniforms.emplace(name); } + void setMode(osg::StateAttribute::GLMode mode) { mModes.emplace(mode); } + void setAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { mAttributes.emplace(typeMemberPair); } + + void setAttribute(const osg::StateAttribute* attribute) + { + mAttributes.emplace(attribute->getTypeMemberPair()); + } + template + void setAttribute(osg::ref_ptr attribute) { setAttribute(attribute.get()); } + + void setAttributeAndModes(const osg::StateAttribute* attribute) + { + setAttribute(attribute); + InterrogateModesHelper helper(this); + attribute->getModeUsage(helper); + } + template + void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } + + bool hasUniform(const std::string& name) { return mUniforms.count(name); } + bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } + bool hasAttribute(osg::StateAttribute::TypeMemberPair typeMemberPair) { return mAttributes.count(typeMemberPair); } + bool hasAttribute(osg::StateAttribute::Type type, unsigned int member) { return hasAttribute(osg::StateAttribute::TypeMemberPair(type, member)); } + + const std::set& getAttributes() { return mAttributes; } + + bool empty() + { + return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + } + + META_Object(Shader, AddedState) + + private: + class InterrogateModesHelper : public osg::StateAttribute::ModeUsage + { + public: + InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} + void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } + void usesTextureMode(osg::StateAttribute::GLMode mode) override {} + + private: + AddedState* mTracker; + }; + + std::unordered_set mUniforms; + std::unordered_set mModes; + std::set mAttributes; + }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mShaderRequired(false) @@ -105,14 +167,32 @@ namespace Shader return static_cast(stateSet.getUserDataContainer()->getUserObject("removedState")); } - void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* stateSet) + void updateRemovedState(osg::UserDataContainer& userData, osg::StateSet* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) - userData.setUserObject(index, stateSet); + userData.setUserObject(index, removedState); else - userData.addUserObject(stateSet); - stateSet->setName("removedState"); + userData.addUserObject(removedState); + removedState->setName("removedState"); + } + + AddedState* getAddedState(osg::StateSet& stateSet) + { + if (!stateSet.getUserDataContainer()) + return nullptr; + + return static_cast(stateSet.getUserDataContainer()->getUserObject("addedState")); + } + + void updateAddedState(osg::UserDataContainer& userData, AddedState* addedState) + { + unsigned int index = userData.getUserObjectIndex("addedState"); + if (index < userData.getNumUserObjects()) + userData.setUserObject(index, addedState); + else + userData.addUserObject(addedState); + addedState->setName("addedState"); } const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap" }; @@ -280,11 +360,13 @@ namespace Shader osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); + osg::ref_ptr addedState = getAddedState(*stateset); + for (const auto* attributeMap : std::initializer_list{ &attributes, &removedAttributes }) { for (osg::StateSet::AttributeList::const_iterator it = attributeMap->begin(); it != attributeMap->end(); ++it) { - if (attributeMap != &removedAttributes && removedAttributes.count(it->first)) + if (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) continue; if (it->first.first == osg::StateAttribute::MATERIAL) { @@ -296,9 +378,6 @@ namespace Shader const osg::Material* mat = static_cast(it->second.first.get()); - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - int colorMode; switch (mat->getColorMode()) { @@ -376,6 +455,10 @@ namespace Shader writableStateSet = node.getOrCreateStateSet(); else writableStateSet = getWritableStateSet(node); + osg::ref_ptr addedState = new AddedState; + osg::ref_ptr previousAddedState = getAddedState(*writableStateSet); + if (!previousAddedState) + previousAddedState = new AddedState; ShaderManager::DefineMap defineMap; for (unsigned int i=0; iaddUniform(new osg::Uniform("colorMode", reqs.mColorMode)); + addedState->addUniform("colorMode"); defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); @@ -403,26 +487,35 @@ namespace Shader removedState = new osg::StateSet(); defineMap["alphaToCoverage"] = "0"; + defineMap["adjustCoverage"] = "0"; if (reqs.mAlphaFunc != osg::AlphaFunc::ALWAYS) { writableStateSet->addUniform(new osg::Uniform("alphaRef", reqs.mAlphaRef)); + addedState->addUniform("alphaRef"); if (!removedState->getAttributePair(osg::StateAttribute::ALPHAFUNC)) { const auto* alphaFunc = writableStateSet->getAttributePair(osg::StateAttribute::ALPHAFUNC); - if (alphaFunc) + if (alphaFunc && !previousAddedState->hasAttribute(osg::StateAttribute::ALPHAFUNC, 0)) 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); + addedState->setAttribute(RemovedAlphaFunc::getInstance(reqs.mAlphaFunc)); // 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); + addedState->setMode(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB); defineMap["alphaToCoverage"] = "1"; } + // Adjusting coverage isn't safe with blending on as blending requires the alpha to be intact. + // Maybe we could also somehow (e.g. userdata) detect when the diffuse map has coverage-preserving mip maps in the future + if (!reqs.mAlphaBlend) + defineMap["adjustCoverage"] = "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) @@ -430,10 +523,11 @@ namespace Shader // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } - if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT) + if (writableStateSet->getMode(GL_ALPHA_TEST) != osg::StateAttribute::INHERIT && !previousAddedState->hasMode(GL_ALPHA_TEST)) 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); + addedState->setMode(GL_ALPHA_TEST); if (!removedState->getModeList().empty() || !removedState->getAttributeList().empty()) { @@ -447,6 +541,18 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (!addedState->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); + + updateAddedState(*writableUserData, addedState); + } + defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; std::string shaderPrefix; @@ -458,11 +564,14 @@ namespace Shader if (vertexShader && fragmentShader) { - writableStateSet->setAttributeAndModes(mShaderManager.getProgram(vertexShader, fragmentShader), osg::StateAttribute::ON); + auto program = mShaderManager.getProgram(vertexShader, fragmentShader); + writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); + addedState->setAttributeAndModes(program); for (std::map::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { writableStateSet->addUniform(new osg::Uniform(texIt->second.c_str(), texIt->first), osg::StateAttribute::ON); + addedState->addUniform(texIt->second); } } } @@ -477,24 +586,57 @@ namespace Shader else writableStateSet = getWritableStateSet(node); - writableStateSet->removeAttribute(osg::StateAttribute::PROGRAM); + // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits + osg::ref_ptr writableUserData; + + if (osg::ref_ptr addedState = getAddedState(*writableStateSet)) + { + if (mAllowedToModifyStateSets) + writableUserData = writableStateSet->getUserDataContainer(); + else + writableUserData = getWritableUserDataContainer(*writableStateSet); + + unsigned int index = writableUserData->getUserObjectIndex("addedState"); + writableUserData->removeUserObject(index); + + // O(n log n) to use StateSet::removeX, but this is O(n) + for (auto itr = writableStateSet->getUniformList().begin(); itr != writableStateSet->getUniformList().end();) + { + if (addedState->hasUniform(itr->first)) + writableStateSet->getUniformList().erase(itr); + else + ++itr; + } + + for (auto itr = writableStateSet->getModeList().begin(); itr != writableStateSet->getModeList().end();) + { + if (addedState->hasMode(itr->first)) + writableStateSet->getModeList().erase(itr); + else + ++itr; + } + + // StateAttributes track the StateSets they're attached to + // We don't have access to the function to do that, and can't call removeAttribute with an iterator + for (const auto& [type, member] : addedState->getAttributes()) + writableStateSet->removeAttribute(type, member); + } + if (osg::ref_ptr removedState = getRemovedState(*writableStateSet)) { - // user data is normally shallow copied so shared with the original stateset - osg::ref_ptr writableUserData; + if (!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); + writableStateSet->merge(*removedState); } } diff --git a/files/shaders/alpha.glsl b/files/shaders/alpha.glsl index 05be801e9..46b748236 100644 --- a/files/shaders/alpha.glsl +++ b/files/shaders/alpha.glsl @@ -22,7 +22,7 @@ float mipmapLevel(vec2 scaleduv) float coveragePreservingAlphaScale(sampler2D diffuseMap, vec2 uv) { - #if @alphaFunc != FUNC_ALWAYS && @alphaFunc != FUNC_NEVER + #if @adjustCoverage vec2 textureSize; #if @useGPUShader4 textureSize = textureSize2D(diffuseMap, 0);