目录
1. 前言
osg为视景器的使用和调试提供了丰富的辅助组件,它们主要是以osg::ViewerBase的成员变量或交互事件处理器(osgGA::GUIEventHandler)的形式出现。osgViewer::StatsHandler、osg::Stats类就是其中的两个经常用到的辅助组件。
2. osgViewer::StatsHandler
2.1. 功能与用法说明
osgViewer::StatsHandler类的头文件位于osg源码目录下的:
include\osgViewer\ViewerEventHandlers
文件; osgViewer::StatsHandler类的cpp文件位于osg源码目录下的:
src\osgViewer\StatsHandler.cpp
该类的功能为向视景器osgViewer::Viewer(单视图)或osgViewer::CompositeViewer(多视图)输出屏幕统计信息。如下代码:
#include<osgViewer/Viewer>
#include<osgDB/readFile>
#include<osgViewer/ViewerEventHandlers>
int main(int argc, char* argv[])
{
osg::ArgumentParser arguments(&argc, argv);
osg::setNotifyLevel(osg::INFO);
osgViewer::Viewer viewer(arguments);
auto pNode = osgDB::readNodeFile(R"(E:\osg\OpenSceneGraph-Data\cow.osg)");
if (nullptr == pNode)
{
OSG_WARN << "file not exist!\r\n";
return 1;
}
auto pStatsEventHandler = new osgViewer::StatsHandler; // 构造一视景器统计事件处理器
viewer.addEventHandler(pStatsEventHandler); // 向视景器增加统计事件处理器
viewer.setSceneData(pNode);
return viewer.run();
}
为视景器对象viewer增加了一个pStatsEventHandler的统计事件处理器对象。一旦程序运行起来,默认情况下,第1次按小写s键,则只统计帧率,如下:
图1
第2次按小写s键时,保持第1次按下s键的信息外,还统计和视景器相关的信息,如下:
图2
当3次按下小写s键时, 保持第1、2次按下s键的信息外,还所有相机和场景相关的信息,如下:
图3
当4次按下小写s键时, 保持第1、2、3次按下s键的信息外,还统计所有视景器和场景相关的信息,如下:
图4
当5次按下小写s键时,则清除图1到图4的所有输出信息,程序保证按下s的次数不会超过5,当达到5时,自动回到0,然后每次按下小写s键,自动加1,递增到5。按下大写S时,则将统计信息输出到标准控制台窗口。一个综合统计图如下所示:
图5
上述统计的信息主要信息说明如下:
参 数 名 | 参 数 含 义 | 备 注 |
Frame Rate | 当前帧率 | 在debug模式下为DEBUG Frame Rate |
ThreadingModel | 线程模式 | ViewerBase::ThreadingModel枚举类型 |
Event | 一帧内交互事件耗费的时间 | 单位:秒 |
Update | 一帧内场景更新耗费的时间 | 单位:秒 |
Cull | 一帧内场景裁剪耗费的时间 | 单位:秒 |
Draw | 渲染一帧耗费的时间 | 单位:秒 |
GPU | 渲染一帧时,GPU耗费的时间 | 单位:秒 |
Drawable | 场景内独立可绘制体总数 | |
Geode | 场景内独立可绘叶节点总数 | |
Vertices | 场景内独立顶点总数 | |
Primitives | 场景内独立可图元数 |
表1
因为统计的参数很多,其它参数含义请参见StatsHandler.cpp类源码。
2.2. 主要接口说明
osgViewer::StatEventHandler类主要接口说明如下:
void setKeyEventTogglesOnScreenStats(int key);
int getKeyEventTogglesOnScreenStats() const;
上述接口功能为:设置或获取统计信息输出到屏幕的按键,默认为小写的s,可以设置为其它键,参数key是osgGA::GUIEventAdapter::KeySymbol枚举,即键盘虚拟码。如下将默认的小写s键改为数字0键作为统计信息的输出按下,此时按下0才能输出统计信息,s不再其作用:
auto pStatsEventHandler = new osgViewer::StatsHandler; // 构造一视景器统计事件处理器
pStatsEventHandler ->setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_0);
下面接口:
void setKeyEventPrintsOutStats(int key);
int getKeyEventPrintsOutStats() const ;
功能为:设置或获取统计信息输出到控制台窗口的按键,同上面的类似,不再赘述。
void reset();
上面接口功能为:重置统计信息,此时界面上不会有任何统计信息。注意和按下默认的s键或设定的统计信息键累计5次不输出统计信息的不同:重置是将用于输出统计信息的相机的图形设备上下文对象及相机孩子对象都清空,这样会释放内存,而后者则不会。
3. osg::Stats
视景器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));
}
}
}
...... // 其它代码略
其中以"Number of"开头的是被统计的属性名称,可以看成是对前文表1和后文表2的补充。
获取视景器的osg::Stats对象如下:
viewer.getViewerStats();
然后使用osg::Stats对象的如下接口设置或获取指定帧索引下指定属性名的属性值:
bool setAttribute(unsigned int frameNumber, const std::string& attributeName, double value);
inline bool getAttribute(unsigned int frameNumber, const std::string& attributeName, double& value) const;
如下为获取第100帧时的帧速率:
double frameRate;
viewer.getViewerStats()->getAttribute(100, "Frame rate", frameRate);
这两个函数第2个参数表示的主要属性名如下表所示:
属性名 | 含 义 |
Frame rate | 帧率 |
Event traversal time taken | 一帧内交互事件耗费的时间(单位:秒) |
Update traversal time taken | 更新场景一帧耗费时间(单位:秒) |
Cull traversal time taken | 一帧内场景裁剪耗费的时间(单位:秒) |
GPU draw time taken | 渲染一帧时,GPU耗费的时间(单位:秒) |
Draw traversal time taken | 场景内独立可绘制体总数 |
Number of unique Geode | 场景内独立的叶节点总数 |
Reference time | 系统运行事件(单位:秒) |
Number of unique Geometry | 场景内独立几何体总数 |
Number of unique Vertices | 场景内独立顶点总数 |
Number of unique Primitives | 场景内独立图元总数 |
表2
因为统计的参数很多,其它参数含义请参见StatsHandler.cpp类源码。其实osgViewer::StatEventHandler类内部就是通过osg::Stats类来实现统计信息的。
4. 附录
更多关于osg::Stats类用法请参考:osg::Stats类用法及该类源码剖析。
osg其它事件处理器用法参见如下博文: