mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 01:45:38 +00:00
Move double buffering implementation inside RigGeometry
The double buffering is an implementation detail so it should be handled as such, rather than mandating the scene graph to be structured in a certain way. Override accept(NodeVisitor&) instead of using callbacks.
This commit is contained in:
parent
132ac6001b
commit
209e139aa8
7 changed files with 97 additions and 156 deletions
|
@ -1236,7 +1236,6 @@ namespace NifOsg
|
|||
|
||||
SceneUtil::RigGeometry::BoneInfluence influence;
|
||||
const std::vector<Nif::NiSkinData::VertWeight> &weights = data->bones[i].weights;
|
||||
//influence.mWeights.reserve(weights.size());
|
||||
for(size_t j = 0;j < weights.size();j++)
|
||||
{
|
||||
std::pair<unsigned short, float> indexWeight = std::make_pair(weights[j].vertex, weights[j].weight);
|
||||
|
@ -1249,17 +1248,7 @@ namespace NifOsg
|
|||
}
|
||||
rig->setInfluenceMap(map);
|
||||
|
||||
// Add a copy, we will alternate between the two copies every other frame using the FrameSwitch
|
||||
// This is so we can set the DataVariance as STATIC, giving a huge performance boost
|
||||
rig->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
osg::ref_ptr<FrameSwitch> frameswitch = new FrameSwitch;
|
||||
|
||||
SceneUtil::RigGeometry* rig2 = osg::clone(rig.get(), osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES);
|
||||
frameswitch->addChild(rig);
|
||||
frameswitch->addChild(rig2);
|
||||
|
||||
parentNode->addChild(frameswitch);
|
||||
parentNode->addChild(rig);
|
||||
}
|
||||
|
||||
osg::BlendFunc::BlendFuncMode getBlendMode(int mode)
|
||||
|
|
|
@ -43,13 +43,13 @@ namespace SceneUtil
|
|||
traverse(node);
|
||||
}
|
||||
|
||||
virtual void apply(osg::Geometry& geom)
|
||||
virtual void apply(osg::Drawable& drawable)
|
||||
{
|
||||
std::string lowerName = Misc::StringUtils::lowerCase(geom.getName());
|
||||
std::string lowerName = Misc::StringUtils::lowerCase(drawable.getName());
|
||||
if ((lowerName.size() >= mFilter.size() && lowerName.compare(0, mFilter.size(), mFilter) == 0)
|
||||
|| (lowerName.size() >= mFilter2.size() && lowerName.compare(0, mFilter2.size(), mFilter2) == 0))
|
||||
{
|
||||
osg::Node* node = &geom;
|
||||
osg::Node* node = &drawable;
|
||||
while (node && node->getNumParents() && !node->getStateSet())
|
||||
node = node->getParent(0);
|
||||
if (node)
|
||||
|
|
|
@ -10,73 +10,17 @@
|
|||
namespace SceneUtil
|
||||
{
|
||||
|
||||
class UpdateRigBounds : public osg::Drawable::UpdateCallback
|
||||
{
|
||||
public:
|
||||
UpdateRigBounds()
|
||||
{
|
||||
}
|
||||
|
||||
UpdateRigBounds(const UpdateRigBounds& copy, const osg::CopyOp& copyop)
|
||||
: osg::Drawable::UpdateCallback(copy, copyop)
|
||||
{
|
||||
}
|
||||
|
||||
META_Object(SceneUtil, UpdateRigBounds)
|
||||
|
||||
void update(osg::NodeVisitor* nv, osg::Drawable* drw)
|
||||
{
|
||||
RigGeometry* rig = static_cast<RigGeometry*>(drw);
|
||||
|
||||
rig->updateBounds(nv);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: make threadsafe for multiple cull threads
|
||||
class UpdateRigGeometry : public osg::Drawable::CullCallback
|
||||
{
|
||||
public:
|
||||
UpdateRigGeometry()
|
||||
{
|
||||
}
|
||||
|
||||
UpdateRigGeometry(const UpdateRigGeometry& copy, const osg::CopyOp& copyop)
|
||||
: osg::Drawable::CullCallback(copy, copyop)
|
||||
{
|
||||
}
|
||||
|
||||
META_Object(SceneUtil, UpdateRigGeometry)
|
||||
|
||||
virtual bool cull(osg::NodeVisitor* nv, osg::Drawable* drw, osg::State*) const
|
||||
{
|
||||
RigGeometry* geom = static_cast<RigGeometry*>(drw);
|
||||
geom->update(nv);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// We can't compute the bounds without a NodeVisitor, since we need the current geomToSkelMatrix.
|
||||
// So we return nothing. Bounds are updated every frame in the UpdateCallback.
|
||||
class DummyComputeBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback
|
||||
{
|
||||
public:
|
||||
virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); }
|
||||
};
|
||||
|
||||
RigGeometry::RigGeometry()
|
||||
: mSkeleton(NULL)
|
||||
, mLastFrameNumber(0)
|
||||
, mBoundsFirstFrame(true)
|
||||
{
|
||||
setCullCallback(new UpdateRigGeometry);
|
||||
setUpdateCallback(new UpdateRigBounds);
|
||||
setSupportsDisplayList(false);
|
||||
setUseVertexBufferObjects(true);
|
||||
setComputeBoundingBoxCallback(new DummyComputeBoundCallback);
|
||||
setUpdateCallback(new osg::Callback); // dummy to make sure getNumChildrenRequiringUpdateTraversal() is correct
|
||||
// update done in accept(NodeVisitor&)
|
||||
}
|
||||
|
||||
RigGeometry::RigGeometry(const RigGeometry ©, const osg::CopyOp ©op)
|
||||
: osg::Geometry(copy, copyop)
|
||||
: Drawable(copy, copyop)
|
||||
, mSkeleton(NULL)
|
||||
, mInfluenceMap(copy.mInfluenceMap)
|
||||
, mLastFrameNumber(0)
|
||||
|
@ -89,57 +33,47 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
|
|||
{
|
||||
mSourceGeometry = sourceGeometry;
|
||||
|
||||
osg::Geometry& from = *sourceGeometry;
|
||||
|
||||
if (from.getStateSet())
|
||||
setStateSet(from.getStateSet());
|
||||
|
||||
// shallow copy primitive sets & vertex attributes that we will not modify
|
||||
setPrimitiveSetList(from.getPrimitiveSetList());
|
||||
setColorArray(from.getColorArray());
|
||||
setSecondaryColorArray(from.getSecondaryColorArray());
|
||||
setFogCoordArray(from.getFogCoordArray());
|
||||
|
||||
// need to copy over texcoord list manually due to a missing null pointer check in setTexCoordArrayList(), this has been fixed in OSG 3.5
|
||||
osg::Geometry::ArrayList& texCoordList = from.getTexCoordArrayList();
|
||||
for (unsigned int i=0; i<texCoordList.size(); ++i)
|
||||
if (texCoordList[i])
|
||||
setTexCoordArray(i, texCoordList[i], osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
setVertexAttribArrayList(from.getVertexAttribArrayList());
|
||||
|
||||
// vertices and normals are modified every frame, so we need to deep copy them.
|
||||
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
||||
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
|
||||
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
|
||||
|
||||
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
|
||||
if (vertexArray)
|
||||
for (unsigned int i=0; i<2; ++i)
|
||||
{
|
||||
vertexArray->setVertexBufferObject(vbo);
|
||||
setVertexArray(vertexArray);
|
||||
}
|
||||
osg::Geometry& from = *sourceGeometry;
|
||||
mGeometry[i] = new osg::Geometry(from, osg::CopyOp::SHALLOW_COPY);
|
||||
osg::Geometry& to = *mGeometry[i];
|
||||
to.setSupportsDisplayList(false);
|
||||
to.setUseVertexBufferObjects(true);
|
||||
to.setCullingActive(false); // make sure to disable culling since that's handled by this class
|
||||
|
||||
if (osg::Array* normals = from.getNormalArray())
|
||||
{
|
||||
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
|
||||
if (normalArray)
|
||||
// vertices and normals are modified every frame, so we need to deep copy them.
|
||||
// assign a dedicated VBO to make sure that modifications don't interfere with source geometry's VBO.
|
||||
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
|
||||
vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
|
||||
|
||||
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL);
|
||||
if (vertexArray)
|
||||
{
|
||||
normalArray->setVertexBufferObject(vbo);
|
||||
setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
|
||||
vertexArray->setVertexBufferObject(vbo);
|
||||
to.setVertexArray(vertexArray);
|
||||
}
|
||||
}
|
||||
|
||||
if (osg::Array* normals = from.getNormalArray())
|
||||
{
|
||||
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL);
|
||||
if (normalArray)
|
||||
{
|
||||
normalArray->setVertexBufferObject(vbo);
|
||||
to.setNormalArray(normalArray, osg::Array::BIND_PER_VERTEX);
|
||||
}
|
||||
}
|
||||
|
||||
if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7)))
|
||||
{
|
||||
mSourceTangents = tangents;
|
||||
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
|
||||
tangentArray->setVertexBufferObject(vbo);
|
||||
setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
|
||||
if (osg::Vec4Array* tangents = dynamic_cast<osg::Vec4Array*>(from.getTexCoordArray(7)))
|
||||
{
|
||||
mSourceTangents = tangents;
|
||||
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL);
|
||||
tangentArray->setVertexBufferObject(vbo);
|
||||
to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
|
||||
}
|
||||
else
|
||||
mSourceTangents = NULL;
|
||||
}
|
||||
else
|
||||
mSourceTangents = NULL;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry()
|
||||
|
@ -228,7 +162,7 @@ void accumulateMatrix(const osg::Matrixf& invBindMatrix, const osg::Matrixf& mat
|
|||
ptrresult[14] += ptr[14] * weight;
|
||||
}
|
||||
|
||||
void RigGeometry::update(osg::NodeVisitor* nv)
|
||||
void RigGeometry::cull(osg::NodeVisitor* nv)
|
||||
{
|
||||
if (!mSkeleton)
|
||||
{
|
||||
|
@ -238,23 +172,24 @@ void RigGeometry::update(osg::NodeVisitor* nv)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!mSkeleton->getActive() && mLastFrameNumber != 0)
|
||||
return;
|
||||
|
||||
if (mLastFrameNumber == nv->getTraversalNumber())
|
||||
if ((!mSkeleton->getActive() && mLastFrameNumber != 0) || mLastFrameNumber == nv->getTraversalNumber())
|
||||
{
|
||||
nv->apply(*getGeometry(mLastFrameNumber));
|
||||
return;
|
||||
}
|
||||
mLastFrameNumber = nv->getTraversalNumber();
|
||||
osg::Geometry& geom = *getGeometry(mLastFrameNumber);
|
||||
|
||||
mSkeleton->updateBoneMatrices(nv->getTraversalNumber());
|
||||
|
||||
// skinning
|
||||
osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||
osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
|
||||
osg::Vec4Array* tangentSrc = mSourceTangents;
|
||||
const osg::Vec3Array* positionSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getVertexArray());
|
||||
const osg::Vec3Array* normalSrc = static_cast<osg::Vec3Array*>(mSourceGeometry->getNormalArray());
|
||||
const osg::Vec4Array* tangentSrc = mSourceTangents;
|
||||
|
||||
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(getVertexArray());
|
||||
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(getNormalArray());
|
||||
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(getTexCoordArray(7));
|
||||
osg::Vec3Array* positionDst = static_cast<osg::Vec3Array*>(geom.getVertexArray());
|
||||
osg::Vec3Array* normalDst = static_cast<osg::Vec3Array*>(geom.getNormalArray());
|
||||
osg::Vec4Array* tangentDst = static_cast<osg::Vec4Array*>(geom.getTexCoordArray(7));
|
||||
|
||||
for (Bone2VertexMap::const_iterator it = mBone2VertexMap.begin(); it != mBone2VertexMap.end(); ++it)
|
||||
{
|
||||
|
@ -294,6 +229,10 @@ void RigGeometry::update(osg::NodeVisitor* nv)
|
|||
normalDst->dirty();
|
||||
if (tangentDst)
|
||||
tangentDst->dirty();
|
||||
|
||||
nv->pushOntoNodePath(&geom);
|
||||
nv->apply(geom);
|
||||
nv->popFromNodePath();
|
||||
}
|
||||
|
||||
void RigGeometry::updateBounds(osg::NodeVisitor *nv)
|
||||
|
@ -365,5 +304,29 @@ void RigGeometry::setInfluenceMap(osg::ref_ptr<InfluenceMap> influenceMap)
|
|||
mInfluenceMap = influenceMap;
|
||||
}
|
||||
|
||||
void RigGeometry::accept(osg::NodeVisitor &nv)
|
||||
{
|
||||
if (!nv.validNodeMask(*this))
|
||||
return;
|
||||
|
||||
nv.pushOntoNodePath(this);
|
||||
|
||||
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
|
||||
cull(&nv);
|
||||
else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
|
||||
updateBounds(&nv);
|
||||
else if (nv.getVisitorType() == osg::NodeVisitor::INTERSECTION_VISITOR)
|
||||
nv.apply(*getGeometry(mLastFrameNumber));
|
||||
else
|
||||
nv.apply(*this);
|
||||
|
||||
nv.popFromNodePath();
|
||||
}
|
||||
|
||||
osg::Geometry* RigGeometry::getGeometry(unsigned int frame) const
|
||||
{
|
||||
return mGeometry[frame%2].get();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -13,10 +13,9 @@ namespace SceneUtil
|
|||
/// @brief Mesh skinning implementation.
|
||||
/// @note A RigGeometry may be attached directly to a Skeleton, or somewhere below a Skeleton.
|
||||
/// Note though that the RigGeometry ignores any transforms below the Skeleton, so the attachment point is not that important.
|
||||
/// @note To avoid race conditions, the rig geometry needs to be double buffered. This can be done
|
||||
/// using a FrameSwitch node that has two RigGeometry children. In the future we may want to consider implementing
|
||||
/// the double buffering inside RigGeometry.
|
||||
class RigGeometry : public osg::Geometry
|
||||
/// @note The internal Geometry used for rendering is double buffered, this allows updates to be done in a thread safe way while
|
||||
/// not compromising rendering performance. This is crucial when using osg's default threading model of DrawThreadPerContext.
|
||||
class RigGeometry : public osg::Drawable
|
||||
{
|
||||
public:
|
||||
RigGeometry();
|
||||
|
@ -24,6 +23,9 @@ namespace SceneUtil
|
|||
|
||||
META_Object(SceneUtil, RigGeometry)
|
||||
|
||||
// At this point compileGLObjects() remains unimplemented, hard to avoid race conditions
|
||||
// and there is limited value in compiling anyway since the data will change again for the next frame
|
||||
|
||||
struct BoneInfluence
|
||||
{
|
||||
osg::Matrixf mInvBindMatrix;
|
||||
|
@ -45,13 +47,15 @@ namespace SceneUtil
|
|||
|
||||
osg::ref_ptr<osg::Geometry> getSourceGeometry();
|
||||
|
||||
// Called automatically by our CullCallback
|
||||
void update(osg::NodeVisitor* nv);
|
||||
|
||||
// Called automatically by our UpdateCallback
|
||||
void updateBounds(osg::NodeVisitor* nv);
|
||||
virtual void accept(osg::NodeVisitor &nv);
|
||||
|
||||
private:
|
||||
void cull(osg::NodeVisitor* nv);
|
||||
void updateBounds(osg::NodeVisitor* nv);
|
||||
|
||||
osg::ref_ptr<osg::Geometry> mGeometry[2];
|
||||
osg::Geometry* getGeometry(unsigned int frame) const;
|
||||
|
||||
osg::ref_ptr<osg::Geometry> mSourceGeometry;
|
||||
osg::ref_ptr<osg::Vec4Array> mSourceTangents;
|
||||
Skeleton* mSkeleton;
|
||||
|
|
|
@ -50,7 +50,7 @@ class RigGeometrySerializer : public osgDB::ObjectWrapper
|
|||
{
|
||||
public:
|
||||
RigGeometrySerializer()
|
||||
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable osg::Geometry SceneUtil::RigGeometry")
|
||||
: osgDB::ObjectWrapper(createInstanceFunc<SceneUtil::RigGeometry>, "SceneUtil::RigGeometry", "osg::Object osg::Node osg::Drawable SceneUtil::RigGeometry")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
|
|
@ -38,8 +38,6 @@ Skeleton::Skeleton()
|
|||
, mNeedToUpdateBoneMatrices(true)
|
||||
, mActive(true)
|
||||
, mLastFrameNumber(0)
|
||||
, mTraversedEvenFrame(false)
|
||||
, mTraversedOddFrame(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -50,8 +48,6 @@ Skeleton::Skeleton(const Skeleton ©, const osg::CopyOp ©op)
|
|||
, mNeedToUpdateBoneMatrices(true)
|
||||
, mActive(copy.mActive)
|
||||
, mLastFrameNumber(0)
|
||||
, mTraversedEvenFrame(false)
|
||||
, mTraversedOddFrame(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -115,11 +111,6 @@ void Skeleton::updateBoneMatrices(unsigned int traversalNumber)
|
|||
|
||||
mLastFrameNumber = traversalNumber;
|
||||
|
||||
if (mLastFrameNumber % 2 == 0)
|
||||
mTraversedEvenFrame = true;
|
||||
else
|
||||
mTraversedOddFrame = true;
|
||||
|
||||
if (mNeedToUpdateBoneMatrices)
|
||||
{
|
||||
if (mRootBone.get())
|
||||
|
@ -144,18 +135,14 @@ bool Skeleton::getActive() const
|
|||
|
||||
void Skeleton::markDirty()
|
||||
{
|
||||
mTraversedEvenFrame = false;
|
||||
mTraversedOddFrame = false;
|
||||
mLastFrameNumber = 0;
|
||||
mBoneCache.clear();
|
||||
mBoneCacheInit = false;
|
||||
}
|
||||
|
||||
void Skeleton::traverse(osg::NodeVisitor& nv)
|
||||
{
|
||||
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR
|
||||
// need to process at least 2 frames before shutting off update, since we need to have both frame-alternating RigGeometries initialized
|
||||
// this would be more naturally handled if the double-buffering was implemented in RigGeometry itself rather than in a FrameSwitch decorator node
|
||||
&& mLastFrameNumber != 0 && mTraversedEvenFrame && mTraversedOddFrame)
|
||||
if (!getActive() && nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR && mLastFrameNumber != 0)
|
||||
return;
|
||||
osg::Group::traverse(nv);
|
||||
}
|
||||
|
|
|
@ -74,8 +74,6 @@ namespace SceneUtil
|
|||
bool mActive;
|
||||
|
||||
unsigned int mLastFrameNumber;
|
||||
bool mTraversedEvenFrame;
|
||||
bool mTraversedOddFrame;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue