目录
1. 概述
Stats是英文单词statistics的简写。顾名思义,就知道osg::Stats是osg中用来统计某些信息的类,如:帧率等,在osg中很多地方用到该类统计某些信息,如下代码是摘自osg的Viewer::eventTraversal()函数,该段代码记录了osg事件遍历开始时刻、事件遍历完成时刻、遍历耗时时长信息:
void Viewer::eventTraversal()
{
...... // 其它代码略
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);
}
...... // 其它代码略
}
如下代码是摘自osg的Viewer::renderingTraversals()函数,记录了某些必要的信息:
void ViewerBase::renderingTraversals()
{
...... // 其它代码略
stats->setAttribute(frameNumber, "Number of unique StateSet", static_cast<double>(statsVisitor._statesetSet.size()));
stats->setAttribute(frameNumber, "Number of unique Group", static_cast<double>(statsVisitor._groupSet.size()));
stats->setAttribute(frameNumber, "Number of unique Transform", static_cast<double>(statsVisitor._transformSet.size()));
stats->setAttribute(frameNumber, "Number of unique LOD", static_cast<double>(statsVisitor._lodSet.size()));
stats->setAttribute(frameNumber, "Number of unique Switch", static_cast<double>(statsVisitor._switchSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geode", static_cast<double>(statsVisitor._geodeSet.size()));
stats->setAttribute(frameNumber, "Number of unique Drawable", static_cast<double>(statsVisitor._drawableSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geometry", static_cast<double>(statsVisitor._geometrySet.size()));
stats->setAttribute(frameNumber, "Number of unique Vertices", static_cast<double>(statsVisitor._uniqueStats._vertexCount));
stats->setAttribute(frameNumber, "Number of unique Primitives", static_cast<double>(unique_primitives));
...... // 其它代码略
stats->setAttribute(frameNumber, "Number of instanced Stateset", static_cast<double>(statsVisitor._numInstancedStateSet));
stats->setAttribute(frameNumber, "Number of instanced Group", static_cast<double>(statsVisitor._numInstancedGroup));
stats->setAttribute(frameNumber, "Number of instanced Transform", static_cast<double>(statsVisitor._numInstancedTransform));
stats->setAttribute(frameNumber, "Number of instanced LOD", static_cast<double>(statsVisitor._numInstancedLOD));
stats->setAttribute(frameNumber, "Number of instanced Switch", static_cast<double>(statsVisitor._numInstancedSwitch));
stats->setAttribute(frameNumber, "Number of instanced Geode", static_cast<double>(statsVisitor._numInstancedGeode));
stats->setAttribute(frameNumber, "Number of instanced Drawable", static_cast<double>(statsVisitor._numInstancedDrawable));
stats->setAttribute(frameNumber, "Number of instanced Geometry", static_cast<double>(statsVisitor._numInstancedGeometry));
stats->setAttribute(frameNumber, "Number of instanced Vertices", static_cast<double>(statsVisitor._instancedStats._vertexCount));
stats->setAttribute(frameNumber, "Number of instanced Primitives", static_cast<double>(instanced_primitives));
...... // 其它代码略
getViewerStats()->setAttribute(frameNumber, "Rendering traversals begin time ", beginRenderingTraversals);
getViewerStats()->setAttribute(frameNumber, "Rendering traversals end time ", endRenderingTraversals);
getViewerStats()->setAttribute(frameNumber, "Rendering traversals time taken", endRenderingTraversals-beginRenderingTraversals);
...... // 其它代码略
}
本博文讲述osg::Stats类用法及对其实现进行源码剖析。
2.代码环境说明
环境说明如下:
- OpenSceneGraph-3.6.2。
- Windows 10。
- Microsoft Visual Studio Community 2022 (64 位) - Current
版本 17.5.5。
说明:本博文是基于OpenSceneGraph的3.6.2版本来讲解的,读者版本可能和本人的版本不同,故本人的源码或功能可能在细节上和读者的有所不同。
3.Stats类用法说明
3.1. 函数接口说明
本节只讲述public接口,Stats类内部调用的private、protected接口不讲述。
Stats(const std::string& name);
Stats(const std::string& name, unsigned int numberOfFrames);
这两个都为构造函数,参数name用来区分不同Stats类对象,参数numberOfFrames表示Stats类每次统计多少帧的帧信息,如果不传入该参数(此时是调用第1个构造函数的情况),则默认统计25帧的帧信息。
void allocate(unsigned int numberOfFrames);
本函数为统计帧数为numberOfFrames的帧开辟内存空间,看看源码就知道其实就是分配一个大小为numberOfFrames的vector,该vector中的每个元素是一个map,该map存放该帧的每种属性键值对。
bool setAttribute(unsigned int frameNumber, const std::string& attributeName, double value);
本函数将帧索引号为frameNumber的帧的属性名为attributeName,属性值为value保存到内存,即allocate开辟的内存空间。
inline bool getAttribute(unsigned int frameNumber, const std::string& attributeName, double& value) const
本函数是setAttribute函数的逆函数,其功能将帧索引号为frameNumber的帧,该帧属性为attributeName的取出来存放到第三个参数value中。
bool getAveragedAttribute(const std::string& attributeName, double& value, bool averageInInverseSpace=false) const;
bool getAveragedAttribute(unsigned int startFrameNumber, unsigned int endFrameNumber, const std::string& attributeName, double& value, bool averageInInverseSpace=false) const;
第1个函数获取第1次记录(最早)的帧(含)到当前最近(最晚)的一帧(含)的属性名为attributeName的属性的平均值(averageInInverseSpace为false)或者平均值的倒数值(averageInInverseSpace为true时)。
第2个函数获取[startFrameNumber, endFrameNumber]属性名为attributeName的属性的平均值(averageInInverseSpace为false)或者平均值的倒数值(averageInInverseSpace为true时)。
void report(std::ostream& out, const char* indent=0) const;
void report(std::ostream& out, unsigned int frameNumber, const char* indent=0) const;
第1个函数是向控制台输出第1次记录(最早)的帧(含)到当前最近(最晚)的一帧(含)的所有帧的所有属性值,并以ident为缩进量缩进打印输出。
第2个函数是向控制台输出帧号为第2个参数的帧的所有属性值,并以ident为缩进量缩进打印输出。
void collectStats(const std::string& str, bool flag) { _collectMap[str] = flag; }
这个函数收集(flag为true时)或不收集 (flag为false时)属性名为第1个参数表示的属性信息。
3.2. 用法说明
视景器osgViewer::Viewer(单视图)或osgViewer::CompositeViewer(多视图)对象(为了便于后文描述,暂称为viewer)可以通过setViewerStats函数设置统计类对象。如下代码设置osg::Stats对象到视景器:
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
auto pStats = new osg::Stats("test"); // 构造一个统计类对象,参数为统计类对象名称,最好根据业务需求取名
pStats->collectStats("scene", true); // 统计场景数据。如果设置为false,则场景有关的数据不统计
viewer->setViewerStats(pStats); // 将统计类对象设置到视景器。
...... // 其它代码
viewer->run();
在执行视景器的run函数时会进入到ViewerBase类的renderingTraversals函数,在该函数中统计了场景相关信息,如下:
void ViewerBase::renderingTraversals()
{
...... // 其它代码略
// 如果为视景器设置了统计对象且需要统计场景类的各种属性
if (getViewerStats() && getViewerStats()->collectStats("scene"))
{
Views views;
getViews(views);
for(Views::iterator vitr = views.begin();
vitr != views.end();
++vitr)
{
View* view = *vitr;
osg::Stats* stats = view->getStats();
osg::Node* sceneRoot = view->getSceneData();
if (sceneRoot && stats)
{
osgUtil::StatsVisitor statsVisitor;
sceneRoot->accept(statsVisitor);
statsVisitor.totalUpStats();
unsigned int unique_primitives = 0;
osgUtil::Statistics::PrimitiveCountMap::iterator pcmitr;
for(pcmitr = statsVisitor._uniqueStats.GetPrimitivesBegin();
pcmitr != statsVisitor._uniqueStats.GetPrimitivesEnd();
++pcmitr)
{
unique_primitives += pcmitr->second;
}
stats->setAttribute(frameNumber, "Number of unique StateSet", static_cast<double>(statsVisitor._statesetSet.size()));
stats->setAttribute(frameNumber, "Number of unique Group", static_cast<double>(statsVisitor._groupSet.size()));
stats->setAttribute(frameNumber, "Number of unique Transform", static_cast<double>(statsVisitor._transformSet.size()));
stats->setAttribute(frameNumber, "Number of unique LOD", static_cast<double>(statsVisitor._lodSet.size()));
stats->setAttribute(frameNumber, "Number of unique Switch", static_cast<double>(statsVisitor._switchSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geode", static_cast<double>(statsVisitor._geodeSet.size()));
stats->setAttribute(frameNumber, "Number of unique Drawable", static_cast<double>(statsVisitor._drawableSet.size()));
stats->setAttribute(frameNumber, "Number of unique Geometry", static_cast<double>(statsVisitor._geometrySet.size()));
stats->setAttribute(frameNumber, "Number of unique Vertices", static_cast<double>(statsVisitor._uniqueStats._vertexCount));
stats->setAttribute(frameNumber, "Number of unique Primitives", static_cast<double>(unique_primitives));
unsigned int instanced_primitives = 0;
for(pcmitr = statsVisitor._instancedStats.GetPrimitivesBegin();
pcmitr != statsVisitor._instancedStats.GetPrimitivesEnd();
++pcmitr)
{
instanced_primitives += pcmitr->second;
}
stats->setAttribute(frameNumber, "Number of instanced Stateset", static_cast<double>(statsVisitor._numInstancedStateSet));
stats->setAttribute(frameNumber, "Number of instanced Group", static_cast<double>(statsVisitor._numInstancedGroup));
stats->setAttribute(frameNumber, "Number of instanced Transform", static_cast<double>(statsVisitor._numInstancedTransform));
stats->setAttribute(frameNumber, "Number of instanced LOD", static_cast<double>(statsVisitor._numInstancedLOD));
stats->setAttribute(frameNumber, "Number of instanced Switch", static_cast<double>(statsVisitor._numInstancedSwitch));
stats->setAttribute(frameNumber, "Number of instanced Geode", static_cast<double>(statsVisitor._numInstancedGeode));
stats->setAttribute(frameNumber, "Number of instanced Drawable", static_cast<double>(statsVisitor._numInstancedDrawable));
stats->setAttribute(frameNumber, "Number of instanced Geometry", static_cast<double>(statsVisitor._numInstancedGeometry));
stats->setAttribute(frameNumber, "Number of instanced Vertices", static_cast<double>(statsVisitor._instancedStats._vertexCount));
stats->setAttribute(frameNumber, "Number of instanced Primitives", static_cast<double>(instanced_primitives));
}
}
}
...... // 其它代码略
如果像下面那样调用前文到的collectStats函数关闭场景统计信息,则所有场景有关的属性都不会统计:
// 关闭场景属性的统计
getViewerStats() && getViewerStats()->collectStats("scene", false);
4. 源码分析
本类源码很简单,唯一有点难理解的是setAttribute函数,该函数如下:
bool Stats::setAttribute(unsigned int frameNumber, const std::string& attributeName, double value)
{
if (frameNumber<getEarliestFrameNumber()) return false;
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
if (frameNumber>_latestFrameNumber)
{
// need to advance
// first clear the entries up to and including the new frameNumber
for(unsigned int i = _latestFrameNumber+1; i<= frameNumber; ++i)
{
unsigned int index = (i - _baseFrameNumber) % _attributeMapList.size();
_attributeMapList[index].clear();
}
if ( (frameNumber-_baseFrameNumber) >= static_cast<unsigned int>(_attributeMapList.size()))
{
_baseFrameNumber = (frameNumber/_attributeMapList.size())*_attributeMapList.size();
}
_latestFrameNumber = frameNumber;
}
int index = getIndex(frameNumber);
if (index<0)
{
OSG_NOTICE<<"Failed to assign valid index for Stats::setAttribute("<<frameNumber<<","<<attributeName<<","<<value<<")"<<std::endl;
return false;
}
AttributeMap& attributeMap = _attributeMapList[index];
attributeMap[attributeName] = value;
return true;
}
这个函数的基本思想是:利用vector构建一个环形、首尾相接的顺序表容器,该容器存放从最早的一帧到最近的一帧。容器中的元素是按帧号来索引的,容器中的每个元素是个map,该map的key该帧的属性名,value是该属性对应的属性值。当超过预定设置的帧数目时(如:默认的25帧或通过allocate设置的帧数目),则更新环形容器存放的帧编号,以保证环形顺序表容器始终存放的是当前最近的帧数目(如:默认的25帧或通过allocate设置的帧数目)。如下所示:
5. 后记
osg::Stats一般和osgViewer::StatsHandler一起使用,关于osgViewer::StatsHandler的使用,可参考:浅谈osgViewer::StatsHandler、osg::Stats类的用法