allan 发表于 2014-9-17 17:08:54

OSG中与时间相关的类osg::FrameStamp,osg::Timer源码剖析

本帖最后由 allan 于 2014-9-17 17:11 编辑

这次分析一下OSG中的的osg::FrameStamp类和osg::Timer类,在OSG中的源码中这两个类的用法随处可见,但是一直没仔细阅读过它的源码,这次因为需要查到了这儿仔细阅读了一下,希望记录下来以后查阅时方便些。

1.osg::FrameStamp类
osg::FrameStamp这个类中准确的说只有四个成员变量,它的方法都是这些成员变量的get方法和set方法。
前三个成员变量如下,很好理解:
      unsigned int    _frameNumber; //这个成员变量用来记录帧号
      double          _referenceTime;//这个成员变量用来记录仿真开始了多少时间,下面会详细讲解
      double          _simulationTime;//这个成员变量还没搞明白它的用法,但是默认情况下和_referenceTime是一样的
我把剩下的这些成员变量称为第四个成员变量,用过C++中tm结构的都知道这些变量和tm结构体中的成员是统一的:
      int tm_sec;            /* Seconds.       (1 leap second) */
      int tm_min;            /* Minutes.       */
      int tm_hour;         /* Hours.          */
      int tm_mday;         /* Day.             */
      int tm_mon;            /* Month.          */
      int tm_year;         /* Year            - 1900.*/
      int tm_wday;         /* Day of week.    */
      int tm_yday;         /* Days in year.       */
      int tm_isdst;         /* DST.         [-1/0/1]*/
至于为什么要把tm结构体中的成员变量都列举出来源码中也说了,是为了保证FrameStamp类中的所有的成员变量都不是动态分配的,这样通过网络在不同的操作系统中传输数据时就不会有问题。
FrameStamp中的函数都是针对这些成员变量的操作:
对_frameNumber的操作:
      void setFrameNumber(unsigned int fnum) { _frameNumber = fnum; }
      unsigned int getFrameNumber() const { return _frameNumber; }
对_referenceTime的操作:
      void setReferenceTime(double refTime) { _referenceTime = refTime; }
      double getReferenceTime() const { return _referenceTime; }
对_simulationTime的操作:
      void setSimulationTime(double refTime) { _simulationTime = refTime; }
      double getSimulationTime() const { return _simulationTime; }
以及对我所说的第四个成员变量的操作,这个成员变量姑且叫做日历时间吧:注意这里无论是get还是set方法参数都是tm结构体类型了
      void setCalendarTime(const tm& calendarTime);
      void getCalendarTime(tm& calendarTime) const;
它的实现也非常简单,就是对结构体中的变量逐个地赋值:
void FrameStamp::setCalendarTime(const tm& ct)
{
    tm_sec = ct.tm_sec;            /* Seconds.    (1 leap second) */
    tm_min = ct.tm_min;            /* Minutes.    */
    tm_hour = ct.tm_hour;            /* Hours.    */
    tm_mday = ct.tm_mday;            /* Day.       */
    tm_mon = ct.tm_mon;            /* Month.    */
    tm_year = ct.tm_year;            /* Year    - 1900.*/
    tm_wday = ct.tm_wday;            /* Day of week.    */
    tm_yday = ct.tm_yday;            /* Days in year.    */
    tm_isdst = ct.tm_isdst;            /* DST.      [-1/0/1]*/
}

void FrameStamp::getCalendarTime(tm& ct) const
{
    ct.tm_sec = tm_sec;            /* Seconds.    (1 leap second) */
    ct.tm_min = tm_min;            /* Minutes.    */
    ct.tm_hour = tm_hour;            /* Hours.    */
    ct.tm_mday = tm_mday;            /* Day.       */
    ct.tm_mon = tm_mon;            /* Month.    */
    ct.tm_year = tm_year;            /* Year    - 1900.*/
    ct.tm_wday = tm_wday;            /* Day of week.    */
    ct.tm_yday = tm_yday;            /* Days in year.    */
    ct.tm_isdst = tm_isdst;            /* DST.      [-1/0/1]*/
}

除此之外就只剩下构造函数,复制构造函数,析构函数,赋值操作符:这些函数的实现主要就是对这四个成员变量的一些赋值而已,只需要记住,这些成员变量默认值都是0。
FrameStamp();//构造函数
FrameStamp(const FrameStamp& fs);//复制构造函数
FrameStamp& operator = (const FrameStamp& fs);//赋值操作符
virtual ~FrameStamp();//析构函数

单独看这个类其实感觉不到它可以用来干什么,这个类只有结合OSG中的其他类才可以看到它的用途,特别是View,Viewer等这些类。
最后看一下FrameStamp的注释:
/** Class which encapsulates the frame number, reference time and calendar
* time of specific frame, used to synchronize operations on the scene graph
* and other machines when using a graphics cluster.Note the calendar
* time can be an artificial simulation time or capture the real time
* of day etc.*/
就是说这个类主要是封装了某一帧的帧号,reference time(仿真时间?),calendar time(日历时间?),它用来在场景图中同步操作,或者当使用一个图形集群时来同步操作。注意calendar time(日历时间)可以是手动设置的仿真时间或者真实的日历时间。

在介绍FrameStamp的使用之前先插入两张继承关系图,


2._frameStamp在osg::View中的使用

接下来就是FrameStamp类的使用了,使用Source Insight工具查看OSG的源码发现在osg::View类中有一个成员变量,并且这个成员变量时protected类型的。所以它的子类都可以访问它
       osg::ref_ptr<osg::FrameStamp>   _frameStamp;
并且在osg::View这个类里有对这个成员变量操作的get和set方法:
      /** Set the frame stamp of the view. */
      void setFrameStamp(osg::FrameStamp* fs) { _frameStamp = fs; }

      /** Get the frame stamp of the view. */
      osg::FrameStamp* getFrameStamp() { return _frameStamp.get(); }

      /** Get the frame stamp of the view. */
      const osg::FrameStamp* getFrameStamp() const { return _frameStamp.get(); }
除此之外在osg::View这个类中就没有_frameStamp这个变量的引用的地方了,但是可以看到osgViewer::View这个类中有_frameStamp类的使用,根据上面的继承关系图可以看出osgViewer::View是继承自osg::View类的,所以osg::View类的protected成员是可以被osgViewer::View访问的,在osgViewer::View类中_frameStamp成员变量有这么几处使用到了:
1.构造函数里有如下代码:

    _frameStamp = new osg::FrameStamp;
    _frameStamp->setFrameNumber(0);
    _frameStamp->setReferenceTime(0);
    _frameStamp->setSimulationTime(0);
就是对_frameStamp这个成员变量进行初始化,并使用_frameStamp这个类的set方法设置成员变量的默认值为0,其实下面的3条set语句都可以省略掉的,因为FrameStamp类的构造函数会将它的成员变量都设为0。
2.take函数里

       _frameStamp = rhs_osgViewer->_frameStamp;
      // clear rhs
      rhs_osgViewer->_frameStamp = 0;
take函数里的这个用法不是关键的地方,它只是将你传递进来的osg::View对象的_framStamp成员变量赋值给当前的osg::Viewer::View对象。
3._frameStamp在osgViewer::Viewer类中的使用

接下来就是在osgViewer::Viewer这个类中的使用了,这儿就大量使用了_frameStamp这个成员变量。
1.constructorInit函数

void Viewer::constructorInit()
{
    _eventVisitor = new osgGA::EventVisitor;
    _eventVisitor->setActionAdapter(this);
    _eventVisitor->setFrameStamp(_frameStamp.get());//将_frameStamp这个成员变量赋给EventVisitor这个类中的成员变量

    _updateVisitor = new osgUtil::UpdateVisitor;
    _updateVisitor->setFrameStamp(_frameStamp.get());//将_frameStamp这个成员变量赋给UpdateVisitor这个类中的成员变量

    setViewerStats(new osg::Stats("Viewer"));
}
就是很简单的赋值语句,从这方面的定义中不难看出osgGA::EventVisitor和osgUtil::UpdateVisitor这两个类中也有FrameStamp类型的成员变量,并且这个成员变量的值和osg::View类中的成员变量的值是一样的。constructorInit这个函数在osgViewer::Viewer类的构造函数中被调用,所以里面的代码一定会执行。
2.advance函数

//注意advance函数是每帧都执行的
void Viewer::advance(double simulationTime)
{
    if (_done) return;

        //获取_frameStamp中的reference time(仿真时间?),就是上一帧调用setReferenceTime给_frameStamp设置的仿真时间
        //所以变量名为previousReferenceTime
    double previousReferenceTime = _frameStamp->getReferenceTime();
       
        //获取上一帧的帧号
    unsigned int previousFrameNumber = _frameStamp->getFrameNumber();

        //将帧号加1后使用setFrameNumber函数赋值给_frameStamp成员变量,注意advance这个函数每一帧都会执行
    _frameStamp->setFrameNumber(_frameStamp->getFrameNumber()+1);

        //设置仿真时间,就是仿真开始了多长时间,这里又出现了一个比较重要的成员变量,即_startTick。还有一个类osg::Timer
        //_startTick默认指定了一个程序启动的时间点,osg::Timer::instance()->tick()这个函数返回程序执行到当前代码时的时间点,
        //因为这两个时间点的单位都不是秒,所以不能简单的相减,而是使用osg::Timer::instance()->delta_s将这两个时间点之间的
        //时间间隔转换成秒
    _frameStamp->setReferenceTime( osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick()) );

        //判断advance函数传递进来的参数simulationTime,如果simulationTime为USE_REFERENCE_TIME,那么将reference time设置给
        //simulation time,由于simulationTime默认就为USE_REFERENCE_TIME,所以一开始讲FrameStamp类时就说过_simulationTime默认是和_referenceTime的含义一样的
    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());
    }


    if (osg::Referenced::getDeleteHandler())
    {
      osg::Referenced::getDeleteHandler()->flush();
      osg::Referenced::getDeleteHandler()->setFrameNumber(_frameStamp->getFrameNumber());
    }

}
说到advance函数,还需要将advance函数的调用关系列举出来,不然估计不久就又忘记了。注意查看上面的两张继承关系图
advance函数首先出现在osgViewer::ViewerBase类中,它是一个纯虚函数,就是说所有继承自osgViewer::ViewerBase的类都需要实现这个函数,osg中继承自osgViewer::ViewerBase类只有两个:osgViewer::Viewer 和osgViewer::CompositeViewer,到目前为止我一直是围绕着 osgViewer::Viewer的讨论展开的,osgViewer::Viewer讨论完了后再分析osgViewer::CompositeViewer就容易多了。
virtual void advance(double simulationTime=USE_REFERENCE_TIME) = 0;
osgViewer::ViewerBase中还有一个虚函数调用了advance函数,也需要列举出来,就是很著名的frame函数。
这是它的声明,注释说的非常清楚,这个函数渲染完整的一帧,调用了advance(), eventTraversal(), updateTraversal(), renderingTraversals()这几个函数。
      /** Render a complete new frame.
          * Calls advance(), eventTraversal(), updateTraversal(), renderingTraversals(). */
      virtual void frame(double simulationTime=USE_REFERENCE_TIME);
再看下它的实现:
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;
    }
       
        //注意frame是虚函数,advance是纯虚函数,所以ViewerBase没有实现advance函数,但是ViewerBase的子类一定会实现它
    advance(simulationTime);

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

advance函数对_frameStamp这个成员变量的操作是在每一帧中对_frameStamp成员变量中的帧号,仿真时间进行赋值,这样我们就可以通过osg::View中的getFrameStamp函数来获取这个成员变量并使用它的信息了。
在分析advance函数的过程中还出现了一个比较重要的与时间相关的成员变量和一个比较重要的类,就是_startTick和osg::Timer,仿真时间的计算还有很多其它函数都会使用到这个成员变量和这个类。_startTick变量是osgViewer::View类的成员变量:
      osg::Timer_t                            _startTick;
它的类型是osg::Timer_t类型的,osg::Timer_t是通过typedef来定义的,如下:就是一个64位的整形数据,int是32位的,为什么要用64位来定义_startTick是为了表示足够大的数,这个宏位于osg::Timer的头文件中。
#if defined(_MSC_VER)
    typedef __int64 Timer_t;
#else
    typedef unsigned long long Timer_t;
#endif
在这里也就顺便看看osg::Timer这个类,这个类比较简单,但是使用的地方也很多,需要把它的函数弄明白。
看完osg::Timer这个类后对advance函数中下面这条代码就好理解了:
_frameStamp->setReferenceTime( osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick()) );
可以猜想_startTick这个类是在仿真开始时对其进行了设置,那么上面代码的含义就是仿真进行了多长时间,也就是渲染的时间了。再来追踪下_startTick这个变量。
_startTick是osgViewer::View类的成员变量,在osgViewer::View这个类的构造函数中有如下代码:
   _startTick = 0;
但是应该发现_startTick不应该为0,它默认应该是程序启动时调用osg::Timer的tick函数返回的值,这样上面的逻辑才能解释通,的确是这样,osg::Viewer::View类中还有个虚函数:
      virtual void setStartTick(osg::Timer_t tick);
这个函数就是用来设置_startTick这个成员变量的。
void View::setStartTick(osg::Timer_t tick)
{
    _startTick = tick;
}
然后在osgViewer::Viewer中对这个函数进行了重写,使用source insight查看发现在osg中基本上都是调用的osgViewer::Viewer类的setStartTick函数。
void Viewer::setStartTick(osg::Timer_t tick)
{
    View::setStartTick(tick);

    Contexts contexts;
    getContexts(contexts,false);

    getEventQueue()->setStartTick(_startTick);
    for(Contexts::iterator citr = contexts.begin();
      citr != contexts.end();
      ++citr)
    {
      osgViewer::GraphicsWindow* gw = dynamic_cast<osgViewer::GraphicsWindow*>(*citr);
      if (gw)
      {
            gw->getEventQueue()->setStartTick(_startTick);
      }
    }
}
它首先调用了父类osgViewer::View的setStartTick函数,然后做了一些其他的工作。
在osgViewer::Viewer的realize函数中有这个两句核心的语句:而realize函数是一定会被执行一次的。
    // initialize the global timer to be relative to the current time.
    //将当前的tick值赋给起始时间,注意setStartTick()函数实现,默认就是将当前的tick值赋给起始时间
    osg::Timer::instance()->setStartTick();

    // pass on the start tick to all the associated event queues
   //然后使用osg::Timer类的起始时间赋给osgViewer::Viewer的_startTick成员变量
    setStartTick(osg::Timer::instance()->getStartTick());
分析到这儿就差不多了,最后再看osgViewer::Viewer中几个与时间相关的函数就会很好理解了:
      virtual void setStartTick(osg::Timer_t tick);
                void setReferenceTime(double time=0.0);
          virtual double elapsedTime();
                virtual osg::FrameStamp* getViewerFrameStamp() { return getFrameStamp(); }

osg::Timer类
4.osg::Timer类

osg::Timer类中有两个成员变量,如下:
protected :

      Timer_t _startTick;//开始时间,以tick为单位
      double_secsPerTick;//每一个tick为多少秒
这里主要就是tick了,可以把tick可以理解成和时,分,秒一样的时间单位,只不过这个单位比较方便计算机来计算时间,但不适合我们人来计算时间,所以还有一个成员变量是用来存储tick到秒这两个单位之间转换的,就是_secsPerTick(每一个tick为多少秒)。
osg::Timer中比较重要的函数就是构造函数和tick函数,它的构造函数和tick函数里调用了系统的api,由于osg是跨平台的,所以里面有针对Windows和非Windows平台的代码,这里只分析windows平台下的代码:
    Timer::Timer()
    {
      LARGE_INTEGER frequency;
      if(QueryPerformanceFrequency(&frequency))
      {
            _secsPerTick = 1.0/(double)frequency.QuadPart;
      }
      else
      {
            _secsPerTick = 1.0;
            OSG_NOTICE<<"Error: Timer::Timer() unable to use QueryPerformanceFrequency, "<<std::endl;
            OSG_NOTICE<<"timing code will be wrong, Windows error code: "<<GetLastError()<<std::endl;
      }

      setStartTick();
    }

    Timer_t Timer::tick() const
    {
      LARGE_INTEGER qpc;
      if (QueryPerformanceCounter(&qpc))
      {
            return qpc.QuadPart;
      }
      else
      {
            OSG_NOTICE<<"Error: Timer::Timer() unable to use QueryPerformanceCounter, "<<std::endl;
            OSG_NOTICE<<"timing code will be wrong, Windows error code: "<<GetLastError()<<std::endl;
            return 0;
      }
    }

构造函数和tick函数里面主要就是QueryPerformanceFrequency和QueryPerformanceCounter,在网上查阅了不少资料和msdn,发现这两个函数是Visual C++提供并且仅供Windows 95及其后续版本使用,其精度与CPU的时钟频率有关,它们要求计算机从硬件上支持精确定时器。QueryPerformanceFrequency()函数和QueryPerformanceCounter()函数的原型如下:
BOOL QueryPerformanceFrequency (LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
这儿又出现了一个新的数据类型LARGE_INTEGER,这就是win32 api常见的形式了,查看LARGE_INTEGER类型的定义:
typedef union _LARGE_INTEGER {
        struct {   
                DWORD LowPart;   
                LONG HighPart;
                } u;
                LONGLONG QuadPart;
} LARGE_INTEGER,*PLARGE_INTEGER;
上面是msdn上的定义,LARGE_INTEGER这个类型是一个联合体,msdn上的备注是如果编译器支持64位的整型变量,那么使用QuadPart来存储64位的整型值,否则使用LowPart和HighPart这两个变量来存储64位的整型值。
QueryPerformanceFrequency这个函数是用来查询时钟频率,就是每秒有多少个tick,它的倒数就是每个tick对应多少秒了,然后将这个值赋给_secsPerTick这个成员变量,
构造函数的上半部分就这些东西,然后就是调用setStartTick函数,setStartTick函数很容易:
      /** Set the start.*/
      void setStartTick() { _startTick = tick(); }
就是调用tick()函数并将返回值设置_startTick。
下面分析tick函数,tick函数里主要也就是QueryPerformanceCounter这个win32函数了,msdn中说这个函数返回的是当前的时钟计数,就是说每次调用tick,会返回一个当前的时间点,这个时间点的单位是计算机能够识别的,所以返回值的单位是tick。
关于_secsPerTick和_startTick这两个成员变量在构造函数和tick函数中的分析就到这儿了,主要是搞明白tick的含义,可以把tick也想象成一种时间单位,这个时间单位是计算机本身用来计算时间的,_startTick就是一个时间点,它是以tick为单位,当调用tick()函数时又会返回一个tick时间点,这两个时间点相减就是花费了多长时间。
      /** Set the start.*/
        //将当前的时间点设置给起始时间,这个函数在构造函数中被调用,
      void setStartTick() { _startTick = tick(); }
               
        //自己定义起始时间,单位是tick
      void setStartTick(Timer_t t) { _startTick = t; }
               
        //返回起始时间
      Timer_t getStartTick() const { return _startTick; }


      /** Get elapsed time in seconds.*/
        //返回当前时间与起始时间之间的间隔,单位是秒
      inline double time_s() const { return delta_s(_startTick, tick()); }

      /** Get elapsed time in milliseconds.*/
        //返回当前时间与起始时间之间的间隔,单位是毫秒
      inline double time_m() const { return delta_m(_startTick, tick()); }

      /** Get elapsed time in microseconds.*/
        //返回当前时间与起始时间之间的间隔,单位是微秒
      inline double time_u() const { return delta_u(_startTick, tick()); }

      /** Get elapsed time in nanoseconds.*/
        //返回当前时间与起始时间之间的间隔,单位是纳秒
      inline double time_n() const { return delta_n(_startTick, tick()); }

      /** Get the time in seconds between timer ticks t1 and t2.*/
        //计算两个tick时间点之间的时间间隔,单位是秒
      inline double delta_s( Timer_t t1, Timer_t t2 ) const { return (double)(t2 - t1)*_secsPerTick; }

      /** Get the time in milliseconds between timer ticks t1 and t2.*/
      inline double delta_m( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e3; }

      /** Get the time in microseconds between timer ticks t1 and t2.*/
      inline double delta_u( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e6; }

      /** Get the time in nanoseconds between timer ticks t1 and t2.*/
      inline double delta_n( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e9; }

      /** Get the the number of seconds per tick. */
      inline double getSecondsPerTick() const { return _secsPerTick; }

osg::Timer中最后还有一个函数,就是
      static Timer* instance();
Timer* Timer::instance()
{
    static Timer s_timer;
    return &s_timer;
}
如果使用instance()这个静态函数,那么整个应用程序中使用的都是Timer类的同一个实例,就是说在osg中Timer是单例的。















页: [1]
查看完整版本: OSG中与时间相关的类osg::FrameStamp,osg::Timer源码剖析