forked from mirror/openmw-tes3mp
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