天下武功,唯快不破
最近网友问了关于点云、倾斜摄影数据的性能优化问题。本来想刀枪剑戟、斧钺勾叉给弄了,但是后来想性能其实是个系统问题,要在第22节分成数小节扎扎实实的讲一讲。
鸣谢
非常感谢王锐王大神的cookbook,我准备主要参考它里面关于性能的一章。也就是第8章。本节讲述性能优化的最基本的手段:共享结点。
本节资源
本文集包括本节所有资源包括模型代码都在此下载,按节的序号有文件或文件夹:
注意:务必使用浏览器打开:
链接:https://pan.baidu.com/s/13gwJLwo_LbRnN3Bl2NXXXw
提取码:xrf5
本节程序如图:
问题描述
大家要添加一头牛,让它在场景中两个位置,大家都知道这么做
1、读取牛=node
2、申请MatrixTransform位置一 ,将其加牛node为子结点
3、设置MatrixTransform位置二,将其加牛node为子结点
上面牛就是共享结点,几乎没有人会将牛读取两次,分别加入到两个MatrixTranform当中。既然大家都知道,那么本文还有什么意义呢?
本文意义有二:
1、让大家看看究竟这样能改善多少。
2、正常的大家为了重复读牛,都会自己维护一个东西,今天为大家提供了一个独立于结点之外的新手段:在读取结点的时候判断当前结点是否已经被读取过了。
实现功能
1、按住CTRL,然后鼠标左键点击某船,则船消失,可以用来观察内存变化情况。这个小功能王锐大师还封了个小结构,大家也可以学习学习。
2、注释掉156 行osgDB::Registry::instance()->setReadFileCallback(sharer.get());,则所有的模型都是硬读取,不管是否已经读取了。否则在读取时会在ReadAndShareCallback类中判断此模型是否已经被读取过。下图左侧是使用共享结点,下图右侧是每次都读。可见内存占用差异很大。内存有两个,一个是private bytes是虚拟地址中的内存,一个是working set(工作内存)是占用的物理内存。
实现思路
1、class ReadAndShareCallback继承自public osgDB::ReadFileCallback可以保证每次readnodefile都会调用该callback,在callback中维护了一个map: ypedef std::map<std::string, osg::ref_ptr<osg::Node> > NodeMap;,这个map是名字与Node对应,也就是靠名字查找这个结点读取了没有,听起来不太靠谱,会意即可。读结点的时候先根据名字去这个map里查一下,有了就不读了:
//先去map里查查有没有这个node
osg::Node* node = getNodeByName(filename);
if (!node)
{
osgDB::ReaderWriter::ReadResult rr =
osgDB::Registry::instance()->readNodeImplementation(filename, options);
if (rr.success()) _nodeMap[filename] = rr.getNode();
return rr;
}
else
std::cout << "[SHARING] The name " << filename << " is already added to the sharing list." << std::endl;
return node;
2、这里面我们是从files.txt中读取结点的,其内容如下:
主要定义了模型的名称和位置,可以看到我们加了8个一模一样的模型在不同的位置。读取files.txt到场景中的代码如下:
void addFileList(osg::Group* root, const std::string& file)
{
std::ifstream is(file.c_str());
if (!is) return;
while (!is.eof())
{
osg::Vec3 pos;
std::string name;
is >> name >> pos[0] >> pos[1] >> pos[2];
osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform;
trans->addChild(osgDB::readNodeFile(name));
trans->setMatrix(osg::Matrix::translate(pos));
trans->setName(c_removeMark);
root->addChild(trans.get());
}
}
其它 现在我们要在场景重复添加大量相同物体,只是在不同的位置,使用的往往是多实例的方法。后面也会讲到,多实例也是觖性能问题的利器。
以上的功能在OSG内部也有为了避免重复读取结点的机制,叫做ObjectCache,如下使用:
osg::ref_ptr<osgDB::Options> options = new osgDB::Options;
options->setObjectCacheHint( osgDB::Options::CACHE_NODES );
osg::Node* model = osgDB::readNodeFile( "cow.osg", options.get() );
读取图片时,可以使用CACHE_IMAGES和osgDB::readImageFile()。
以下是本节所有代码:
/* -*-c++-*- OpenSceneGraph Cookbook
* Chapter 8 Recipe 3
* Author: Wang Rui <wangray84 at gmail dot com>
*/
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>
#include <fstream>
#include <iostream>
const std::string c_removeMark("Removable");
//读取文件的callback,设置该callback之后,所有的读取node的动作都会先走到这里
//我们设置这个callback的目的是要看看当前是否已经读取了该结点,它将所有读取的结点都
//存在了一个map中,每5秒看一下这个结点是不是还有人引用,没有人引用则删除掉这个结点
class ReadAndShareCallback : public osgDB::ReadFileCallback
{
public:
virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& filename, const osgDB::Options* options)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_shareMutex);
osg::Node* node = getNodeByName(filename);
if (!node)
{
osgDB::ReaderWriter::ReadResult rr =
osgDB::Registry::instance()->readNodeImplementation(filename, options);
if (rr.success()) _nodeMap[filename] = rr.getNode();
return rr;
}
else
std::cout << "[SHARING] The name " << filename << " is already added to the sharing list." << std::endl;
return node;
}
void prune(int second)
{
if (!(second % 5)) // Prune the scene every 5 seconds
return;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_shareMutex);
for (NodeMap::iterator itr = _nodeMap.begin(); itr != _nodeMap.end(); )
{
if (itr->second.valid())
{
if (itr->second->referenceCount() <= 1)
{
std::cout << "[REMOVING] The name " << itr->first << " is removed from the sharing list." << std::endl;
itr->second = NULL;
}
}
++itr;
}
}
protected:
osg::Node* getNodeByName(const std::string& filename)
{
NodeMap::iterator itr = _nodeMap.find(filename);
if (itr != _nodeMap.end()) return itr->second.get();
return NULL;
}
typedef std::map<std::string, osg::ref_ptr<osg::Node> > NodeMap;
NodeMap _nodeMap;
OpenThreads::Mutex _shareMutex;
};
//做一个点击事件的基类,当点住CTRL的情况下,左键单击模型,则会对这个模型调用doUserOperations
class PickHandler : public osgGA::GUIEventHandler
{
public:
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
if (ea.getEventType() != osgGA::GUIEventAdapter::RELEASE ||
ea.getButton() != osgGA::GUIEventAdapter::LEFT_MOUSE_BUTTON ||
!(ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_CTRL))
return false;
osgViewer::View* viewer = dynamic_cast<osgViewer::View*>(&aa);
if (viewer)
{
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector =
new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, ea.getX(), ea.getY());
osgUtil::IntersectionVisitor iv(intersector.get());
viewer->getCamera()->accept(iv);
if (intersector->containsIntersections())
{
osgUtil::LineSegmentIntersector::Intersection result = *(intersector->getIntersections().begin());
doUserOperations(result);
}
}
return false;
}
virtual void doUserOperations(osgUtil::LineSegmentIntersector::Intersection& result) = 0;
};
//继承点击基类,当点中模型之后看它是否有父亲,有父亲的话就会从父亲中把它删除掉
class RemoveModelHandler : public PickHandler
{
public:
RemoveModelHandler(ReadAndShareCallback* cb) : _callback(cb) {}
virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
if (ea.getEventType() == osgGA::GUIEventAdapter::FRAME)
{
//每一帧都会调用一下pruce,将已经删除掉的孤点从读取结点的map中删除
if (_callback.valid())
_callback->prune((int)ea.getTime());
}
return PickHandler::handle(ea, aa);
}
virtual void doUserOperations(osgUtil::LineSegmentIntersector::Intersection& result)
{
for (osg::NodePath::iterator itr = result.nodePath.begin();
itr != result.nodePath.end(); ++itr)
{
if ((*itr)->getName() == c_removeMark)
{
osg::Group* parent = ((*itr)->getNumParents() > 0 ? (*itr)->getParent(0) : NULL);
if (parent) parent->removeChild((*itr));
break;
}
}
}
osg::observer_ptr<ReadAndShareCallback> _callback;
};
void addFileList(osg::Group* root, const std::string& file)
{
std::ifstream is(file.c_str());
if (!is) return;
while (!is.eof())
{
osg::Vec3 pos;
std::string name;
is >> name >> pos[0] >> pos[1] >> pos[2];
osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform;
trans->addChild(osgDB::readNodeFile(name));
trans->setMatrix(osg::Matrix::translate(pos));
trans->setName(c_removeMark);
root->addChild(trans.get());
}
}
int main(int argc, char** argv)
{
osg::ref_ptr<ReadAndShareCallback> sharer = new ReadAndShareCallback;
osgDB::Registry::instance()->setReadFileCallback(sharer.get());
osg::ref_ptr<osg::Group> root = new osg::Group;
addFileList(root.get(), "files.txt");
osgViewer::Viewer viewer;
viewer.setSceneData(root.get());
viewer.addEventHandler(new RemoveModelHandler(sharer.get()));
viewer.addEventHandler(new osgViewer::StatsHandler);
return viewer.run();
}