最长的一帧学习 part1

文章目录

  • 场景图形-SceneGraph;场景子树-Subgraph;节点-Node;摄像机-Camera;渲染器-Renderer;窗口-Window;视口-Viewport;场景-Scene;视图-View;视景器-Viewer;漫游器-Manipulator;访问器-Visitor;回调-Callback;事件-Event;更新-Update;筛选-Cull;绘制-Draw
  • 原文链接:http://bbs.osgchina.org/forum.php?mod=viewthread&tid=509&highlight=OSG%D4%AD%B4%B4%BD%CC%B3%CC%A3%BA%D7%EE%B3%A4%B5%C4%D2%BB%D6%A1

一、osgViewer:: ViewerBase:: frame()

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();
}

该函数所执行的主要工作如下:

  • 如果这是仿真系统启动后的第一帧,则执行viewerInit();此时如果还没有执行realize()函数,则执行它。
  • 执行advance函数。
  • 执行eventTraversal函数,顾名思义,这个函数将负责处理系统产生的各种事件,诸如鼠标的移动,点击,键盘的响应,窗口的关闭等等,以及摄像机与场景图形的事件回调(EventCallback)。
  • 执行updateTraversal函数,这个函数负责遍历所有的更新回调(UpdateCallback);除此之外,它的另一个重要任务就是负责更新DatabasePager与ImagePager这两个重要的分页数据处理组件。
  • 执行renderingTraversals函数,这里将使用较为复杂的线程处理方法,完成场景的筛选(cull)和绘制(draw)工作。

1.osgViewer:: View:: init()

void View::init()
{
    OSG_INFO<<"View::init()"<<std::endl;

    osg::ref_ptr<osgGA::GUIEventAdapter> initEvent = _eventQueue->createEvent();
    initEvent->setEventType(osgGA::GUIEventAdapter::FRAME);

    if (_cameraManipulator.valid())
    {
        _cameraManipulator->init(*initEvent, *this);
    }
}
  • View::osg::ref_ptr<osgGA::EventQueue> _eventQueue;储存该视景器的事件队列(osgGA::GUIEventAdapter)
  • 其中createEvent是分配和返回一个新的GUIEventAdapter事件的指针。
  • View::osg::ref_ptr<osgGA::CameraManipulator> _cameraManipulator为该视景器的操作器的实例,如果希望编写自定义的场景操作器/漫游器,那么覆写并使用osgGA:: MatrixManipulator:: init就可以灵活地初始化自定义漫游器的功能了,它的调用时机就在这里。
  • 其中如果调用的是viewer.run(),没有设置操作器,osg会自动设置一个:
int Viewer::run()
{
    if (!getCameraManipulator() && getCamera()->getAllowEventFocus())
    {
        setCameraManipulator(new osgGA::TrackballManipulator());
    }

    setReleaseContextAtEndOfFrameHint(false);

    return ViewerBase::run();
}

int ViewerBase::run()
{
    if (!isRealized())
    {
        realize();
    }

    unsigned int runTillFrameNumber = osg::UNINITIALIZED_FRAME_NUMBER;
    osg::getEnvVar("OSG_RUN_FRAME_COUNT", runTillFrameNumber);

    while(!done() && (runTillFrameNumber==osg::UNINITIALIZED_FRAME_NUMBER || getViewerFrameStamp()->getFrameNumber()<runTillFrameNumber))
    {
        double minFrameTime = _runMaxFrameRate>0.0 ? 1.0/_runMaxFrameRate : 0.0;
        osg::Timer_t startFrameTick = osg::Timer::instance()->tick();
        if (_runFrameScheme==ON_DEMAND)
        {
            if (checkNeedToDoFrame())
            {
                frame();
            }
            else
            {
                // we don't need to render a frame but we don't want to spin the run loop so make sure the minimum
                // loop time is 1/100th of second, if not otherwise set, so enabling the frame microSleep below to
                // avoid consume excessive CPU resources.
                if (minFrameTime==0.0) minFrameTime=0.01;
            }
        }
        else
        {
            frame();
        }

        // work out if we need to force a sleep to hold back the frame rate
        osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
        double frameTime = osg::Timer::instance()->delta_s(startFrameTick, endFrameTick);
        if (frameTime < minFrameTime) OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(minFrameTime-frameTime)));
    }

    return 0;
}

2.osgViewer::Viewer::realize(),窗口和场景的“设置”工作

part1 GraphicsContext

void Viewer::realize(){
Contexts contexts;
getContexts(contexts);}
void Viewer::getContexts(Contexts& contexts, bool onlyValid)
{
    typedef std::set<osg::GraphicsContext*> ContextSet;
    ContextSet contextSet;

    contexts.clear();

    if (_camera.valid() &&
        _camera->getGraphicsContext() &&
        (_camera->getGraphicsContext()->valid() || !onlyValid))
    {
        contextSet.insert(_camera->getGraphicsContext());
        contexts.push_back(_camera->getGraphicsContext());
    }

    for(unsigned int i=0; i<getNumSlaves(); ++i)
    {
        Slave& slave = getSlave(i);
        osg::GraphicsContext* sgc = slave._camera.valid() ? slave._camera->getGraphicsContext() : 0;
        if (sgc && (sgc->valid() || !onlyValid))
        {
            if (contextSet.count(sgc)==0)
            {
                contextSet.insert(sgc);
                contexts.push_back(sgc);
            }
        }
    }
}
  • ViewBase::std::vectorosg::GraphicsContext* Contexts
  • contexts是一个保存了osg::GraphicsContext指针的向量组
  • Viewer::getContexts函数的作用是获取所有的图形上下文,并保存到这个向量组中来。
    首先判断场景的主摄像机_camera是否包含了一个有效的GraphicsContext设备,然后再遍历所有的从摄像机_slaves(一个视景器可以包含一个主摄像级和多个从摄像机),将所有找到的GraphicsContext图形上下文设备记录下来。(其中osg::GraphicsContext*互斥不重复!)
  • 当程序还没有进入仿真循环,且对于osgViewer::Viewer还没有任何的操作之时,系统是不会存在任何图形上下文的;创建一个新的osg::Camera对象也不会为其自动分配图形上下文。但是,图形上下文GraphicsContext却是场景显示的唯一平台,系统有必要在开始渲染之前完成其创建工作。
    假设用户已经在进入仿真循环之前,自行创建了新的Camera摄像机对象,为其分配了自定义的GraphicsContext设备,并将Camera对象传递给视景器,就像osgviewerMFC和osgcamera例子,以及我们在编写与GUI系统嵌合的仿真程序时常做的那样。此时,系统已经不必为图形上下文的创建作任何多余的工作,因为用户不需要更多的窗口来显示自己的场景了。所以就算主摄像机_camera还没有分配GraphicsContext,只要系统中已经存在图形上下文,即可以开始执行仿真程序了
    所以在osgcamera例子中,哪怕有已经有一个主相机(没有gc),但是addsalve了6个带有gc的子相机,也是能正常显示的。
    在这里插入图片描述
    在这里插入图片描述
part1.1 通过阅读osgViewer::View::setUpViewInWindow()了解osg最基础的操作

详见这里!!!
GraphicsContext的创建由平台相关的抽象接口类WindowingSystemInterface负责,对于Win32平台而言,这个类是由GraphicsWindowWin32.cpp的Win32WindowingSystem类具体实现的,它创建的显示窗口设备即osgViewer::GraphicsWindowWin32的实例。

当我们尝试使用createGraphicsContext来创建一个图形设备上下文时,系统返回的实际上是这个函数的值:

ref_ptr<GraphicsContext::WindowingSystemInterface> &wsref = windowingSystemInterfaceRef();
return wsref->createGraphicsContext(traits);

part2 DisplaySettings

void Viewer::realize()
{
。。。
	 // get the display settings that will be active for this viewer
    osg::DisplaySettings* ds = _displaySettings.valid() ? _displaySettings.get() : osg::DisplaySettings::instance().get();
    
    // 获取窗口系统API接口,即GraphicsContext::WindowingSystemInterface的实例
    //  static osg::ref_ptr<WindowingSystemInterfaces>& getWindowingSystemInterfaces()
    osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface();

    // pass on the display settings to the WindowSystemInterface.
    if (wsi && wsi->getDisplaySettings()==0) wsi->setDisplaySettings(ds);

    unsigned int maxTexturePoolSize = ds->getMaxTexturePoolSize();
    unsigned int maxBufferObjectPoolSize = ds->getMaxBufferObjectPoolSize();
}

osg::DisplaySettings类使用单例模式来声明所有这些元素的窗口的唯一实例(一个程序中只能有一个DisplaySettings::instance()对象)。所以我们可以在程序中的任意获取显示设置实例:
DisplaySettings的作用仅仅是保存所有可能在系统显示中用到的数据,这个类本身并不会据此改变任何系统设置和渲染方式
osg::DisplaySettings* ds = osg::DisplaySettings::instance();

  • 主要记录窗口的一些显示设置,例如渲染窗口的OpenGL图像环境。
  • DisplaySettings可以很方便地从系统环境变量或者命令行参数中获取用户对显示设备的设置,详细的调用方法可以参阅DisplaySettings::readEnvironmentalVariables和DisplaySettings::readCommandLine两个函数的内容。
  • 如果希望在用户程序中更改DisplaySettings中的显示设置,请务必在执行视景器的realize函数之前,当然也就是仿真循环开始之前
  • _displayType:显示器类型,默认为MONITOR(监视器),此外还支持POWERWALL(威力墙),REALITY_CENTER(虚拟实境中心)和HEAD_MOUNTED_DISPLAY(头盔显示器)。
  • _stereoMode:立体显示模式,默认为ANAGLYPHIC(互补色),此外还支持QUAD_BUFFER(四方体缓冲),HORIZONTAL_SPLIT(水平分割),VERTICAL_SPLIT(垂直分割),LEFT_EYE(左眼用),RIGHT_EYE(右眼用),HORIZONTAL_INTERLACE(水平交错),VERTICAL_INTERLACE(垂直交错),CHECKERBOARD(棋盘式交错,用于DLP显示器)。
  • _eyeSeparation:双眼的物理距离,默认为0.05。
  • _screenWidth,_screenHeight:屏幕的实际宽度和高度,分别默认设置为0.325和0.26,目前它们影响的仅仅是视图采用透视投影时的宽高比。
  • _screenDistance:人眼到屏幕的距离,默认为0.5。
  • _splitStereoHorizontalEyeMapping:默认为LEFT_EYE_LEFT_VIEWPORT(左眼渲染左视口),也可设为LEFT_EYE_RIGHT_VIEWPORT(左眼渲染右视口)。
  • _splitStereoHorizontalSeparation:左视口和右视口之间的距离(像素数),默认为0。
  • _splitStereoVerticalEyeMapping:默认为LEFT_EYE_TOP_VIEWPORT(左眼渲染顶视口),也可设为LEFT_EYE_BOTTOM_VIEWPORT(左眼渲染底视口)。
  • _splitStereoVerticalSeparation:顶视口和底视口之间的距离(像素数),默认为0。
  • _splitStereoAutoAdjustAspectRatio:默认为true,用于屏幕分割之后对其宽高比进行补偿。
  • _maxNumOfGraphicsContexts:用户程序中最多可用的GraphicsContext(图形设备上下文)数目,默认为32个。
  • _numMultiSamples:多重采样的子像素样本数,默认为0。如果显示卡支持的话,打开多重采样可以大幅改善反走样(anti-aliasing)的效果。
  • _minimumNumberStencilBits(模板缓存的最小位数)
  • 更多具体使用参考这里的Chapter7

part3 遍历所得的所有GraphicsContext设备

void Viewer::realize()
{
。。。
for(Contexts::iterator citr = contexts.begin();
        citr != contexts.end();
        ++citr)
    {
        osg::GraphicsContext* gc = *citr;

        if (ds->getSyncSwapBuffers()) gc->setSwapCallback(new osg::SyncSwapBuffersCallback);

        // set the pool sizes, 0 the default will result in no GL object pools.
        gc->getState()->setMaxTexturePoolSize(maxTexturePoolSize);
        gc->getState()->setMaxBufferObjectPoolSize(maxBufferObjectPoolSize);

        gc->realize();

        if (_realizeOperation.valid() && gc->valid())
        {
            gc->makeCurrent();

            (*_realizeOperation)(gc);

            gc->releaseContext();
        }
    }
}

在这里插入图片描述在这里插入图片描述

_realizeOperation,通过ViewerBase::setRealizeOperation来设置的,其主要作用是在执行realize函数时,顺便完成用户指定的一些工作。您自己的工作内容可以通过继承osg::Operation类,并重载operator()操作符来添加。osgcatch这个妙趣横生的例子(一个傻娃娃接玩具的小游戏)中就使用了setRealizeOperation,主要的作用是为场景中的Drawable几何对象立即编译显示列表(Display List)。

part4 把鼠标焦点转到当前窗口上

当前位置:osgViewer/Viewer.cpp第463行,osgViewer::Viewer::realize()
下面我们再次遍历所有GraphicsContext设备,对于每个GraphicsContext指针gc,判断它是否为GraphicsWindow对象,并执行GraphicsWindow::grabFocusIfPointerInWindow函数。阅读GraphicsWindowWin32类(即GraphicsContext的具体实现者)的同名函数可以发现,这个函数不过是负责把鼠标焦点转到当前窗口上而已。

void Viewer::realize()
{
。。。
// attach contexts to _incrementalCompileOperation if attached.
    if (_incrementalCompileOperation) _incrementalCompileOperation->assignContexts(contexts);

    bool grabFocus = true;
    if (grabFocus)
    {
        for(Contexts::iterator citr = contexts.begin();
            citr != contexts.end();
            ++citr)
        {
            osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(*citr);
            if (gw)
            {
                gw->grabFocusIfPointerInWindow();
            }
        }
    }
}

part5 启动OSG内部定时器并开始计时,设置线程,Compile Contexts

void Viewer::realize()
{
。。。
// initialize the global timer to be relative to the current time.
    osg::Timer::instance()->setStartTick();

    // pass on the start tick to all the associated event queues
    setStartTick(osg::Timer::instance()->getStartTick());

    // configure threading.
    setUpThreading();

    if (osg::DisplaySettings::instance()->getCompileContextsHint())
    {
        for(unsigned int i=0; i<= osg::GraphicsContext::getMaxContextID(); ++i)
        {
            osg::GraphicsContext* gc = osg::GraphicsContext::getOrCreateCompileContext(i);

            if (gc)
            {
                gc->createGraphicsThread();
                gc->getGraphicsThread()->startThread();
            }
        }
    }
}
  • Viewer::setStartTick函数的工作是找到当前视景器和所有GraphicsContext设备的事件队列_eventQueue,并设定它们的启动时刻为当前时间。
  • ViewerBase::setUpThreading函数……设置线程:
    在这里插入图片描述
  • OSG的视景器包括四种线程模型,可以使用viewer.setThreadingModel进行设置,更多参考这里
    在这里插入图片描述
  • 启用编译上下文Compile Contexts,只需要在调用realize之前执行:osg::DisplaySettings::instance()->setCompileContextsHint(true);
    在这里插入图片描述

3.osgViewer::Viewer::advance()

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);
    }

	//有的时候我们需要将帧速率,参考时间等内容予以记录并显示给用户,
	//此时需要通过ViewerBase::getStats函数获得osg::Stats对象,用以进行帧状态的保存和显示
    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());
    }

//如果需要的话,使用Referenced::getDeleteHandler()来处理osg::Referenced对象被弃用之后的删除工作
    if (osg::Referenced::getDeleteHandler())
    {
        osg::Referenced::getDeleteHandler()->flush();
        osg::Referenced::getDeleteHandler()->setFrameNumber(_frameStamp->getFrameNumber());//默认两帧后删
    }

}

getDeleteHandler

  • 收集所有已经弃用的OSG场景对象,并在需要的时候(例如advance函数代码的相应部分)执行osg:: DeleteHandler::flush,将它们统一删除。
    在这里插入图片描述

4.osgViewer::Viewer::eventTraversal()

OSG视景器、摄像机与场景的关系

在这里插入图片描述

视景器包括几个最主要的组件:

  • 漫游器_cameraManipulator,用于实现交互式的场景漫游;
  • 事件处理器组_eventHandlers,负责处理视景器的事件队列_eventQueue,主要是键盘/鼠标等事件的处理;
  • 场景_scene,它包括视景器所对应的场景图形根节点,以及用于提高节点和图像数据处理速度的两个分页数据库;
  • 摄像机_camera和_slaves,前者为场景的主摄像机,后者为从摄像机组,不过OSG并没有规定一定要使用主摄像机来显示场景,它的更重要的作用是为OSG世界矩阵的计算提供依据。

摄像机是OSG视图显示的核心器件,没有摄像机就没有办法将场景图形的实景展现给用户。它包括:

  • 视口(Viewport),它指示了摄像机显示窗口的位置和尺寸。
  • 图形上下文(GraphicsContext),通常这也就是平台相关的图形显示窗口(即GraphicsWindow,对于Win32系统而言,它实际上是通过CreateWindowEx这个熟悉的API来创建的),不过也可能是离屏渲染的设备(例如PixelBufferWin32)。
    图形窗口的另一个任务是及时把系统和用户交互的事件反馈到事件处理器组中去,观察Win32平台下的窗口设备GraphicsWindowWin32中的handleNativeWindowingEvent函数和它的传入参数,hwnd,msg,lParam,wParam……没错,相信您已经找到熟悉的感觉了。OSG所处理的事件正是来源于Win32 SDK编程中常见的窗口消息。
  • 渲染器(GraphicsOperation,更多时候是osgViewer::Renderer),这是整个OSG筛选(CULL)和绘制(DRAW)的关键,它的功能我们会在后面的日子里慢慢展开。
    此外,OSG的显示设置工具DisplaySettings也会直接对摄像机的处理工作负责,大部分设置选项都可以传递到摄像机对应的窗口特性(GraphicsContext::Traits)中,并在渲染过程中发挥作用。

事件处理

eventTraversal除了可以执行用户设置的事件回调(EventCallback)之外,更重要的工作是为所有的用户交互和系统事件提供一个响应的机制:

  • 它必须在每一帧的仿真过程中,取出已经发生的所有事件,摒弃那些对场景不会有助益的(例如,在视口以外发生的鼠标移动事件和胡乱点击),依次交付给各个事件处理器,最后清空现有的事件队列,等待下一帧的到来.

视景器中保存有一个事件处理器组_eventHandlers(osgGA::GUIEventHandler),它负责处理由图形窗口设备(gc)传递到事件队列_eventQueue的各种事件。

  • View::osg::ref_ptrosgGA::EventQueue _eventQueue;储存该视景器的事件队列(osgGA::GUIEventAdapter).
  • 新的事件处理器可以通过View::addEventHandler添加,除了RecordCameraPathHandler,StatsHandler等常见的处理器工具之外,通过继承事件处理器的基类osgGA::GUIEventHandler,并重写handle函数,我们还可以实现自定义的交互事件响应流程,这在osgviewer,osgkeyboardmouse等例子中均有详细的演示。

part1 记录这个函数的起始时刻

相应的,在最后部分会记录函数的结束时刻,并将函数的总共运行时间送入_stats记录器。这有助于我们了解每一帧当中事件遍历,更新遍历和渲染遍历运行所占用的时间比例。

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;
。。。
 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);
    }
}

part2 取得事件队列的状态事件(公用)(EventQueue::getCurrentEventState)

事件之间可能共通的参数和状态。

举例来说,鼠标移动的事件之后,再触发键盘按键的事件(这种操作在《反恐精英》等游戏中司空见惯),则前者将负责更新“状态事件”中的鼠标X,Y坐标参数;而后者就从中取得此坐标,并再次更新“状态事件”中按键相关的信息。
因此,当我们在处理有关按键的GUIEventAdapter事件时,同样也可以使用成员函数GUIEventAdapter::getX和getY取得当前的鼠标位置,而不必担心键盘事件与鼠标的操作无关。

void Viewer::eventTraversal()
{
。。。
// 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
    // gc其实就是由WindowingSystemInterface创建
    checkWindowStatus(contexts);
    if (_done) return;

	//事件之间可能共通的参数和状态
    osgGA::GUIEventAdapter* eventState = getEventQueue()->getCurrentEventState();
}

part3 取得主摄像机的视口范围,设置事件队列的“响应范围”,计算主摄像机的VPW矩阵(EventQueue::setInputRange)

checkEvents的作用:
使用Viewer::getContexts函数找到视景器中所有已有的GraphicsWindow图形窗口,然后执行GraphicsWindowWin32::checkEvents函数,看看这个函数的内容(TranslateMessage,DispatchMessage等消息传递函数),用于通知Windows执行窗口的消息回调函数,进而执行用户交互和系统消息的检查函数GraphicsWindowWin32::handleNativeWindowingEvent,它负责把WM_*消息转化并传递给osgGA::EventQueue消息队列。
在这里插入图片描述

  • 如果主摄像机的视口范围存在的话,正如我们在前面所论述的,主摄像机并不一定存在Viewport视口,也不一定存在GraphicsContext图形设备)
  • EventQueue::setInputRange函数的主要工作是设置鼠标活动的最大和最小范围,如果同时还开启了鼠标范围的限定标志(EventQueue::setUseFixedMouseInputRange),那么鼠标移动的范围将自动限制在这个范围之内(不过此选项默认是关闭的).
  • 新版类似功能在generatePointerData中。
  • addSlave的主要功能是设置随着相机运动的从相机,不能取得窗口信息。
  • setCamera会设置一个完全自由且关联了渲染窗口的相机,一个view一个这种相机。
  • takeEvents函数除了将所有的事件取出之外,还负责清空事件队列

stopThreading:

  • 当我们选择关闭一个GraphicsWindow窗口gw时,OSG系统必须首先尝试终止所有的渲染线程(包括各个图形设备开启的线程和渲染器开启的线程,参见realize()),关闭窗口之后再打开所有的渲染线程。
  • 事实上,当我们试图在运行时开启一个新的OSG图形窗口时,也必须使用相同的线程控制步骤,即,关闭线程,创建新渲染窗口,开启线程。否则很可能造成系统的崩溃(这同样涉及到OSG的多线程机制)。
void Viewer::eventTraversal()
{
。。。
 // 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);
        }
    }
    。。。
}

1、首先处理Y轴的方向问题,通常的GUI窗口系统都会将屏幕左上角定义为(0, 0),右下角定义为(Xmax, Ymax),即GUIEventAdapter::Y_INCREASING_DOWNWARDS宏的含义,此时由GUI系统传回到OSG的坐标位置也是符合这一坐标系的;但是OSG的视口坐标系定义为左下角(0, 0),右上角(Xmax, Ymax),也就是Y_INCREASING_DOWNWARDS。此时,有必要对每个event对象的鼠标坐标值做一步转换。
2、对于符合下列条件的摄像机,设置为“焦点摄像机”(Camera with Focus),即_cameraWithFocus:
(1)鼠标移动(而非拖动)时,鼠标进入了摄像机的视口范围之内;
(2)摄像机是对应该视口的,允许接收事件(Camera::getAllowEventFocus),且“渲染目标实现方式”(Render Target Implementation)为Camera::FRAME_BUFFER。
3、new_coord实际上是焦点摄像机中的(x, y)经过转换之后,在主摄像机视口中的坐标位置,而这个位置被重新写入所有事件的setX,setY函数。也就是说,当我们在重写后的GUIEventHandler::handle函数中使用ea.getX和ea.getY获取鼠标位置时,这个位置很可能已经经过了一次换算,并非实际的像素位置。

void Viewer::generatePointerData(osgGA::GUIEventAdapter& event)
{
    osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(event.getGraphicsContext());
    if (!gw) return;

    float x = event.getX();
    float y = event.getY();

    bool invert_y = event.getMouseYOrientation()==osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS;
    if (invert_y && gw->getTraits()) y = gw->getTraits()->height - 1 - y;

    event.addPointerData(new osgGA::PointerData(gw, x, 0, gw->getTraits()->width - 1,
                                                    y, 0, gw->getTraits()->height - 1));

    event.setMouseYOrientationAndUpdateCoords(osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS);

    typedef std::vector<osg::Camera*> CameraVector;
    CameraVector activeCameras;

    osgViewer::View* this_view = dynamic_cast<osgViewer::View*>(this);
    osg::GraphicsContext::Cameras& cameras = gw->getCameras();
    for(osg::GraphicsContext::Cameras::iterator citr = cameras.begin();
        citr != cameras.end();
        ++citr)
    {
        osg::Camera* camera = *citr;
        if (camera->getView()==this_view &&
            camera->getAllowEventFocus() &&
            camera->getRenderTargetImplementation()==osg::Camera::FRAME_BUFFER)
        {
            osg::Viewport* viewport = camera->getViewport();
            if (viewport &&
                x >= viewport->x() && y >= viewport->y() &&
                x < (viewport->x()+viewport->width()) && y < (viewport->y()+viewport->height()) )
            {
                activeCameras.push_back(camera);
            }
        }
    }

    std::sort(activeCameras.begin(), activeCameras.end(), osg::CameraRenderOrderSortOp());

    osg::Camera* camera = activeCameras.empty() ? 0 : activeCameras.back();

    if (camera)
    {
        osg::Viewport* viewport = camera->getViewport();

        event.addPointerData(new osgGA::PointerData(camera, (x-viewport->x())/(viewport->width() - 1)*2.0f-1.0f, -1.0, 1.0,
                                                            (y-viewport->y())/(viewport->height() - 1)*2.0f-1.0f, -1.0, 1.0));

        // if camera isn't the master it must be a slave and could need reprojecting.
        if (camera!=getCamera())
        {
            generateSlavePointerData(camera, event);
        }
    }
}
void Viewer::generateSlavePointerData(osg::Camera* camera, osgGA::GUIEventAdapter& event)
{
    osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(event.getGraphicsContext());
    if (!gw) return;

    // What type of Camera is it?
    // 1) Master Camera : do nothing extra
    // 2) Slave Camera, Relative RF, Same scene graph as master : transform coords into Master Camera and add to PointerData list
    // 3) Slave Camera, Relative RF, Different scene graph from master : do nothing extra?
    // 4) Slave Camera, Absolute RF, Same scene graph as master : do nothing extra?
    // 5) Slave Camera, Absolute RF, Different scene graph : do nothing extra?
    // 6) Slave Camera, Absolute RF, Different scene graph but a distortion correction subgraph depending upon RTT Camera (slave or master)
    //                              : project ray into RTT Camera's clip space, and RTT Camera's is Relative RF and sharing same scene graph as master then transform coords.

    // if camera isn't the master it must be a slave and could need reprojecting.
    if (camera!=getCamera())
    {
        float x = event.getX();
        float y = event.getY();

        bool invert_y = event.getMouseYOrientation()==osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS;
        if (invert_y && gw->getTraits()) y = gw->getTraits()->height - 1 - y;

        double master_min_x = -1.0;
        double master_max_x = 1.0;
        double master_min_y = -1.0;
        double master_max_y = 1.0;

        osg::Matrix masterCameraVPW = getCamera()->getViewMatrix() * getCamera()->getProjectionMatrix();
        if (getCamera()->getViewport())
        {
            osg::Viewport* viewport = getCamera()->getViewport();
            master_min_x = viewport->x();
            master_min_y = viewport->y();
            master_max_x = viewport->x() + viewport->width() - 1;
            master_max_y = viewport->y() + viewport->height() - 1;
            masterCameraVPW *= viewport->computeWindowMatrix();
        }

        // slave Camera if it shares the same View
        osg::View::Slave* slave = findSlaveForCamera(camera);
        if (slave)
        {
            if (camera->getReferenceFrame()==osg::Camera::RELATIVE_RF && slave->_useMastersSceneData)
            {
                osg::Viewport* viewport = camera->getViewport();
                osg::Matrix localCameraVPW = camera->getViewMatrix() * camera->getProjectionMatrix();
                if (viewport)
                {
                    localCameraVPW *= viewport->computeWindowMatrix();
                }

                osg::Matrix matrix( osg::Matrix::inverse(localCameraVPW) * masterCameraVPW );
                osg::Vec3d new_coord = osg::Vec3d(x,y,0.0) * matrix;
                event.addPointerData(new osgGA::PointerData(getCamera(), new_coord.x(), master_min_x, master_max_x,
                                                                         new_coord.y(), master_min_y, master_max_y));
            }
            else if (!slave->_useMastersSceneData)
            {
                // Are their any RTT Camera's that this Camera depends upon for textures?

                osg::ref_ptr<osgUtil::RayIntersector> ray = new osgUtil::RayIntersector(osgUtil::Intersector::WINDOW, x,y);
                osgUtil::IntersectionVisitor iv(ray.get());
                camera->accept(iv);
                if (ray->containsIntersections())
                {
                    osg::Vec3 tc;
                    osg::Texture* texture = ray->getFirstIntersection().getTextureLookUp(tc);
                    if (texture)
                    {
                        // look up Texture in RTT Camera's.
                        for(unsigned int i=0; i<getNumSlaves();++i)
                        {
                            osg::Camera* slave_camera = getSlave(i)._camera.get();
                            if (slave_camera)
                            {
                                osg::Camera::BufferAttachmentMap::const_iterator ba_itr = slave_camera->getBufferAttachmentMap().find(osg::Camera::COLOR_BUFFER);
                                if (ba_itr != slave_camera->getBufferAttachmentMap().end())
                                {
                                    if (ba_itr->second._texture == texture)
                                    {
                                        osg::TextureRectangle* tr = dynamic_cast<osg::TextureRectangle*>(ba_itr->second._texture.get());
                                        osg::TextureCubeMap* tcm = dynamic_cast<osg::TextureCubeMap*>(ba_itr->second._texture.get());
                                        if (tr)
                                        {
                                            event.addPointerData(new osgGA::PointerData(slave_camera, tc.x(), 0.0f, static_cast<float>(tr->getTextureWidth()),
                                                                                                      tc.y(), 0.0f, static_cast<float>(tr->getTextureHeight())));
                                        }
                                        else if (tcm)
                                        {
                                            OSG_INFO<<"  Slave has matched texture cubemap"<<ba_itr->second._texture.get()<<", "<<ba_itr->second._face<<std::endl;
                                        }
                                        else
                                        {
                                            event.addPointerData(new osgGA::PointerData(slave_camera, tc.x(), 0.0f, 1.0f,
                                                                                                      tc.y(), 0.0f, 1.0f));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

part4 处理OSG的退出事件

  • ViewerBase::setQuitEventSetsDone设置是否允许按下某个键之后直接退出这种做法。
  • ViewerBase::setKeyEventSetsDone来设置自定义的退出键(缺省是GUIEventAdapter::KEY_Escape)。
void Viewer::eventTraversal()
{
。。。
    // 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;
。。。
}

part5 遍历所有得到的事件GUIEventAdapter,传递给每一个注册的GUIEventHandler事件处理器

在OSG中事件回调(EventCallback)和更新回调(UpdateCallback)最为常见,对于场景图形中的任何节点,以及Geode叶节点所包含的任何Drawable几何体,我们都可以使用setEventCallback和setUpdateCallback函数设置它的事件回调与更新回调对象。这些回调对象的调用时机就在eventTraversal函数中。

  • 为了正确地遍历场景的节点和几何体对象,并执行所有可能的事件回调和更新回调,OSG使用访问器(Visitor)机制来处理场景图形的访问工作。这其中,_eventVisitor就是负责管理事件回调的遍历工作的,updateTraversal中访问器_updateVisitor负责更新遍历。这个变量的值会自动初始化,当然我们也可以用ViewerBase::setEventVisitor加载我们自定义的事件访问器,前提是这个访问器要继承自osgGA::EventVisitor类。
  • 在事件回调的处理函数中(operator()或者event),我们可以通过读取第二个传入参数,并调用EventVisitor::getEvents函数来获取当前发生的事件。
  • 场景节点的回调对象必须继承自osg::NodeCallback,并重写NodeCallback:: operator()函数以实现回调的具体内容。
  • Drawable对象的事件回调必须继承自Drawable::EventCallback,并具现EventCallback::event函数的内容;其更新回调则必须继承Drawable::UpdateCallback并具现UpdateCallback::update函数。
    typedef DrawableUpdateCallback UpdateCallback;
    typedef DrawableEventCallback EventCallback;
    typedef DrawableCullCallback CullCallback;
class OSGGA_EXPORT EventVisitor : public osg::NodeVisitor
{
virtual void apply(osg::Drawable& drawable)
        {
            osg::Callback* callback = drawable.getEventCallback();
            if (callback)
            {
                osgGA::EventHandler* eh = callback->asEventHandler();
                if (eh)
                {
                    callback->run(&drawable,this);
                }
                else
                {
                    osg::DrawableEventCallback* drawable_callback = callback->asDrawableEventCallback();
                    osg::NodeCallback* node_callback = callback->asNodeCallback();
                    osg::CallbackObject* callback_object = callback->asCallbackObject();

                    if (drawable_callback) drawable_callback->event(this,&drawable);
                    if (node_callback) (*node_callback)(&drawable, this);
                    if (callback_object)  callback_object->run(&drawable, this);

                    if (!drawable_callback && !node_callback && !callback_object) callback->run(&drawable, this);
                }
            }

            handle_callbacks(drawable.getStateSet());
        }
}
void Viewer::eventTraversal()
{
。。。
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);
                }
            }

//在遍历场景节点并执行其事件回调之后,OSG还要转至主摄像机_camera和从摄像机组_slaves,
//再次执行它们的事件回调对象,,但是设置访问器不要向下遍历节点(因为Camera同样可以作为场景的一个中间节点)
//,在访问过所有摄像机之后再恢复访问器的原有值。
            // 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);

        }
    }

// 注册的GUIEventHandler
	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());
        }
    }

。。。
}

GUIEventHandler::handle函数,在用户自定义的事件处理器中是必须被重写的。

  • 当handle函数返回值为true时,表示此事件已经处理完毕,其它事件处理器不会再理会这个事件;返回false则继续交由后继的GUIEventHandler对象处理。
  • GUIEventHandler::setIgnoreHandledEventsMask,传入GUIEventAdapter::EventType类型的参数(或者通过“或”运算传入多个EventType类型),就可以禁止处理器处理此类事件了
    在这里插入图片描述
  • 在事件处理器组_eventHandlers处理过事件之后,场景漫游器_cameraManipulator将再次考察同一事件,TrackballManipulator等漫游器就是在这个时候执行场景的平移、旋转和缩放动作的。因此,对于鼠标/键盘事件,请不要轻易设置handle函数的返回值为true,因为那样将屏蔽漫游器对这些事件的处理。

5.osgViewer::Viewer::updateTraversal()

updateTraversal除了处理用户的更新回调对象之外,还要负责更新摄像机的位置,并且更新分页数据库DatabasePager和图像库ImagePager的内容。

  • 有专门的访问器_updateVisitor的负责场景图形更新遍历;所有的节点和Drawable几何体对象都可以使用setUpdateCallback设置更新回调;通过具现NodeCallback:: operator()或者Drawable::UpdateCallback::update函数,可以在回调对象中添加自定义的工作。
  • 更新回调与事件回调最大的不同在于:每当一个用户交互或系统事件产生时,每一个节点(以及Drawable对象)的事件回调都会被调用一次;而节点(以及Drawable对象)的更新回调只会在每帧中被调用一次。

part1 记录这个函数的起始时刻

void Viewer::updateTraversal()
{
    if (_done) return;

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

    _updateVisitor->reset();
    _updateVisitor->setFrameStamp(getFrameStamp());
    _updateVisitor->setTraversalNumber(getFrameStamp()->getFrameNumber());
   。。。
     if (getViewerStats() && getViewerStats()->collectStats("update"))
    {
        double endUpdateTraversal = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());

        // update current frames stats
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Update traversal begin time", beginUpdateTraversal);
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Update traversal end time", endUpdateTraversal);
        getViewerStats()->setAttribute(_frameStamp->getFrameNumber(), "Update traversal time taken", endUpdateTraversal-beginUpdateTraversal);
    }
}

part2 使用预设的更新访问器_updateVisitor,更新分页数据库的内容

  • updateSceneGraph函数的工作是更新分页数据库的内容,它的内容简单到只包含了两个执行函数的内容:
    1、DatabasePager::removeExpiredSubgraphs:用于去除已经过期的场景子树;
    2、DatabasePager::addLoadedDataToSceneGraph:用于向场景图形中添加新载入的数据。
  • 在解读DatabasePager之前,我们或许需要一点预备知识:也就是OpenSceneGraph中的线程处理库OpenThreads:
    参考这里
void Viewer::updateTraversal()
{
   。。。
    _scene->updateSceneGraph(*_updateVisitor);

	// 未知
	// if we have a shared state manager prune any unused entries
    if (osgDB::Registry::instance()->getSharedStateManager())
        osgDB::Registry::instance()->getSharedStateManager()->prune();

    // update the Registry object cache.
    osgDB::Registry::instance()->updateTimeStampOfObjectsInCacheWithExternalReferences(*getFrameStamp());
    osgDB::Registry::instance()->removeExpiredObjectsInCache(*getFrameStamp());
   。。。
}
  • 访问场景图形的根节点并遍历其子节点,实现各个节点和Drawable对象的更新回调
  • 使用DatabasePager::updateSceneGraph函数以及ImagePager::updateSceneGraph函数,分别更新场景的分页数据库和分页图像库:
    即每一帧的更新遍历执行到updateSceneGraph函数时,都会自动将“一段时间之内始终不在当前页面上”的场景子树去除,并将“新载入到当前页面”的场景子树加入渲染,这里所说的“页面”往往指的就是用户的视野范围。这些分页和节点管理的工作如果由渲染循环来完成的话,恐怕是费时又费力的,对于场景的显示速度有较大的影响,因此,DatabasePager中内置了专用于相关工作处理的DatabaseThread线程。
void Scene::updateSceneGraph(osg::NodeVisitor& updateVisitor)
{
    if (!_sceneData) return;

    if (getDatabasePager())
    {
        // synchronize changes required by the DatabasePager thread to the scene graph
        getDatabasePager()->updateSceneGraph((*updateVisitor.getFrameStamp()));
    }

    if (getImagePager())
    {
        // synchronize changes required by the DatabasePager thread to the scene graph
        getImagePager()->updateSceneGraph(*(updateVisitor.getFrameStamp()));
    }

    if (getSceneData())
    {
        updateVisitor.setImageRequestHandler(getImagePager());
        getSceneData()->accept(updateVisitor);
    }
}
part2.1 DatabasePager中内置osgDB:: DatabasePager:: DatabaseThread::run()

OSG的分页数据库应该使用单独的线程来处理什么:
在这里插入图片描述

数据的检索与输入数据存储的内存空间存储数据的输出
长时间离开“当前页面”的对象->弃用对象列表_childrenToDeleteList ->释放对象以降低系统开销(线程)
新的对象,或进入“当前页面”的对象->数据请求列表_requestList->尝试加载或重新加载对象(线程)
加载后,需要提前编译的对象(线程)->等待编译列表_dataToCompileList->执行对象的预编译工作(线程)
编译完成,或者不需要提前编译的独享(线程)->等待合并列表_dataToMergeList->将对象合并到场景图形

DatabasePager类所定义的各种数据结构:

  • DatabasePager:: DatabaseThread类:这是分页数据库的核心处理线程,它负责实现场景元素的定期清理,加载以及合并工作;但是让它一直处于检查各个数据列表的循环状态,这未免太过耗费系统资源。因此,这个线程在平常状态下应当被阻塞,需要时再予以唤醒。
  • DatabasePager:: DatabaseRequest结构体:这个结构体保存了用户的单个数据请求,包括数据文件名,请求时间,数据加载后存入的节点,以及要进行合并的父节点等;除此之外还有一个重要的编译映射表_dataToCompileMap,这个映射表负责保存图形设备ID与编译对象(几何体显示列表,纹理等)的映射关系。
  • DatabasePager::RequestQueue结构体:它负责保存和管理一个“数据请求列表”_requestList,也就是由DatabaseRequest对象组成的向量组,除此之外还负责对列表中的数据按请求时间排序。上图中所示的_dataToCompileList和_dataToMergeList实际上都是RequestQueue类型的对象,不过它们所保存的“请求列表”事实上是已经完成加载的“待编译/待合并列表”了。
  • DatabasePager::ReadQueue结构体:这个结构体继承自RequestQueue,不过还增加了一个“弃用对象列表”_childrenToDeleteList,也就是osg::Object对象组成的向量组。它是数据处理线程中最重要的对象之一,除了可以随时向两个列表里追加数据请求和弃用对象之外,这个结构体还包括了一个updateBlock函数,负责阻塞或者放行DatabaseThread线程,其根据是:列表中是否存在新的数据请求或弃用对象需要处理,以及用户是否通过函数设置暂时不要启用线程(DatabasePager ::setDatabasePagerThreadPause)。
void DatabasePager::ReadQueue::updateBlock()
{
// DatabasePager* _pager;
    _block->set((!_requestList.empty() || !_childrenToDeleteList.empty()) &&
                !_pager->_databasePagerThreadPaused);
}
  • 每次循环开始时,DatabaseThread数据处理线程都被自动阻塞,避免无谓的系统消耗;直到updateBlock函数在外部被执行才会放行.
  • updateBlock函数可能在以下几种情形下被执行:
    1、ReadQueue对象中的“数据请求列表_requestList”被修改,例如新的数据加载请求被传入,请求被取出,列表被重置。
    2、ReadQueue对象中的“弃用对象列表_childrenToDeleteList ” 被修改,例如有新的过期对象被送入,对象被删除,列表被重置。
    3、执行了DatabasePager ::setDatabasePagerThreadPause函数,当线程被重新启动时,会自动检查线程是否应当被唤醒。
part2.2 osgDB:: DatabasePager::updateSceneGraph()
  • DatabasePager::removeExpiredSubgraphs:用于去除已经过期的场景子树。

我们首先遍历DatabasePager::_pagedLODList这个成员变量,并执行其中每个PagedLOD对象的removeExpiredChildren函数,取得其中已经过期的子节点并记录到一个列表里。将这些过期节点标记为“可删除”,并传递给_fileRequestQueue->_childrenToDeleteList成员,也就是前文所述的“待删除列表”,同时唤醒DatabaseThread线程

下一步,将过期节点从_pagedLODList中删除,由于它们已经被传递到“待删除列表”当中,因此ref_ptr引用计数不会减到零,也就不会在主仿真循环中触发内存释放(delete)动作。

最后还要执行SharedStateManager::prune函数。这里的osgDB::SharedStateManager指的是一个渲染状态共享管理器,它负责记录分页数据库中各个节点的渲染属性(StateAttribute),并判断节点之间是否共享了同一个渲染属性,从而节省加载和预编译的时间。prune函数的工作是从SharedStateManager中剔除那些没有被共享的渲染属性。
如果希望启用SharedStateManager(默认是关闭的,其性能目前可能没有想象的那么好),需要在进入仿真循环之前执行:osgDB::Registry::instance()->getOrCreateSharedStateManager();

  • DatabasePager::addLoadedDataToSceneGraph:用于向场景图形中添加新载入的数据。

这里首先取得“待合并列表”_dataToMergeList,并遍历其中每一个DatabaseRequest对象。
遍历过程中,首先执行SharedStateManager::share函数,将新加载节点_loadedModel的渲染属性保存到SharedStateManager管理器中。
随后执行DatabasePager::registerPagedLODs,在加载的节点及其子树中搜索PagedLOD节点,并添加到刚刚提到的_pagedLODList列表中。
最后,判断DatabaseRequest::_groupForAddingLoadedSubgraph对象(也就是新加载节点在场景中的父节点)是否合法,并将DatabaseRequest::_loadedModel添加为它的子节点。
使用osg:: PagedLOD和osg:: ProxyNode节点的时候我们才会用到DatabasePager.`

void DatabasePager::updateSceneGraph(const osg::FrameStamp& frameStamp)
{

#define UPDATE_TIMING 0
#if UPDATE_TIMING
    osg::ElapsedTime timer;
    double timeFor_removeExpiredSubgraphs, timeFor_addLoadedDataToSceneGraph;
#endif

    {
        removeExpiredSubgraphs(frameStamp);

#if UPDATE_TIMING
        timeFor_removeExpiredSubgraphs = timer.elapsedTime_m();
#endif

        addLoadedDataToSceneGraph(frameStamp);

#if UPDATE_TIMING
        timeFor_addLoadedDataToSceneGraph = timer.elapsedTime_m() - timeFor_removeExpiredSubgraphs;
#endif

    }

#if UPDATE_TIMING
    double elapsedTime = timer.elapsedTime_m();
    if (elapsedTime>0.4)
    {
        OSG_NOTICE<<"DatabasePager::updateSceneGraph() total time = "<<elapsedTime<<"ms"<<std::endl;
        OSG_NOTICE<<"   timeFor_removeExpiredSubgraphs    = "<<timeFor_removeExpiredSubgraphs<<"ms"<<std::endl;
        OSG_NOTICE<<"   timeFor_addLoadedDataToSceneGraph = "<<timeFor_addLoadedDataToSceneGraph<<"ms"<<std::endl;
        OSG_NOTICE<<"   _activePagedLODList.size()        = "<<_activePagedLODList->size()<<std::endl;
        OSG_NOTICE<<"   _inactivePagedLODList.size()      = "<<_inactivePagedLODList->size()<<std::endl;
        OSG_NOTICE<<"   total                             = "<<_activePagedLODList->size() + _inactivePagedLODList->size()<<std::endl;
    }
#endif
}

part3 处理用户定义的更新工作队列_updateOperations

在前文osgViewer::Viewer::realize() ,part3 遍历所得的所有GraphicsContext设备,执行_realizeOperation(gc),来完成自定义的场景预处理工作
更新工作的作用及实现方法与之相似,均需要继承osg::Operation并重写operator()操作符,以实现一些特定的用户操作。

  • ViewerBase::addUpdateOperation写入多组更新处理器Operation对象
  • removeUpdateOperation来移除
  • Operation对象可以用来处理与图形设备以及OpenGL底层API相关的工作,不过用户层级的工作在这里实现通常也没有问题
void Viewer::updateTraversal()
{
。。。
	 if (_updateOperations.valid())
    {
        _updateOperations->runOperations(this);
    }
     if (_incrementalCompileOperation.valid())
    {
        // merge subgraphs that have been compiled by the incremental compiler operation.
        _incrementalCompileOperation->mergeCompiledSubgraphs(getFrameStamp());
    }
。。。
}

part4 执行主摄像机_camera以及从摄像机组_slaves的更新回调(但是不会遍历到它们的子节点)

从摄像机组与主摄像机的关系:从摄像机组从本质上继承了主摄像机的投影矩阵,观察矩阵和场景筛选设置,但是可以在使用View::addSlave添加从摄像机时,设置投影矩阵与观察矩阵的偏置值。

  • 以使用CullSettings::setInheritanceMask设置CullSettings的继承掩码。
  • 设置一种或多种筛选方式可以使用CullSettings::setCullingMode函数(采用“或”运算来指定多种筛选方式),如果希望暂时屏蔽某一种筛选方式,可以如下编写代码:camera->setCullingMode(camera->getCullingMode() & ~osg::CullSettings::SMALL_FEATURE_CULLING);
  • 有关各种筛选方式的详细算法,可以参考CullingSet::isCulled函数,有关遮挡筛选和聚集筛选的算法请单独参阅OccluderNode,ConvexPlanarOccluder和ClusterCullingCallback等相关类的实现代码
void Viewer::updateTraversal()
{
。。。
	 if (_incrementalCompileOperation.valid())
    {
        // merge subgraphs that have been compiled by the incremental compiler operation.
        _incrementalCompileOperation->mergeCompiledSubgraphs(getFrameStamp());
    }

    {
        // Do UpdateTraversal 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(*_updateVisitor);
            }
        }
    }

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

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

        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(*_updateVisitor);
            }
        }

        _updateVisitor->setTraversalMode(tm);
    }
。。。
}

part5 根据漫游器_cameraManipulator的位置姿态矩阵,更新主摄像机_camera

void Viewer::updateTraversal()
{
。。。
	 if (_cameraManipulator.valid())
    {
    //Note, only used when working in stereo
        setFusionDistance( getCameraManipulator()->getFusionDistanceMode(),
                            getCameraManipulator()->getFusionDistanceValue() );

        _cameraManipulator->updateCamera(*_camera);
    }
。。。
}

part6 使用View::updateSlaves函数更新从摄像机组_slaves中所有摄像机的投影矩阵,观察矩阵和场景筛选设置(CullSettings)

void Viewer::updateTraversal()
{
。。。
	 updateSlaves();
。。。
}
void View::updateSlaves()
{
    for(unsigned int i=0; i<_slaves.size(); ++i)
    {
        Slave& slave = _slaves[i];
        slave.updateSlave(*this);
    }
}
struct OSG_EXPORT Slave
{
 			void updateSlave(View& view)
            {
                if (_updateSlaveCallback.valid()) _updateSlaveCallback->updateSlave(view, *this);
                else updateSlaveImplementation(view);
            }

            virtual void updateSlaveImplementation(View& view);
			osg::ref_ptr<osg::Camera>           _camera;
            osg::Matrixd                        _projectionOffset;
            osg::Matrixd                        _viewOffset;
            bool                                _useMastersSceneData;
            osg::ref_ptr<UpdateSlaveCallback>   _updateSlaveCallback;
};

void View::Slave::updateSlaveImplementation(View& view)
{
    if (!view.getCamera()) return;

    if (_camera->getReferenceFrame()==osg::Transform::RELATIVE_RF)
    {
        _camera->setProjectionMatrix(view.getCamera()->getProjectionMatrix() * _projectionOffset);
        _camera->setViewMatrix(view.getCamera()->getViewMatrix() * _viewOffset);
    }

    _camera->inheritCullSettings(*(view.getCamera()), _camera->getInheritanceMask());
}

bool View::addSlave(osg::Camera* camera, const osg::Matrix& projectionOffset, const osg::Matrix& viewOffset, bool useMastersSceneData)
{
    if (!camera) return false;

    camera->setView(this);


    if (useMastersSceneData)
    {
        camera->removeChildren(0,camera->getNumChildren());

        if (_camera.valid())
        {
            for(unsigned int i=0; i<_camera->getNumChildren(); ++i)
            {
                camera->addChild(_camera->getChild(i));
            }
        }
    }

    unsigned int i = _slaves.size();

    _slaves.push_back(Slave(camera, projectionOffset, viewOffset, useMastersSceneData));
    _slaves[i].updateSlave(*this);

    camera->setRenderer(createRenderer(camera));

    return true;
}

二、void View::setSceneData(osg::Node* node)中重要的操作

void View::setSceneData(osg::Node* node)
{
    if (node==_scene->getSceneData()) return;

    osg::ref_ptr<Scene> scene = Scene::getScene(node);

    if (scene)
    {
        OSG_INFO<<"View::setSceneData() Sharing scene "<<scene.get()<<std::endl;
        _scene = scene;
    }
    else
    {
        if (_scene->referenceCount()!=1)
        {
            // we are not the only reference to the Scene so we cannot reuse it.
            _scene = new Scene;
            OSG_INFO<<"View::setSceneData() Allocating new scene"<<_scene.get()<<std::endl;
        }
        else
        {
            OSG_INFO<<"View::setSceneData() Reusing existing scene"<<_scene.get()<<std::endl;
        }

        _scene->setSceneData(node);
    }

    if (getSceneData())
    {
#if 0
        #if defined(OSG_GLES2_AVAILABLE)
            osgUtil::ShaderGenVisitor sgv;
            getSceneData()->getOrCreateStateSet();
            getSceneData()->accept(sgv);
        #endif
#endif

        // now make sure the scene graph is set up with the correct DataVariance to protect the dynamic elements of
        // the scene graph from being run in parallel.
        osgUtil::Optimizer::StaticObjectDetectionVisitor sodv;
        getSceneData()->accept(sodv);

        // make sure that existing scene graph objects are allocated with thread safe ref/unref
        if (getViewerBase() &&
            getViewerBase()->getThreadingModel()!=ViewerBase::SingleThreaded)
        {
            getSceneData()->setThreadSafeRefUnref(true);
        }

        // update the scene graph so that it has enough GL object buffer memory for the graphics contexts that will be using it.
        // 用户程序中最多可用的GraphicsContext(图形设备上下文)数目,默认为32个
        getSceneData()->resizeGLObjectBuffers(osg::DisplaySettings::instance()->getMaxNumberOfGraphicsContexts());
    }

    computeActiveCoordinateSystemNodePath();

    assignSceneDataToCameras();
}
  • Scene::getScene(node),获取当前视景器对应的osgViewer::Scene对象,也就是场景。一个场景包括了唯一的场景图形根节点,分页数据库(DatabasePager),以及分页图像库(ImagePager)。Viewer视景器对象通常只包括一个Scene场景,而CompositeViewer复合视景器则可能包括多个场景对象。
  • View::assignSceneDataToCameras,这其中包括以下几项工作:
    1、对于场景漫游器_cameraManipulator,执行其setNode函数和home函数,也就是设置漫游器对应于场景图形根节点,并回到其原点位置。不过在我们使用setCameraManipulator函数时也会自动执行同样的操作。
    2、将场景图形赋予主摄像机_camera,同时设置它对应的渲染器(Renderer)的相关函数。
    3、同样将场景图形赋予所有的从摄像机_slaves,并设置每个从摄像机的渲染器(Renderer)。
void View::assignSceneDataToCameras()
{
    // OSG_NOTICE<<"View::assignSceneDataToCameras()"<<std::endl;

    if (_scene.valid() && _scene->getDatabasePager() && getViewerBase())
    {
        _scene->getDatabasePager()->setIncrementalCompileOperation(getViewerBase()->getIncrementalCompileOperation());
    }

    osg::Node* sceneData = _scene.valid() ? _scene->getSceneData() : 0;

    if (_cameraManipulator.valid())
    {
        _cameraManipulator->setNode(sceneData);

        osg::ref_ptr<osgGA::GUIEventAdapter> dummyEvent = _eventQueue->createEvent();

        _cameraManipulator->home(*dummyEvent, *this);
    }

    if (_camera.valid())
    {
        _camera->removeChildren(0,_camera->getNumChildren());
        if (sceneData) _camera->addChild(sceneData);

        Renderer* renderer = dynamic_cast<Renderer*>(_camera->getRenderer());
        if (renderer) renderer->setCompileOnNextDraw(true);

    }

    for(unsigned i=0; i<getNumSlaves(); ++i)
    {
        Slave& slave = getSlave(i);
        if (slave._camera.valid() && slave._useMastersSceneData)
        {
            slave._camera->removeChildren(0,slave._camera->getNumChildren());
            if (sceneData) slave._camera->addChild(sceneData);

            Renderer* renderer = dynamic_cast<Renderer*>(slave._camera->getRenderer());
            if (renderer) renderer->setCompileOnNextDraw(true);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值