#include "shadervisitor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "removedalphafunc.hpp" #include "shadermanager.hpp" 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) , mColorMode(0) , mMaterialOverridden(false) , mAlphaTestOverridden(false) , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) { } ShaderVisitor::ShaderRequirements::~ShaderRequirements() { } ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mForceShaders(false) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { mRequirements.emplace_back(); } void ShaderVisitor::setForceShaders(bool force) { mForceShaders = force; } void ShaderVisitor::apply(osg::Node& node) { if (node.getStateSet()) { pushRequirements(node); applyStateSet(node.getStateSet(), node); traverse(node); popRequirements(); } else traverse(node); } osg::StateSet* getWritableStateSet(osg::Node& node) { if (!node.getStateSet()) return node.getOrCreateStateSet(); osg::ref_ptr newStateSet = new osg::StateSet(*node.getStateSet(), osg::CopyOp::SHALLOW_COPY); node.setStateSet(newStateSet); 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* removedState) { unsigned int index = userData.getUserObjectIndex("removedState"); if (index < userData.getNumUserObjects()) userData.setUserObject(index, removedState); else 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" }; bool isTextureNameRecognized(const std::string& name) { for (unsigned int i=0; i stateset, osg::Node& node) { osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); bool shaderRequired = false; if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; const osg::Texture* normalMap = nullptr; const osg::Texture* specularMap = nullptr; const osg::Texture* bumpMap = nullptr; for(unsigned int unit=0;unitgetTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { const osg::Texture* texture = attr->asTexture(); if (texture) { std::string texName = texture->getName(); if ((texName.empty() || !isTextureNameRecognized(texName)) && unit == 0) texName = "diffuseMap"; if (texName == "normalHeightMap") { mRequirements.back().mNormalHeight = true; texName = "normalMap"; } if (!texName.empty()) { mRequirements.back().mTextures[unit] = texName; if (texName == "normalMap") { mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // normal maps are by default off since the FFP can't render them, now that we'll use shaders switch to On writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); normalMap = texture; } else if (texName == "diffuseMap") diffuseMap = texture; else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") { bumpMap = texture; mRequirements.back().mShaderRequired = true; if (!writableStateSet) writableStateSet = getWritableStateSet(node); // Bump maps are off by default as well writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); } else if (texName == "envMap" && mApplyLightingToEnvMaps) { mRequirements.back().mShaderRequired = true; } } else if (!mTranslucentFramebuffer) Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } } if (mAutoUseNormalMaps && diffuseMap != nullptr && normalMap == nullptr && diffuseMap->getImage(0)) { std::string normalMapFileName = diffuseMap->getImage(0)->getFileName(); osg::ref_ptr image; bool normalHeight = false; std::string normalHeightMap = normalMapFileName; Misc::StringUtils::replaceLast(normalHeightMap, ".", mNormalHeightMapPattern + "."); if (mImageManager.getVFS()->exists(normalHeightMap)) { image = mImageManager.getImage(normalHeightMap); normalHeight = true; } else { Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + "."); if (mImageManager.getVFS()->exists(normalMapFileName)) { image = mImageManager.getImage(normalMapFileName); } } // Avoid using the auto-detected normal map if it's already being used as a bump map. // It's probably not an actual normal map. bool hasNamesakeBumpMap = image && bumpMap && bumpMap->getImage(0) && image->getFileName() == bumpMap->getImage(0)->getFileName(); if (!hasNamesakeBumpMap && image) { osg::ref_ptr normalMapTex (new osg::Texture2D(image)); normalMapTex->setTextureSize(image->s(), image->t()); normalMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); normalMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); normalMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); normalMapTex->setName("normalMap"); int unit = texAttributes.size(); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttributeAndModes(unit, normalMapTex, osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "normalMap"; mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mShaderRequired = true; mRequirements.back().mNormalHeight = normalHeight; } } if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + "."); if (mImageManager.getVFS()->exists(specularMapFileName)) { osg::ref_ptr image (mImageManager.getImage(specularMapFileName)); osg::ref_ptr specularMapTex (new osg::Texture2D(image)); specularMapTex->setTextureSize(image->s(), image->t()); specularMapTex->setWrap(osg::Texture::WRAP_S, diffuseMap->getWrap(osg::Texture::WRAP_S)); specularMapTex->setWrap(osg::Texture::WRAP_T, diffuseMap->getWrap(osg::Texture::WRAP_T)); specularMapTex->setFilter(osg::Texture::MIN_FILTER, diffuseMap->getFilter(osg::Texture::MIN_FILTER)); specularMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); specularMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); specularMapTex->setName("specularMap"); int unit = texAttributes.size(); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttributeAndModes(unit, specularMapTex, osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "specularMap"; mRequirements.back().mShaderRequired = true; } } if (diffuseMap) { if (!writableStateSet) writableStateSet = getWritableStateSet(node); // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. // Also it should probably belong to the shader manager or be applied by the shadows bin writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); 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 (addedState && attributeMap != &removedAttributes && addedState->hasAttribute(it->first)) continue; if (it->first.first == osg::StateAttribute::MATERIAL) { // 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->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mMaterialOverridden = true; const osg::Material* mat = static_cast(it->second.first.get()); 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(); } } } } 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) { mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; } void ShaderVisitor::popRequirements() { mRequirements.pop_back(); } void ShaderVisitor::createProgram(const ShaderRequirements &reqs) { if (!reqs.mShaderRequired && !mForceShaders) { ensureFFP(*reqs.mNode); return; } osg::Node& node = *reqs.mNode; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) 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; i::const_iterator texIt = reqs.mTextures.begin(); texIt != reqs.mTextures.end(); ++texIt) { defineMap[texIt->second] = "1"; defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; defineMap["geometryShader"] = "1"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); 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(); 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 && !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) 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 && !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()) { // 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); } 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; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; osg::ref_ptr vertexShader (mShaderManager.getShader(shaderPrefix + "_vertex.glsl", defineMap, osg::Shader::VERTEX)); osg::ref_ptr fragmentShader (mShaderManager.getShader(shaderPrefix + "_fragment.glsl", defineMap, osg::Shader::FRAGMENT)); if (vertexShader && fragmentShader) { 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); } } } void ShaderVisitor::ensureFFP(osg::Node& node) { if (!node.getStateSet() || !node.getStateSet()->getAttribute(osg::StateAttribute::PROGRAM)) return; osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); else writableStateSet = getWritableStateSet(node); // 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)) { if (!writableUserData) { if (mAllowedToModifyStateSets) writableUserData = writableStateSet->getUserDataContainer(); else writableUserData = getWritableUserDataContainer(*writableStateSet); } unsigned int index = writableUserData->getUserObjectIndex("removedState"); writableUserData->removeUserObject(index); writableStateSet->merge(*removedState); } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) { bool useShader = reqs.mShaderRequired || mForceShaders; bool generateTangents = reqs.mTexStageRequiringTangents != -1; bool changed = false; if (mAllowedToModifyStateSets && (useShader || generateTangents)) { // make sure that all UV sets are there for (std::map::const_iterator it = reqs.mTextures.begin(); it != reqs.mTextures.end(); ++it) { if (sourceGeometry.getTexCoordArray(it->first) == nullptr) { sourceGeometry.setTexCoordArray(it->first, sourceGeometry.getTexCoordArray(0)); changed = true; } } if (generateTangents) { osg::ref_ptr generator (new osgUtil::TangentSpaceGenerator); generator->generate(&sourceGeometry, reqs.mTexStageRequiringTangents); sourceGeometry.setTexCoordArray(7, generator->getTangentArray(), osg::Array::BIND_PER_VERTEX); changed = true; } } return changed; } void ShaderVisitor::apply(osg::Geometry& geometry) { bool needPop = (geometry.getStateSet() != nullptr); if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it { pushRequirements(geometry); applyStateSet(geometry.getStateSet(), geometry); } if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); adjustGeometry(geometry, reqs); createProgram(reqs); } else ensureFFP(geometry); if (needPop) popRequirements(); } void ShaderVisitor::apply(osg::Drawable& drawable) { // non-Geometry drawable (e.g. particle system) bool needPop = (drawable.getStateSet() != nullptr); if (drawable.getStateSet()) { pushRequirements(drawable); applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); createProgram(reqs); if (auto rig = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = rig->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) rig->setSourceGeometry(sourceGeometry); } else if (auto morph = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) morph->setSourceGeometry(sourceGeometry); } } else ensureFFP(drawable); if (needPop) popRequirements(); } void ShaderVisitor::setAllowedToModifyStateSets(bool allowed) { mAllowedToModifyStateSets = allowed; } void ShaderVisitor::setAutoUseNormalMaps(bool use) { mAutoUseNormalMaps = use; } void ShaderVisitor::setNormalMapPattern(const std::string &pattern) { mNormalMapPattern = pattern; } void ShaderVisitor::setNormalHeightMapPattern(const std::string &pattern) { mNormalHeightMapPattern = pattern; } void ShaderVisitor::setAutoUseSpecularMaps(bool use) { mAutoUseSpecularMaps = use; } void ShaderVisitor::setSpecularMapPattern(const std::string &pattern) { mSpecularMapPattern = pattern; } void ShaderVisitor::setApplyLightingToEnvMaps(bool apply) { mApplyLightingToEnvMaps = apply; } void ShaderVisitor::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; } void ShaderVisitor::setTranslucentFramebuffer(bool translucent) { 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); } }