From 9389cfaa42532c532e00f666de259ef983b73554 Mon Sep 17 00:00:00 2001 From: "glassmancody.info" Date: Sat, 20 Nov 2021 18:39:20 -0800 Subject: [PATCH 1/2] mac os driver workaround and shadervisitor fixes --- apps/openmw/mwrender/postprocessor.cpp | 21 +++++ apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwrender/sky.cpp | 2 - components/nifosg/nifloader.cpp | 1 - components/shader/shadervisitor.cpp | 89 +++++++++++++------ components/shader/shadervisitor.hpp | 3 + .../reference/modding/settings/shaders.rst | 3 + files/shaders/objects_fragment.glsl | 2 +- 8 files changed, 90 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index b08cfe1f71..19655c5cdb 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -102,8 +102,15 @@ namespace : mOpaqueDepthFbo(new osg::FrameBufferObject) , mSourceFbo(sourceFbo) , mOpaqueDepthTex(opaqueDepthTex) + , mColorAttached(false) { mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::DEPTH_BUFFER, osg::FrameBufferAttachment(opaqueDepthTex)); + +#ifdef __APPLE__ + // Mac OS drivers complain that a FBO is incomplete if it has no color attachment + mOpaqueDepthFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER, osg::FrameBufferAttachment(new osg::RenderBuffer(mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_RGB))); + mColorAttached = true; +#endif } void drawImplementation(osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override @@ -114,7 +121,10 @@ namespace osg::GLExtensions* ext = state.get(); mSourceFbo->apply(state, osg::FrameBufferObject::READ_FRAMEBUFFER); + postBindOperation(state); + mOpaqueDepthFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + postBindOperation(state); ext->glBlitFramebuffer(0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), 0, 0, mOpaqueDepthTex->getTextureWidth(), mOpaqueDepthTex->getTextureHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); @@ -124,9 +134,20 @@ namespace bin->drawImplementation(renderInfo, previous); } private: + void postBindOperation(osg::State& state) + { + if (mColorAttached) + return; + #if !defined(OSG_GLES1_AVAILABLE) && !defined(OSG_GLES2_AVAILABLE) && !defined(OSG_GLES3_AVAILABLE) + state.glDrawBuffer(GL_NONE); + state.glReadBuffer(GL_NONE); + #endif + } + osg::ref_ptr mOpaqueDepthFbo; osg::ref_ptr mSourceFbo; osg::ref_ptr mOpaqueDepthTex; + bool mColorAttached; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 30be74c839..9abd2e7487 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -316,6 +316,7 @@ namespace MWRender resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); // Shadows and radial fog have problems with fixed-function mode bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") + || Settings::Manager::getBool("soft particles", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows") || lightingMethod != SceneUtil::LightingMethod::FFP diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index fcb7447cf3..8d24ada5be 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -636,7 +636,6 @@ namespace MWRender mParticleNode = new osg::PositionAttitudeTransform; mParticleNode->addCullCallback(mUnderwaterSwitch); mParticleNode->setNodeMask(Mask_WeatherParticles); - mParticleNode->getOrCreateStateSet(); mRootNode->addChild(mParticleNode); } @@ -668,7 +667,6 @@ namespace MWRender ps->getParticle(particleIndex)->update(0, true); } - ps->getOrCreateStateSet(); ps->setUserValue("simpleLighting", true); } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 91d2300161..f85946f7bd 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1049,7 +1049,6 @@ namespace NifOsg void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); - partsys->getOrCreateStateSet(); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); const Nif::NiParticleSystemController* partctrl = nullptr; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 95e0a75f18..2063546034 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,6 +1,7 @@ #include "shadervisitor.hpp" #include +#include #include #include @@ -37,12 +38,14 @@ namespace Shader , mUniforms(rhs.mUniforms) , mModes(rhs.mModes) , mAttributes(rhs.mAttributes) + , mTextureModes(rhs.mTextureModes) { } 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 setTextureMode(int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setAttribute(const osg::StateAttribute* attribute) { @@ -64,12 +67,20 @@ namespace Shader 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)); } + bool hasTextureMode(int unit, osg::StateAttribute::GLMode mode) + { + auto it = mTextureModes.find(unit); + if (it == mTextureModes.cend()) + return false; + + return it->second.count(mode); + } const std::set& getAttributes() { return mAttributes; } bool empty() { - return mUniforms.empty() && mModes.empty() && mAttributes.empty(); + return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty(); } META_Object(Shader, AddedState) @@ -86,9 +97,12 @@ namespace Shader AddedState* mTracker; }; + using ModeSet = std::unordered_set; + std::unordered_set mUniforms; - std::unordered_set mModes; + ModeSet mModes; std::set mAttributes; + std::unordered_map mTextureModes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() @@ -102,6 +116,8 @@ namespace Shader , mAlphaBlend(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) + , mSoftParticles(false) + , mSoftParticleSize(0.f) , mNode(nullptr) { } @@ -213,6 +229,8 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + osg::ref_ptr addedState = getAddedState(*stateset); + if (!texAttributes.empty()) { const osg::Texture* diffuseMap = nullptr; @@ -224,6 +242,9 @@ namespace Shader const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { + if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) + continue; + const osg::Texture* texture = attr->asTexture(); if (texture) { @@ -350,7 +371,6 @@ 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 }) { @@ -545,6 +565,25 @@ namespace Shader updateRemovedState(*writableUserData, removedState); } + if (reqs.mSoftParticles) + { + osg::ref_ptr depth = new SceneUtil::AutoDepth; + depth->setWriteMask(false); + writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + addedState->setAttribute(depth); + + writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); + addedState->addUniform("particleSize"); + + writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); + addedState->addUniform("opaqueDepthTex"); + + writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); + addedState->setTextureMode(2, GL_TEXTURE_2D); + } + + defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; + if (!addedState->empty()) { // user data is normally shallow copied so shared with the original stateset @@ -557,27 +596,6 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - bool softParticles = false; - - if (mOpaqueDepthTex) - { - auto partsys = dynamic_cast(&node); - - if (partsys) - { - softParticles = true; - - auto depth = new SceneUtil::AutoDepth; - depth->setWriteMask(false); - writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - writableStateSet->addUniform(new osg::Uniform("particleSize", partsys->getDefaultParticleTemplate().getSizeRange().maximum)); - writableStateSet->addUniform(new osg::Uniform("opaqueDepthTex", 2)); - writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - } - } - - defineMap["softParticles"] = softParticles ? "1" : "0"; - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -719,13 +737,22 @@ namespace Shader void ShaderVisitor::apply(osg::Drawable& drawable) { - // non-Geometry drawable (e.g. particle system) - bool needPop = (drawable.getStateSet() != nullptr); + auto partsys = dynamic_cast(&drawable); - if (drawable.getStateSet()) + bool needPop = drawable.getStateSet() || partsys; + + if (needPop) { pushRequirements(drawable); - applyStateSet(drawable.getStateSet(), drawable); + + if (partsys && mOpaqueDepthTex) + { + mRequirements.back().mSoftParticles = true; + mRequirements.back().mSoftParticleSize = partsys->getDefaultParticleTemplate().getSizeRange().maximum; + } + + if (drawable.getStateSet()) + applyStateSet(drawable.getStateSet(), drawable); } if (!mRequirements.empty()) @@ -831,6 +858,12 @@ namespace Shader for (const auto& attribute : removedState->getAttributeList()) writableStateSet->setAttribute(attribute.second.first, attribute.second.second); + + for (unsigned int unit = 0; unit < removedState->getTextureModeList().size(); ++unit) + { + for (const auto&[mode, value] : removedState->getTextureModeList()[unit]) + writableStateSet->setTextureMode(unit, mode, value); + } } } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index d80e697fd8..72dec05b5e 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -101,6 +101,9 @@ namespace Shader // -1 == no tangents required int mTexStageRequiringTangents; + bool mSoftParticles; + float mSoftParticleSize; + // the Node that requested these requirements osg::Node* mNode; }; diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index b9d8cfe1b9..5629e321d0 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -282,3 +282,6 @@ intersection between individual particles and other opaque geometry by blending between them. Note, this relies on overriding specific properties of particle systems that potentially differ from the source content, this setting may change the look of some particle systems. + +Note that the rendering will act as if you have 'force shaders' option enabled. +This means that shaders will be used to render all objects and the terrain. \ No newline at end of file diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 99ed44919b..bae9ff7951 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -226,7 +226,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @softParticles +#if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade(); #endif From 14a330609fad0164ed4036832836d34d04b26c38 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 8 Dec 2021 20:44:10 +0000 Subject: [PATCH 2/2] ShaderVisitor improvements * Adds comments explaining the less-than-obvious aspects. * Adds comments explaining what to do when adding new stuff. * Some fixes caused by those comments not historically existing. * Add a TODO comment to something which may catch fire in the future. --- components/shader/shadervisitor.cpp | 130 +++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 2063546034..107665369c 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -29,6 +29,15 @@ 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. + * When expanding tracking to cover new things, ensure they're accounted for in ensureFFP. + */ class AddedState : public osg::Object { public: @@ -45,7 +54,6 @@ namespace Shader 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 setTextureMode(int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } void setAttribute(const osg::StateAttribute* attribute) { @@ -63,6 +71,25 @@ namespace Shader template void setAttributeAndModes(osg::ref_ptr attribute) { setAttributeAndModes(attribute.get()); } + void setTextureMode(unsigned int unit, osg::StateAttribute::GLMode mode) { mTextureModes[unit].emplace(mode); } + 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()); } + + void setTextureAttributeAndModes(unsigned int unit, const osg::StateAttribute* attribute) + { + setTextureAttribute(unit, attribute); + InterrogateModesHelper helper(this, unit); + attribute->getModeUsage(helper); + } + template + void setTextureAttributeAndModes(unsigned int unit, osg::ref_ptr attribute) { setTextureAttributeAndModes(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); } @@ -77,10 +104,11 @@ namespace Shader } const std::set& getAttributes() { return mAttributes; } + const std::unordered_map>& getTextureAttributes() { return mTextureAttributes; } bool empty() { - return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty(); + return mUniforms.empty() && mModes.empty() && mAttributes.empty() && mTextureModes.empty() && mTextureAttributes.empty(); } META_Object(Shader, AddedState) @@ -89,20 +117,26 @@ namespace Shader class InterrogateModesHelper : public osg::StateAttribute::ModeUsage { public: - InterrogateModesHelper(AddedState* tracker) : mTracker(tracker) {} + InterrogateModesHelper(AddedState* tracker, unsigned int textureUnit = 0) + : mTracker(tracker) + , mTextureUnit(textureUnit) + {} void usesMode(osg::StateAttribute::GLMode mode) override { mTracker->setMode(mode); } - void usesTextureMode(osg::StateAttribute::GLMode mode) override {} + void usesTextureMode(osg::StateAttribute::GLMode mode) override { mTracker->setTextureMode(mTextureUnit, mode); } private: AddedState* mTracker; + unsigned int mTextureUnit; }; using ModeSet = std::unordered_set; + using AttributeSet = std::set; std::unordered_set mUniforms; ModeSet mModes; - std::set mAttributes; - std::unordered_map mTextureModes; + AttributeSet mAttributes; + std::unordered_map mTextureModes; + std::unordered_map mTextureAttributes; }; ShaderVisitor::ShaderRequirements::ShaderRequirements() @@ -229,6 +263,7 @@ namespace Shader if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired) mRequirements.back().mShaderRequired = true; + // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); if (!texAttributes.empty()) @@ -242,6 +277,8 @@ namespace Shader const osg::StateAttribute *attr = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE); if (attr) { + // If textures ever get removed in createProgram, expand this to check we're operating on main texture attribute list + // rather than the removed list if (addedState && addedState->hasTextureMode(unit, GL_TEXTURE_2D)) continue; @@ -462,6 +499,22 @@ namespace Shader return; } + /** + * 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) @@ -486,7 +539,10 @@ namespace Shader } if (defineMap["diffuseMap"] == "0") + { writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + addedState->addUniform("useDiffuseMapForShadowAlpha"); + } defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; @@ -495,7 +551,6 @@ namespace Shader 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); @@ -570,7 +625,7 @@ namespace Shader osg::ref_ptr depth = new SceneUtil::AutoDepth; depth->setWriteMask(false); writableStateSet->setAttributeAndModes(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - addedState->setAttribute(depth); + addedState->setAttributeAndModes(depth); writableStateSet->addUniform(new osg::Uniform("particleSize", reqs.mSoftParticleSize)); addedState->addUniform("particleSize"); @@ -579,23 +634,11 @@ namespace Shader addedState->addUniform("opaqueDepthTex"); writableStateSet->setTextureAttributeAndModes(2, mOpaqueDepthTex, osg::StateAttribute::ON); - addedState->setTextureMode(2, GL_TEXTURE_2D); + addedState->setTextureAttributeAndModes(2, mOpaqueDepthTex); } defineMap["softParticles"] = reqs.mSoftParticles ? "1" : "0"; - 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); - } - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -615,6 +658,18 @@ namespace Shader addedState->addUniform(texIt->second); } } + + 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); + } } void ShaderVisitor::ensureFFP(osg::Node& node) @@ -627,6 +682,18 @@ namespace Shader else writableStateSet = getWritableStateSet(node); + /** + * We might have been using shaders temporarily with the node (e.g. if a GlowUpdater applied a temporary + * environment map for a temporary enchantment). + * We therefore need to remove any state doing so added, and restore any that it removed. + * This is kept track of in createProgram in the StateSet's userdata. + * If new classes of state get added, handling it here is required - not all StateSet features are implemented + * in AddedState yet as so far they've not been necessary. + * Removed state requires no particular special handling as it's dealt with by merging StateSets. + * We don't need to worry about state in writableStateSet having the OVERRIDE flag as if it's in both, it's also + * in addedState, and gets removed first. + */ + // user data is normally shallow copied so shared with the original stateset - we'll need to copy before edits osg::ref_ptr writableUserData; @@ -661,6 +728,23 @@ namespace Shader // 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); + + for (unsigned int unit = 0; unit < writableStateSet->getTextureModeList().size(); ++unit) + { + for (auto itr = writableStateSet->getTextureModeList()[unit].begin(); itr != writableStateSet->getTextureModeList()[unit].end();) + { + if (addedState->hasTextureMode(unit, itr->first)) + writableStateSet->getTextureModeList()[unit].erase(itr++); + else + ++itr; + } + } + + for (const auto& [unit, attributeList] : addedState->getTextureAttributes()) + { + for (const auto& [type, member] : attributeList) + writableStateSet->removeTextureAttribute(unit, type); + } } @@ -833,6 +917,10 @@ namespace Shader 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());