#include "shadervisitor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "removedalphafunc.hpp" #include "shadermanager.hpp" namespace Shader { /** * Miniature version of osg::StateSet used to track state added by the shader visitor which should be ignored when * it's applied a second time, and removed when shaders are removed. * Actual StateAttributes aren't kept as they're recoverable from the StateSet this is attached to - we just want * the TypeMemberPair as that uniquely identifies which of those StateAttributes it was we're tracking. * Not all StateSet features have been added yet - we implement an equivalently-named method to each of the StateSet * methods called in createProgram, and implement new ones as they're needed. */ 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()); } void setTextureAttribute(int unit, osg::StateAttribute::TypeMemberPair typeMemberPair) { mTextureAttributes[unit].emplace(typeMemberPair); } void setTextureAttribute(unsigned int unit, const osg::StateAttribute* attribute) { mTextureAttributes[unit].emplace(attribute->getTypeMemberPair()); } template void setTextureAttribute(unsigned int unit, osg::ref_ptr attribute) { setTextureAttribute(unit, attribute.get()); } bool hasUniform(const std::string& name) { return mUniforms.count(name); } bool hasMode(osg::StateAttribute::GLMode mode) { return mModes.count(mode); } bool hasAttribute(const 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; } const std::unordered_map>& getTextureAttributes() { return mTextureAttributes; } bool empty() { return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureAttributes.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; }; using ModeSet = std::unordered_set; using AttributeSet = std::set; std::unordered_set mUniforms; ModeSet mModes; AttributeSet mAttributes; std::unordered_map mTextureAttributes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() : mAlphaTestOverridden(false) , mAlphaBlendOverridden(false) , mAlphaFunc(GL_ALWAYS) , mAlphaRef(1.0) , mAlphaBlend(false) , mBlendFuncOverridden(false) , mAdditiveBlending(false) , mDiffuseHeight(false) , mNormalHeight(false) , mReconstructNormalZ(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) { } ShaderVisitor::ShaderVisitor( ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(true) , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) , mAdjustCoverageForAlphaTest(false) , mSupportsNormalsRT(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { } void ShaderVisitor::apply(osg::Node& node) { bool needPop = false; if (node.getStateSet() || mRequirements.empty()) { needPop = true; pushRequirements(node); if (node.getStateSet()) applyStateSet(node.getStateSet(), node); } traverse(node); if (needPop) popRequirements(); } 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"); } // This list is used both for detecting known texture types (including added normal maps etc.) and setting the // shader defines. Normal maps and normal height maps both get sent to the shader as a normal map, so the latter // must be detected separately. const char* defaultTextures[] = { "diffuseMap", "normalMap", "emissiveMap", "darkMap", "detailMap", "envMap", "specularMap", "decalMap", "bumpMap", "glossMap" }; bool isTextureNameRecognized(std::string_view name) { if (std::find(std::begin(defaultTextures), std::end(defaultTextures), name) != std::end(defaultTextures)) return true; else return name == "normalHeightMap"; } void ShaderVisitor::applyStateSet(osg::ref_ptr stateset, osg::Node& node) { osg::StateSet* writableStateSet = nullptr; if (mAllowedToModifyStateSets) writableStateSet = node.getStateSet(); const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList(); bool softEffect = false; if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); 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; unit < texAttributes.size(); ++unit) { const osg::StateAttribute* attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { const osg::Texture* texture = attr->asTexture(); if (texture) { std::string texName = SceneUtil::getTextureType(*stateset, *texture, unit); 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; normalMap = texture; } else if (texName == "diffuseMap") { int applyMode; // Oblivion parallax if (node.getUserValue("applyMode", applyMode) && applyMode == 4) { mRequirements.back().mDiffuseHeight = true; mRequirements.back().mTexStageRequiringTangents = unit; } diffuseMap = texture; } else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") { bumpMap = texture; } } else 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 + "."); const VFS::Path::Normalized normalHeightMapPath(normalHeightMap); if (mImageManager.getVFS()->exists(normalHeightMapPath)) { image = mImageManager.getImage(normalHeightMapPath); normalHeight = true; } else { Misc::StringUtils::replaceLast(normalMapFileName, ".", mNormalMapPattern + "."); const VFS::Path::Normalized normalMapPath(normalMapFileName); if (mImageManager.getVFS()->exists(normalMapPath)) { image = mImageManager.getImage(normalMapPath); } } // 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()); normalMap = normalMapTex; int unit = static_cast(texAttributes.size()); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttribute(unit, normalMapTex, osg::StateAttribute::ON); writableStateSet->setTextureAttribute(unit, new SceneUtil::TextureType(normalHeight ? "normalHeightMap" : "normalMap"), osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "normalMap"; mRequirements.back().mTexStageRequiringTangents = unit; mRequirements.back().mNormalHeight = normalHeight; } } if (normalMap != nullptr && normalMap->getImage(0)) { // Special handling for red-green normal maps (e.g. BC5 or R8G8) switch (SceneUtil::computeUnsizedPixelFormat(normalMap->getImage(0)->getPixelFormat())) { case GL_RG: case GL_RG_INTEGER: { mRequirements.back().mReconstructNormalZ = true; mRequirements.back().mNormalHeight = false; } } } if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); Misc::StringUtils::replaceLast(specularMapFileName, ".", mSpecularMapPattern + "."); const VFS::Path::Normalized specularMapPath(specularMapFileName); if (mImageManager.getVFS()->exists(specularMapPath)) { osg::ref_ptr image(mImageManager.getImage(specularMapPath)); 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()); int unit = static_cast(texAttributes.size()); if (!writableStateSet) writableStateSet = getWritableStateSet(node); writableStateSet->setTextureAttribute(unit, specularMapTex, osg::StateAttribute::ON); writableStateSet->setTextureAttribute( unit, new SceneUtil::TextureType("specularMap"), osg::StateAttribute::ON); mRequirements.back().mTextures[unit] = "specularMap"; } } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); osg::StateSet::AttributeList removedAttributes; if (osg::ref_ptr removedState = getRemovedState(*stateset)) removedAttributes = removedState->getAttributeList(); 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) { if (!writableStateSet) writableStateSet = getWritableStateSet(node); static_cast(it->second.first.get()) ->setStateSet(writableStateSet, it->second.second); } 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(); } } else if (it->first.first == osg::StateAttribute::BLENDFUNC) { if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) mRequirements.back().mBlendFuncOverridden = true; const osg::BlendFunc* blend = static_cast(it->second.first.get()); mRequirements.back().mAdditiveBlending = blend->getSource() == osg::BlendFunc::SRC_ALPHA && blend->getDestination() == osg::BlendFunc::ONE; } } } } 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) { if (mRequirements.empty()) mRequirements.emplace_back(); else mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; } void ShaderVisitor::popRequirements() { mRequirements.pop_back(); } void ShaderVisitor::createProgram(const ShaderRequirements& reqs) { /** * The shader visitor is supposed to be idempotent and undoable. * That means we need to back up state we've removed (so it can be restored and/or considered by further * applications of the visitor) and track which state we added (so it can be removed and/or ignored by further * applications of the visitor). * Before editing writableStateSet in a way that explicitly removes state or might overwrite existing state, it * should be copied to removedState, another StateSet, unless it's there already or was added by a previous * application of the visitor (is in previousAddedState). * If it's a new class of state that's not already handled by ReinstateRemovedStateVisitor::apply, make sure to * add handling there. * Similarly, any time new state is added to writableStateSet, the equivalent method should be called on * addedState. * If that method doesn't exist yet, implement it - we don't use a full StateSet as we only need to check * existence, not equality, and don't need to actually get the value as we can get it from writableStateSet * instead. */ 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 < sizeof(defaultTextures) / sizeof(defaultTextures[0]); ++i) { defineMap[defaultTextures[i]] = "0"; defineMap[std::string(defaultTextures[i]) + std::string("UV")] = "0"; } for (std::map::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); } if (defineMap["diffuseMap"] == "0") { writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); addedState->addUniform("useDiffuseMapForShadowAlpha"); } defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; defineMap["reconstructNormalZ"] = reqs.mReconstructNormalZ ? "1" : "0"; defineMap["alphaFunc"] = std::to_string(reqs.mAlphaFunc); defineMap["additiveBlending"] = reqs.mAdditiveBlending ? "1" : "0"; 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::PROTECTED); 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 (mAdjustCoverageForAlphaTest && !reqs.mAlphaBlend) defineMap["adjustCoverage"] = "1"; // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size if (SceneUtil::getGLExtensions().isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } bool isParticle = dynamic_cast(&node); defineMap["particle"] = isParticle ? "1" : "0"; bool simpleLighting = false; node.getUserValue("simpleLighting", simpleLighting); if (simpleLighting) defineMap["simpleLighting"] = "1"; if (simpleLighting || isParticle) defineMap["forcePPL"] = "0"; bool particleOcclusion = false; node.getUserValue("particleOcclusion", particleOcclusion); defineMap["particleOcclusion"] = particleOcclusion && mWeatherParticleOcclusion ? "1" : "0"; if (reqs.mAlphaBlend && mSupportsNormalsRT) { if (reqs.mSoftParticles) defineMap["disableNormals"] = "1"; auto colorMask = new osg::ColorMaski(1, false, false, false, false); writableStateSet->setAttribute(colorMask); addedState->setAttribute(colorMask); } if (reqs.mSoftParticles) { const int unitSoftEffect = mShaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); addedState->addUniform("opaqueDepthTex"); } 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); } defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; ShaderManager::SamplerBindingMap samplers; for (const auto& [unit, name] : reqs.mTextures) samplers[name] = unit; auto program = mShaderManager.getProgram(shaderPrefix, defineMap, mProgramTemplate, samplers); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); addedState->setAttributeAndModes(std::move(program)); 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); } } bool ShaderVisitor::adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs) { bool changed = false; if (mAllowedToModifyStateSets) { // make sure that all UV sets are there // it's not safe to assume there's one for slot zero, so try and use one from another slot if possible // if there are none at all, bail. // the TangentSpaceGenerator would bail, but getTangentArray would give an empty array, which is enough to // bypass null checks, but feeds the driver a bad pointer if (sourceGeometry.getTexCoordArray(0) == nullptr) { for (const auto& array : sourceGeometry.getTexCoordArrayList()) { if (array) { sourceGeometry.setTexCoordArray(0, array); break; } } if (sourceGeometry.getTexCoordArray(0) == nullptr) return changed; } for (const auto& [unit, name] : reqs.mTextures) { if (sourceGeometry.getTexCoordArray(unit) == nullptr) { sourceGeometry.setTexCoordArray(unit, sourceGeometry.getTexCoordArray(0)); changed = true; } } bool generateTangents = reqs.mTexStageRequiringTangents != -1; 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() || mRequirements.empty(); if (needPop) pushRequirements(geometry); if (geometry.getStateSet()) // TODO: check if stateset affects shader permutation before pushing it applyStateSet(geometry.getStateSet(), geometry); if (!mRequirements.empty()) { const ShaderRequirements& reqs = mRequirements.back(); adjustGeometry(geometry, reqs); createProgram(reqs); } if (needPop) popRequirements(); } void ShaderVisitor::apply(osg::Drawable& drawable) { bool needPop = drawable.getStateSet() || mRequirements.empty(); // We need to push and pop a requirements object because particle systems can have // different shader requirements to other drawables, so might need a different shader variant. if (!needPop && dynamic_cast(&drawable)) needPop = true; if (needPop) { pushRequirements(drawable); if (drawable.getStateSet()) applyStateSet(drawable.getStateSet(), drawable); } 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(std::move(sourceGeometry)); } else if (auto morph = dynamic_cast(&drawable)) { osg::ref_ptr sourceGeometry = morph->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) morph->setSourceGeometry(std::move(sourceGeometry)); } else if (auto osgaRig = dynamic_cast(&drawable)) { osg::ref_ptr sourceOsgaRigGeometry = osgaRig->getSourceRigGeometry(); osg::ref_ptr sourceGeometry = sourceOsgaRigGeometry->getSourceGeometry(); if (sourceGeometry && adjustGeometry(*sourceGeometry, reqs)) { sourceOsgaRigGeometry->setSourceGeometry(std::move(sourceGeometry)); osgaRig->setSourceRigGeometry(std::move(sourceOsgaRigGeometry)); } } 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::setConvertAlphaTestToAlphaToCoverage(bool convert) { mConvertAlphaTestToAlphaToCoverage = convert; } void ShaderVisitor::setAdjustCoverageForAlphaTest(bool adjustCoverage) { mAdjustCoverageForAlphaTest = adjustCoverage; } ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) { } void ReinstateRemovedStateVisitor::apply(osg::Node& node) { // TODO: this may eventually need to remove added state. // If so, we can migrate from explicitly copying removed state to just calling osg::StateSet::merge. // Not everything is transferred from removedState yet - implement more when createProgram starts marking more // as removed. 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); } }