第22.3节 性能篇-共享结点

天下武功,唯快不破

最近网友问了关于点云、倾斜摄影数据的性能优化问题。本来想刀枪剑戟、斧钺勾叉给弄了,但是后来想性能其实是个系统问题,要在第22节分成数小节扎扎实实的讲一讲。

鸣谢

非常感谢王锐王大神的cookbook,我准备主要参考它里面关于性能的一章。也就是第8章。本节讲述性能优化的最基本的手段:共享结点。

本节资源

本文集包括本节所有资源包括模型代码都在此下载,按节的序号有文件或文件夹:

注意: 务必使用浏览器打开:
【击此打开网盘资源链接】

本节程序如图:
image.png

问题描述

大家要添加一头牛,让它在场景中两个位置,大家都知道这么做
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(工作内存)是占用的物理内存。

image.png

实现思路

1、class ReadAndShareCallback继承自public osgDB::ReadFileCallback可以保证每次readnodefile都会调用该callback,在callback中维护了一个map: ypedef std::map<std::string, osg::ref_ptrosg::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中读取结点的,其内容如下:
image.png

主要定义了模型的名称和位置,可以看到我们加了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();
}

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片