osgViewer:: Viewer::advance() osg多线程与智能指针

void ViewerBase::frame(double simulationTime)
{
    if (_done) return;

    // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl;

    if (_firstFrame)
    {
        viewerInit();

        if (!isRealized())
        {
            realize();
        }

        _firstFrame = false;
    }
    advance(simulationTime);

    eventTraversal();
    updateTraversal();
    renderingTraversals();
}

  当前位置:osgViewer/Viewer.cpp 第 643行,osgViewer:: Viewer::advance()

  好的,现在我们终于正式进入仿真循环当中了,之前的 realize 函数虽然十分重要,但它实际上是循环运行前的准备工作。而从这一日开始介绍的 advance,eventTraversal,updateTraversal 和 renderingTraversals 函数,才是真正的一帧的组成部分。

advance 函数的工作内容如下:
  1、获取上一次记录的参考时间(Reference Time);
  2、根据当前时刻,重新记录参考时间,并因此得到两次记录之间的差值,即一帧经历的时间;
  3、记录已经经过的帧数;
  4、有的时候我们需要将帧速率,参考时间等内容予以记录并显示给用户,此时需要通过 ViewerBase::getStats 函数获得 osg::Stats 对象,用以进行帧状态的保存和显示;
  5、如果需要的话,使用 Referenced::getDeleteHandler()来处理 osg::Referenced 对象被弃用之后的删除工作。

  仿真循环运行的参考时间,总时间和总帧数都是由 osg::FrameStamp 变量_frameStamp来处理的,如果用户程序需要获取这些信息的话,也可以通过读取这个变量的成员函数来实现。当然,使用 Viewer 中的 osg::Stats 变量_stats 也是可以的,缺省情况下,这个变量会忠实地记录当前帧以及之前的 24 帧的每帧用时,事件遍历用时,更新遍历用时,以及渲染遍历用时信息。如果我们想获得更多的历史数据,抑或对于频繁的记录操作感到厌烦,可以在开始仿真循环之前执行 ViewerBase::setStats 函数,重新设置这个记录器的参数,或者简单地将其置为 NULL。

void Viewer::advance(double simulationTime)
{
    if (_done) return;

    double previousReferenceTime = _frameStamp->getReferenceTime();
    unsigned int previousFrameNumber = _frameStamp->getFrameNumber();

    _frameStamp->setFrameNumber(_frameStamp->getFrameNumber()+1);

    _frameStamp->setReferenceTime( osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick()) );

    if (simulationTime==USE_REFERENCE_TIME)
    {
        _frameStamp->setSimulationTime(_frameStamp->getReferenceTime());
    }
    else
    {
        _frameStamp->setSimulationTime(simulationTime);
    }

    if (getViewerStats() && getViewerStats()->collectStats("frame_rate"))
    {
        // update previous frame stats
        double deltaFrameTime = _frameStamp->getReferenceTime() - previousReferenceTime;
        getViewerStats()->setAttribute(previousFrameNumber, "Frame duration", deltaFrameTime);
        getViewerStats()->setAttribute(previousFrameNumber, "Frame rate", 1.0/deltaFrameTime);

        // update current frames stats
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Reference time", _frameStamp->getReferenceTime());
    }

   //简单说来,它的工作是收集所有已经弃用的 OSG 场景对象,并在需要的时候(例如advance 函数代码的相应部分)执行 osg::DeleteHandler::flush,将它们统一删除。
   //这里所说的“弃用”,与我们非常熟悉的 osg::ref_ptr 智能指针是密切相关的。我们已经知道,ref_ptr 采用内存引用计数的方式,当一个场景对象(通常是 Node 节点)链接到根节点或者其他节点时,它的引用计数加一,这一动作是通过 ref_ptr::ref()函数实现的;如果它被剔除出节点,那么它的引用计数减一,执行这一工作的函数是 ref_ptr::unref()。unref 函数的另一个重要任务是检查对象的引用计数值是否到达零,如果已经没有被其它对象所引用,那么称这个对象被“弃用”,它应当被立即删除,以释放相应的内存空间,避免泄露。
if (osg::Referenced::getDeleteHandler()) { osg::Referenced::getDeleteHandler()->flush(); osg::Referenced::getDeleteHandler()->setFrameNumber(_frameStamp->getFrameNumber()); } }
  C++中最通用的删除对象的方法是 delete,OSG 的智能指针也是采用这种方式来释放对象的,不过由于OSG采用多线程更新/渲染的方式(这一点我们会在后面的日子中详细介绍),这样做可能带来会某些隐患,想象这样一种情况:
  1、场景某个的节点负责显示某种图形,它的工作一直很正常;
  2、我们采用 DrawThreadPerContext 或者 CullThreadPerCameraDrawThreadPerContext 线程模型,根据前一日中我们所知的,这两种模式中存在“上次的渲染工作与下次的更新工作交叠”这一情形。
  3、假设我们在更新工作中立即将这个节点删除,而上次渲染工作可能正要将这个节点中的数据送往 OpenGL 图形渲染管线,那么灾难就发生了……
  看到这里,读者您一定已经想到了一种解决方案。对,就是在渲染后台也使用 ref_ptr来引用(ref)图形节点,然后在渲染结束取消引用(unref),这样不就可以避免无谓的牺牲了吗?也省却用户的很多麻烦。
  说得有道理,不过这其中恐怕忽视了一个核心的问题:渲染效率。是的,假设我们要渲染成千上万个这样的几何体节点(这对您来说也许简直是家常便饭),如果每个节点的渲染都要多执行一次 ref/unref 的话,效率的损失将是无法被忽略的。事实上经过测算,CPU 时间的流失大概可以达到 6%,对于一个实时渲染系统来说,这的确值得斟酌。
  因此,OSG 的新版本中提出了 DeleteHandler 的概念,也就是“垃圾收集”,把那些引用计数已经为零的对象统一收集起来,确保它们不会再被渲染线程用到之后,再在适当的地方予以释放。DeleteHandler 有一个重要的参数_numFramesToRetainObjects,它的意义是,垃圾对象被收集之后,再经过多少帧(默认设置是 2),方予以释放。因此,OSG 的垃圾收集器同样需要使用 DeleteHandler::setFrameNumber 来记录当前的帧数。
  这个概念提出的时间并不长,也许还需要一段时间的测试,也许会有更好的方案来替代它。目前,OSG 的发行版本仍然采用第一种方式,也就是渲染后台采用 ref_ptr 引用计数的方式来避免删除对象造成的问题;如果您想要尝试使用和帮助调试 DeleteHandler 的话,可以在自己的程序中(main 函数之前)加入:
  #undef OSGUTIL_RENDERBACKEND_USE_REF_PTR
以请求使用 DeleteHandler。
DeleteHandler* Referenced::getDeleteHandler()
{
    return s_deleteHandler.get();
}

又一次回到初始函数

void ViewerBase::frame(double simulationTime)
{
    if (_done) return;

    // OSG_NOTICE<<std::endl<<"CompositeViewer::frame()"<<std::endl<<std::endl;

    if (_firstFrame)
    {
        viewerInit();

        if (!isRealized())
        {
            realize();
        }

        _firstFrame = false;
    }
    advance(simulationTime);

    eventTraversal();
    updateTraversal();
    renderingTraversals();
}

 

eventTraversal()这个函数有二百多行,在我们实验室的编码标准中是不允许的,这种情况下必须拆分成好几个函数,哈哈哈,言归正传,继续看王锐老师的解读

在开始了解 OSG 事件遍历的过程之前,也许有必要先总结一下 OSG 视景器、摄像机与场景的关系。如下图所示:
 
  视景器包括几个最主要的组件:漫游器_cameraManipulator,用于实现交互式的场景漫游;事件处理器组_eventHandlers,负责处理视景器的事件队列_eventQueue,主要是键盘/鼠标等事的处理;场景_scene,它包括视景器所对应的场景图形根节点,以及用于提高节点和图像数据处理速度的两个分页数据库;摄像机_camera 和_slaves,前者为场景的主摄像机,后者为从摄像机组,不过 OSG 并没有规定一定要使用主摄像机来显示场景,它的更重要的作用是为 OSG 世界矩阵的计算提供依据。
  摄像机是 OSG 视图显示的核心器件,没有摄像机就没有办法将场景图形的实景展现给用户。它包括:
  1、视口(Viewport),它指示了摄像机显示窗口的位置和尺寸。
  2、图形上下文(GraphicsContext),通常这也就是平台相关的图形显示窗口(即GraphicsWindow,对于 Win32 系统而言,它实际上是通过 CreateWindowEx 这个熟悉的 API来创建的),不过也可能是离屏渲染的设备(例如 PixelBufferWin32)。
  图形窗口的另一个任务是及时把系统和用户交互的事件反馈到事件处理器组中去,观察Win32 平台下的窗口设备GraphicsWindowWin32 中的handleNativeWindowingEvent函数和它的传入参数,hwnd,msg,lParam,wParam……没错,相信您已经找到熟悉的感觉了。OSG所处理的事件正是来源于 Win32 SDK 编程中常见的窗口消息。如果您正好有些新的想法想要实践(例如,捕捉 WM_HOTKEY 系统热键消息,并传递给 OSG 的事件队列),不妨试一试修改相关的内容,并记得把好的建议发送到 osg-users。
  3、渲染器(GraphicsOperation,更多时候是 osgViewer::Renderer),这是整个 OSG 筛选(CULL)和绘制(DRAW)的关键,它的功能我们会在后面的日子里慢慢展开。此外,OSG 的显示设置工具 DisplaySettings 也会直接对摄像机的处理工作负责,大部分设置选项都可以传递到摄像机对应的窗口特性(GraphicsContext::Traits)中,并在渲染过程中发挥作用。
 
void Viewer::eventTraversal()
{
    if (_done) return;

    double cutOffTime = _frameStamp->getReferenceTime();

    double beginEventTraversal = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());

    // OSG_NOTICE<<"Viewer::frameEventTraversal()."<<std::endl;

    // need to copy events from the GraphicsWindow's into local EventQueue;
    osgGA::EventQueue::Events events;

    Contexts contexts;
    getContexts(contexts);

    // set done if there are no windows
    checkWindowStatus(contexts);
    if (_done) return;

    osgGA::GUIEventAdapter* eventState = getEventQueue()->getCurrentEventState();

    // get events from user Devices attached to Viewer.
    for(Devices::iterator eitr = _eventSources.begin();
        eitr != _eventSources.end();
        ++eitr)
    {
        osgGA::Device* es = eitr->get();
        if (es->getCapabilities() & osgGA::Device::RECEIVE_EVENTS)
            es->checkEvents();

        // open question, will we need to reproject mouse coordinates into current view's coordinate frame as is down for GraphicsWindow provided events?
        // for now assume now and just get the events directly without any reprojection.
        es->getEventQueue()->takeEvents(events, cutOffTime);
    }

    // get events from all windows attached to Viewer.
    for(Contexts::iterator citr = contexts.begin();
        citr != contexts.end();
        ++citr)
    {
        osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(*citr);
        if (gw)
        {
            gw->checkEvents();

            osgGA::EventQueue::Events gw_events;
            gw->getEventQueue()->takeEvents(gw_events, cutOffTime);

            osgGA::EventQueue::Events::iterator itr;
            for(itr = gw_events.begin();
                itr != gw_events.end();
                ++itr)
            {
                osgGA::GUIEventAdapter* event = (*itr)->asGUIEventAdapter();
                if (!event) continue;

                event->setGraphicsContext(gw);

                switch(event->getEventType())
                {
                    case(osgGA::GUIEventAdapter::PUSH):
                    case(osgGA::GUIEventAdapter::RELEASE):
                    case(osgGA::GUIEventAdapter::DOUBLECLICK):
                    case(osgGA::GUIEventAdapter::MOVE):
                    case(osgGA::GUIEventAdapter::DRAG):
                    {
                        if (event->getEventType()!=osgGA::GUIEventAdapter::DRAG ||
                            eventState->getGraphicsContext()!=event->getGraphicsContext() ||
                            eventState->getNumPointerData()<2)
                        {
                            generatePointerData(*event);
                        }
                        else
                        {
                            reprojectPointerData(*eventState, *event);
                        }


                        eventState->copyPointerDataFrom(*event);

                        break;
                    }
                    default:
                        event->copyPointerDataFrom(*eventState);
                        break;
                }

                events.push_back(event);
            }

            for(itr = gw_events.begin();
                itr != gw_events.end();
                ++itr)
            {
                osgGA::GUIEventAdapter* event = (*itr)->asGUIEventAdapter();
                if (!event) continue;
                switch(event->getEventType())
                {
                    case(osgGA::GUIEventAdapter::CLOSE_WINDOW):
                    {
                        bool wasThreading = areThreadsRunning();
                        if (wasThreading) stopThreading();

                        gw->close();
                        _currentContext = NULL;

                        if (wasThreading) startThreading();

                        break;
                    }
                    default:
                        break;
                }
            }

        }
    }

    // create a frame event for the new frame.
    {
        osg::ref_ptr<osgGA::GUIEventAdapter> event = _eventQueue->frame( getFrameStamp()->getReferenceTime() );

        if (!eventState || eventState->getNumPointerData()<2)
        {
            generatePointerData(*event);
        }
        else
        {
            reprojectPointerData(*eventState, *event);
        }
    }

    // OSG_NOTICE<<"mouseEventState Xmin = "<<eventState->getXmin()<<" Ymin="<<eventState->getYmin()<<" xMax="<<eventState->getXmax()<<" Ymax="<<eventState->getYmax()<<std::endl;

    _eventQueue->takeEvents(events, cutOffTime);

    // OSG_NOTICE<<"Events "<<events.size()<<std::endl;

    if ((_keyEventSetsDone!=0) || _quitEventSetsDone)
    {
        for(osgGA::EventQueue::Events::iterator itr = events.begin();
            itr != events.end();
            ++itr)
        {
            osgGA::GUIEventAdapter* event = (*itr)->asGUIEventAdapter();
            if (!event) continue;

            // ignore event if it's already been handled.
            if (event->getHandled()) continue;

            switch(event->getEventType())
            {
                case(osgGA::GUIEventAdapter::KEYUP):
                    if (_keyEventSetsDone && event->getKey()==_keyEventSetsDone) _done = true;
                    break;

                case(osgGA::GUIEventAdapter::QUIT_APPLICATION):
                    if (_quitEventSetsDone) _done = true;
                    break;

                default:
                    break;
            }
        }
    }

    if (_done) return;

    if (_eventVisitor.valid() && getSceneData())
    {
        _eventVisitor->setFrameStamp(getFrameStamp());
        _eventVisitor->setTraversalNumber(getFrameStamp()->getFrameNumber());

        for(osgGA::EventQueue::Events::iterator itr = events.begin();
            itr != events.end();
            ++itr)
        {
            osgGA::GUIEventAdapter* event = (*itr)->asGUIEventAdapter();
            if (!event) continue;

            _eventVisitor->reset();
            _eventVisitor->addEvent( event );

            getSceneData()->accept(*_eventVisitor);

            // Do EventTraversal for slaves with their own subgraph
            for(unsigned int i=0; i<getNumSlaves(); ++i)
            {
                osg::View::Slave& slave = getSlave(i);
                osg::Camera* camera = slave._camera.get();
                if(camera && !slave._useMastersSceneData)
                {
                    camera->accept(*_eventVisitor);
                }
            }


            // call any camera event callbacks, but only traverse that callback, don't traverse its subgraph
            // leave that to the scene update traversal.
            osg::NodeVisitor::TraversalMode tm = _eventVisitor->getTraversalMode();
            _eventVisitor->setTraversalMode(osg::NodeVisitor::TRAVERSE_NONE);

            if (_camera.valid()) _camera->accept(*_eventVisitor);

            for(unsigned int i=0; i<getNumSlaves(); ++i)
            {
                osg::View::Slave& slave = getSlave(i);
                osg::Camera* camera = slave._camera.get();
                if (camera && slave._useMastersSceneData)
                {
                    camera->accept(*_eventVisitor);
                }
            }

            _eventVisitor->setTraversalMode(tm);

        }
    }


    for(osgGA::EventQueue::Events::iterator itr = events.begin();
        itr != events.end();
        ++itr)
    {
        osgGA::Event* event = itr->get();
        for(EventHandlers::iterator hitr = _eventHandlers.begin();
            hitr != _eventHandlers.end();
            ++hitr)
        {
            (*hitr)->handle( event, 0, _eventVisitor.get());
        }

    }

    for(osgGA::EventQueue::Events::iterator itr = events.begin();
        itr != events.end();
        ++itr)
    {
        osgGA::Event* event = itr->get();
        if (event && _cameraManipulator.valid())
        {
            _cameraManipulator->handle( event, 0, _eventVisitor.get());
        }
    }

    if (getViewerStats() && getViewerStats()->collectStats("event"))
    {
        double endEventTraversal = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());

        // update current frames stats
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Event traversal begin time", beginEventTraversal);
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Event traversal end time", endEventTraversal);
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Event traversal time taken", endEventTraversal-beginEventTraversal);
    }

}

 

posted @ 2019-07-02 16:03  西北逍遥  阅读(1293)  评论(0编辑  收藏  举报