From 8e9571d155893a20b09adea1dae1e0d684fe65b5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 4 Dec 2015 00:06:22 +0100 Subject: [PATCH] Double buffer the light StateAttributes and StateSets Fixes a race condition where the position of a light could jump a frame ahead. --- apps/openmw/mwrender/animation.cpp | 32 ++++++++++++++++-------- components/sceneutil/lightcontroller.cpp | 2 +- components/sceneutil/lightmanager.cpp | 29 ++++++++++++--------- components/sceneutil/lightmanager.hpp | 23 +++++++++++------ 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 7a2cce7c9..ecfb2df14 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1091,8 +1091,7 @@ namespace MWRender } osg::ref_ptr lightSource = new SceneUtil::LightSource; - osg::Light* light = new osg::Light; - lightSource->setLight(light); + osg::ref_ptr light (new osg::Light); lightSource->setNodeMask(Mask_Lighting); const MWWorld::Fallback* fallback = MWBase::Environment::get().getWorld()->getFallback(); @@ -1123,6 +1122,8 @@ namespace MWRender light->setAmbient(osg::Vec4f(0,0,0,1)); light->setSpecular(osg::Vec4f(0,0,0,0)); + lightSource->setLight(light); + osg::ref_ptr ctrl (new SceneUtil::LightController); ctrl->setDiffuse(light->getDiffuse()); if (esmLight->mData.mFlags & ESM::Light::Flicker) @@ -1318,22 +1319,31 @@ namespace MWRender } else { - if (!mGlowLight) + effect += 3; + float radius = effect * 66.f; + float linearAttenuation = 0.5f / effect; + + if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation()) { - mGlowLight = new SceneUtil::LightSource; - mGlowLight->setLight(new osg::Light); - mGlowLight->setNodeMask(Mask_Lighting); - osg::Light* light = mGlowLight->getLight(); + if (mGlowLight) + { + mInsert->removeChild(mGlowLight); + mGlowLight = NULL; + } + + osg::ref_ptr light (new osg::Light); light->setDiffuse(osg::Vec4f(0,0,0,0)); light->setSpecular(osg::Vec4f(0,0,0,0)); light->setAmbient(osg::Vec4f(1.5f,1.5f,1.5f,1.f)); + light->setLinearAttenuation(linearAttenuation); + + mGlowLight = new SceneUtil::LightSource; + mGlowLight->setNodeMask(Mask_Lighting); mInsert->addChild(mGlowLight); + mGlowLight->setLight(light); } - effect += 3; - osg::Light* light = mGlowLight->getLight(); - mGlowLight->setRadius(effect * 66.f); - light->setLinearAttenuation(0.5f/effect); + mGlowLight->setRadius(radius); } } diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index ccfd836f7..d44a1a94a 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -118,7 +118,7 @@ namespace SceneUtil else if(mType == LT_PulseSlow) brightness = 0.7f + pulseAmplitude(mDeltaCount*slow)*0.3f; - static_cast(node)->getLight()->setDiffuse(mDiffuseColor * brightness); + static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * brightness); } void LightController::setDiffuse(osg::Vec4f color) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index ba2f8c510..6a992c503 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -98,7 +98,7 @@ namespace SceneUtil throw std::runtime_error("can't find parent LightManager"); } - mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath())); + mLightManager->addLight(static_cast(node), osg::computeLocalToWorld(nv->getNodePath()), nv->getTraversalNumber()); traverse(node, nv); } @@ -160,37 +160,42 @@ namespace SceneUtil mLightsInViewSpace.clear(); // do an occasional cleanup for orphaned lights - if (mStateSetCache.size() > 5000) - mStateSetCache.clear(); + for (int i=0; i<2; ++i) + { + if (mStateSetCache[i].size() > 5000) + mStateSetCache[i].clear(); + } } - void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat) + void LightManager::addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum) { LightSourceTransform l; l.mLightSource = lightSource; l.mWorldMatrix = worldMat; - lightSource->getLight()->setPosition(osg::Vec4f(worldMat.getTrans().x(), + lightSource->getLight(frameNum)->setPosition(osg::Vec4f(worldMat.getTrans().x(), worldMat.getTrans().y(), worldMat.getTrans().z(), 1.f)); mLights.push_back(l); } - osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList) + osg::ref_ptr LightManager::getLightListStateSet(const LightList &lightList, unsigned int frameNum) { // possible optimization: return a StateSet containing all requested lights plus some extra lights (if a suitable one exists) size_t hash = 0; for (unsigned int i=0; imLightSource->getId()); - LightStateSetMap::iterator found = mStateSetCache.find(hash); - if (found != mStateSetCache.end()) + LightStateSetMap& stateSetCache = mStateSetCache[frameNum%2]; + + LightStateSetMap::iterator found = stateSetCache.find(hash); + if (found != stateSetCache.end()) return found->second; else { std::vector > lights; for (unsigned int i=0; imLightSource->getLight()); + lights.push_back(lightList[i]->mLightSource->getLight(frameNum)); osg::ref_ptr attr = new LightStateAttribute(mStartLight, lights); @@ -200,7 +205,7 @@ namespace SceneUtil stateset->setAttribute(attr, osg::StateAttribute::ON); stateset->setAssociatedModes(attr, osg::StateAttribute::ON); - mStateSetCache.insert(std::make_pair(hash, stateset)); + stateSetCache.insert(std::make_pair(hash, stateset)); return stateset; } } @@ -348,10 +353,10 @@ namespace SceneUtil while (lightList.size() > maxLights) lightList.pop_back(); } - stateset = mLightManager->getLightListStateSet(lightList); + stateset = mLightManager->getLightListStateSet(lightList, nv->getTraversalNumber()); } else - stateset = mLightManager->getLightListStateSet(mLightList); + stateset = mLightManager->getLightListStateSet(mLightList, nv->getTraversalNumber()); cv->pushStateSet(stateset); diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 89ffc1305..78703dfca 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -12,7 +12,7 @@ namespace SceneUtil /// LightSource managed by a LightManager. class LightSource : public osg::Node { - osg::ref_ptr mLight; + osg::ref_ptr mLight[2]; // The activation radius float mRadius; @@ -37,17 +37,24 @@ namespace SceneUtil mRadius = radius; } - osg::Light* getLight() + /// Get the osg::Light safe for modification in the given frame. + osg::Light* getLight(unsigned int frame) { - return mLight; + return mLight[frame % 2]; } + /// @warning It is recommended not to replace an existing osg::Light, because there might still be + /// references to it in the light StateSet cache that are associated with this LightSource's ID. + /// These references will stay valid due to ref_ptr but will point to the old object. + /// @warning Do not modify the \a light after you've called this function. void setLight(osg::Light* light) { - mLight = light; + mLight[0] = light; + mLight[1] = osg::clone(light); } - int getId() + /// Get the unique ID for this light source. + int getId() const { return mId; } @@ -77,7 +84,7 @@ namespace SceneUtil void update(); // Called automatically by the LightSource's UpdateCallback - void addLight(LightSource* lightSource, const osg::Matrixf& worldMat); + void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, unsigned int frameNum); struct LightSourceTransform { @@ -97,7 +104,7 @@ namespace SceneUtil typedef std::vector LightList; - osg::ref_ptr getLightListStateSet(const LightList& lightList); + osg::ref_ptr getLightListStateSet(const LightList& lightList, unsigned int frameNum); /// Set the first light index that should be used by this manager, typically the number of directional lights in the scene. void setStartLight(int start); @@ -113,7 +120,7 @@ namespace SceneUtil // < Light list hash , StateSet > typedef std::map > LightStateSetMap; - LightStateSetMap mStateSetCache; + LightStateSetMap mStateSetCache[2]; int mStartLight;