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();
}
下面我们再次遍历所有 GraphicsContext 设备,对于每个 GraphicsContext 指针 gc,判断它是否为GraphicsWindow对象,并执行GraphicsWindow::grabFocusIfPointerInWindow函数。阅读 GraphicsWindowWin32 类(即 GraphicsContext 的具体实现者)的同名函数可以发现,这个函数不过是负责把鼠标焦点转到当前窗口上而已。

sgViewer/Viewer.cpp 第 496 行,void Viewer::realize()

void Viewer::realize()
{
//OSG_INFO<<"Viewer::realize()"<<std::endl; Contexts contexts;
getContexts(contexts); if (contexts.empty())
{
OSG_INFO<<"Viewer::realize() - No valid contexts found, setting up view across all screens."<<std::endl; // no windows are already set up so set up a default view std::string value;
if (osg::getEnvVar("OSG_CONFIG_FILE", value))
{
readConfiguration(value);
}
else
{
int screenNum = -;
osg::getEnvVar("OSG_SCREEN", screenNum); int x = -, y = -, width = -, height = -;
osg::getEnvVar("OSG_WINDOW", x, y, width, height); if (osg::getEnvVar("OSG_BORDERLESS_WINDOW", x, y, width, height))
{
osg::ref_ptr<osgViewer::SingleWindow> sw = new osgViewer::SingleWindow(x, y, width, height, screenNum);
sw->setWindowDecoration(false);
apply(sw.get());
}
else if (width> && height>)
{
if (screenNum>=) setUpViewInWindow(x, y, width, height, screenNum);
else setUpViewInWindow(x,y,width,height);
}
else if (screenNum>=)
{
setUpViewOnSingleScreen(screenNum);
}
else
{
setUpViewAcrossAllScreens();
}
} getContexts(contexts);
} if (contexts.empty())
{
OSG_NOTICE<<"Viewer::realize() - failed to set up any windows"<<std::endl;
_done = true;
return;
} // get the display settings that will be active for this viewer
osg::DisplaySettings* ds = _displaySettings.valid() ? _displaySettings.get() : osg::DisplaySettings::instance().get();
osg::GraphicsContext::WindowingSystemInterface* wsi = osg::GraphicsContext::getWindowingSystemInterface(); // pass on the display settings to the WindowSystemInterface.
if (wsi && wsi->getDisplaySettings()==) wsi->setDisplaySettings(ds); unsigned int maxTexturePoolSize = ds->getMaxTexturePoolSize();
unsigned int maxBufferObjectPoolSize = ds->getMaxBufferObjectPoolSize(); 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); /*
首先是 GraphicsContext::realize 函数,实际上也就是 GraphicsContext::realizeImplementation 函数。
realizeImplementation 是纯虚函数吗?没错,回想一下第三日的内容,当我们尝试使用createGraphicsContext 来创建一个图形设备上下文时,系统返回的实际上是这个函数的值:
而正如我们历经千辛万苦所分析的那样,wsref 所指向的是平台相关的 API 接口类,也就是 Win32 API 的接口,也就是 GraphicsWindowWin32.cpp 中对应类的实例。换句话说,此时 WindowingSystemInterface:: createGraphicsContext 函数返回的值,也应当是派生自GraphicsContext 的具体类的实例!
正确,对于 Windows 用户来说,这个函数返回的恰恰是 GraphicsWindowWin32 的实例,而前文的 realizeImplementation 函数,正是 GraphicsWindowWin32::realizeImplementation。
*/
gc->realize(); if (_realizeOperation.valid() && gc->valid())
{
gc->makeCurrent(); (*_realizeOperation)(gc); gc->releaseContext();
}
} // 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();
}
}
} // initialize the global timer to be relative to the current time.
//首先调用 osg::Timer::setStartTick 函数,启动 OSG 内部定时器并开始计时。
osg::Timer::instance()->setStartTick(); // pass on the start tick to all the associated event queues
//Viewer::setStartTick 函数的工作是找到当前视景器和所有 GraphicsContext 设备的事件队列_eventQueue,并设定它们的启动时刻为当前时间。
setStartTick(osg::Timer::instance()->getStartTick()); // configure threading.
//调用 ViewerBase::setUpThreading 函数……设置线程,对于一向以多线程渲染而闻名的 OSG 而言,这一定是个值得深究的话题。
setUpThreading(); if (osg::DisplaySettings::instance()->getCompileContextsHint())
{
for(unsigned int i=; i<= osg::GraphicsContext::getMaxContextID(); ++i)
{
osg::GraphicsContext* gc = osg::GraphicsContext::getOrCreateCompileContext(i); if (gc)
{
gc->createGraphicsThread();
gc->getGraphicsThread()->startThread();
}
}
}
#if 0
osgGA::GUIEventAdapter* eventState = getEventQueue()->getCurrentEventState();
if (getCamera()->getViewport())
{
osg::Viewport* viewport = getCamera()->getViewport();
eventState->setInputRange( viewport->x(), viewport->y(), viewport->x() + viewport->width(), viewport->y() + viewport->height());
}
else
{
eventState->setInputRange(-1.0, -1.0, 1.0, 1.0);
}
#endif
}

  OSG 的视景器包括四种线程模型,可以使用 setThreadingModel 进行设置,不同的线程模型在仿真循环运行时将表现出不同的渲染效率和线程控制特性。通常而言,这四种线程的特性如下:

  SingleThreaded:单线程模型。OSG 不会创建任何新线程来完成场景的筛选和渲染,因而也不会对渲染效率的提高有任何助益。它适合任何配置下使用。
  CullDrawThreadPerContext:OSG 将为每一个图形设备上下文(GraphicsContext)创建一个图形线程,以实现并行的渲染工作。如果有多个 CPU 的话,那么系统将尝试把线程分别放在不同的 CPU 上运行,不过每一帧结束前都会强制同步所有的线程。
  DrawThreadPerContext:这一线程模型同样会为每个 GraphicsContext 创建线程,并分配到不同的 CPU 上。十分值得注意的是,这种模式会在当前帧的所有线程完成工作之前,开始下一帧。
  CullThreadPerCameraDrawThreadPerContext:这一线程模型将为每个 GraphicsContext和每个摄像机创建线程,这种模式同样不会等待前一次的渲染结束,而是返回仿真循环并再次开始执行 frame 函数。如果您使用四核甚至更高的系统配置,那么使用这一线程模型将最大限度地发挥多 CPU 的处理能力。
  与 DrawThreadPerContext 和 CullThreadPerCameraDrawThreadPerContext 这两种同样可以用于多 CPU 系统,且相对更有效率的线程模型相比,CullDrawThreadPerContext 的应用范围比较有限;而 SingleThreaded 模式在单核以及配置较低的系统上运行稳定。

  这些话长篇大论地说出来似乎令人满腹疑窦:OSG 为什么要为每个 GraphicsContext 设备配置一个线程?为什么又要为每个摄像机配置一个线程?线程的同步是怎么实现的?线程与 CPU 的关系又是怎么处理的?OSG 入门书籍中常说的更新(Update)/筛选(Cull)/绘制(Draw)三线程又是在那里体现的?为什么……

  天哪,这么多问题我们都要解读吗?是的,绝对要解读,不管花费多少时间!OSG学习是为了实际的应用,但是只有真正理解了它的运行机制,才能够最有效地把这个愈加著名的实时场景渲染软件用好。但是有些事情是急不来的,从 frame 函数的源代码中可以大致推测出来,场景的筛选和绘制工作是由 ViewerBase::renderingTraversals 函数来完成的。相应的,很多线程的调度和同步工作也是在这个函数中完成的,那么就让我们把问题留到那个时候吧。不过不妨先透露一点信息:第四日中我们提到的渲染器(Renderer)类,事实上也是与 OSG 的渲染线程密切相关的,因为筛选和绘制的工作就是由它来具体负责!好的,遗留的问题可以说暂时得到了解答,不过新的问题又出现了,而且任务看起来更为艰巨,继续努力好了。

  线程相关的问题留待后面解决,不过还是让我们先通读一下 setUpThreading 函数的代码也无妨。它的工作主要是处理单线程(SingleThreaded)模式下,多处理器系统的数据线程分配方式。

  听起来很深奥,不过实际上这里没有多么复杂。在现阶段,如果采用单线程模式的话,OSG 系统将使用 CPU0 来处理用户更新、筛选和渲染等一切事务,而使用 CPU1 来处理场景的两个分页数据库(DatabasePager)线程(它们分别用于处理本地和网络上的场景数据)。

  这里还出现了一个 Viewer::getScenes 函数(osgViewer/Viewer.cpp,141 行),它的作用是获取当前视景器对应的 osgViewer::Scene 对象,也就是场景。一个场景包括了唯一的场景图形根节点,分页数据库(DatabasePager),以及分页图像库(ImagePager)。Viewer 视景器对象通常只包括一个 Scene 场景,而 CompositeViewer 复合视景器则可能包括多个场景对象。

  如果系统采用了 SingleThreaded 之外的其它线程模型,那么 setUpThreading 函数将自动执行 ViewerBase::startThreading——多线程渲染的最重要函数之一,这个函数将在我们追踪到 renderingTraversals 函数的时候重新进行解析。

void ViewerBase::setUpThreading()
{
if (_threadingModel==AutomaticSelection)
{
_threadingModel = suggestBestThreadingModel();
} // if required configure affinity before we start threads
if (_useConfigureAffinity) configureAffinity(); Contexts contexts;
getContexts(contexts); // set up affinity of main thread
OpenThreads::SetProcessorAffinityOfCurrentThread(_affinity); // set up the number of graphics contexts.
{
Scenes scenes;
getScenes(scenes); for(Scenes::iterator scitr = scenes.begin();
scitr != scenes.end();
++scitr)
{
if ((*scitr)->getSceneData())
{
// update the scene graph so that it has enough GL object buffer memory for the graphics contexts that will be using it.
(*scitr)->getSceneData()->resizeGLObjectBuffers(osg::DisplaySettings::instance()->getMaxNumberOfGraphicsContexts());
}
}
} if (_threadingModel==SingleThreaded)
{
if (_threadsRunning) stopThreading();
}
else
{
if (!_threadsRunning) startThreading();
} }

  好了,如果您还没有忘记我们来自何方的话,请回到 realize 函数,现在这个函数的执行已经接近了尾声,不过我们又遇到了一个问题:编译上下文(也就是 Compile Contexts,暂时就这样翻译吧)?如果要启用它的话并不困难,只需要在调用 realize 之前执行:

  osg::DisplaySettings::instance()->setCompileContextsHint(true);

  随后,正如您在 realize 函数的 491-503 行之间看到的,系统将设法遍历所有可能的GraphicsContext 设备,针对它们分别再各自添加一个新的 GraphicsContext 设备(也就是说,如果系统中已经有了数个图形上下文,那么现在又将新增同样数量的图形上下文与之对应),所用的函数为 GraphicsContext::getOrCreateCompileContext。这之后,分别执行了创建图形线程,设置 CPU 依赖性,以及启动图形线程的工作,具体的实现内容可以暂时忽略。

  观察 getOrCreateCompileContext 函数的内容,很快我们就可以发现其中的重点:这些新增的 GraphicsContext 对象使用了 pBuffer 的特性,并与对应的已有对象共享同一个图形上下文(Traits::sharedContext 特性)。事实上,这是 OSG 利用 OpenGL 的像素缓存(Pixel Buffer)技术,为图形上下文的后台编译提供的一种新的解决方案。这样不仅可以提高图形刷新的速度,还可以方便用户为某一特定的 GraphicsContext 设备添加特殊的处理动作,方法是使用osg::GraphicsContext::getCompileContext 获取后台图形上下文,再使用 GraphicsContext::add函数向其中追加 osg::Operation 对象,类似的例子可以参看 osgterrain。

  对了,在结束这一日的旅途之前,还要提示一句:“编译上下文”这一功能在 Windows的实现尚有问题,目前可能会造成系统的崩溃(不要大失所望呀^_^)。

文字参考:王锐老师《最长的一帧》
代码参考:OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield    (osg3.4)

osg 线程模型的更多相关文章

  1. 看我是如何处理自定义线程模型---java

    看过我之前文章的园友可能知道我是做游戏开发,我的很多思路和出发点是按照游戏思路来处理的,所以和web的话可能会有冲突,不相符合. 来说说为啥我要自定义线程模型呢? 按照我做的mmorpg或者mmoar ...

  2. HBase的Write Ahead Log (WAL) —— 整体架构、线程模型

    解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回放机制.每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中. 如果出 ...

  3. Netty学习三:线程模型

    1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...

  4. Mina、Netty、Twisted一起学(十):线程模型

    要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...

  5. WPF QuickStart系列之线程模型(Thread Model)

    这篇博客将介绍WPF中的线程模型. 首先我们先来看一个例子,用来计算一定范围内的素数个数. XAML: <Grid> <Grid.RowDefinitions> <Row ...

  6. servlet的生命周期与运行时的线程模型

    第 14 章 生命周期 注意 讲一下servlet的生命周期与运行时的线程模型,对了解servlet的运行原理有所帮助,这样才能避免一些有冲突的设计. 如果你不满足以下任一条件,请继续阅读,否则请跳过 ...

  7. eventloop & actor模式 & Java线程模型演进 & Netty线程模型 总结

    eventloop的基本概念可以参考:http://www.ruanyifeng.com/blog/2013/10/event_loop.html Eventloop指的是独立于主线程的一条线程,专门 ...

  8. 理解RxJava线程模型

    RxJava作为目前一款超火的框架,它便捷的线程切换一直被人们津津乐道,本文从源码的角度,来对RxJava的线程模型做一次深入理解.(注:本文的多处代码都并非原本的RxJava的源码,而是用来说明逻辑 ...

  9. Hbase WAL线程模型源码分析

    版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...

随机推荐

  1. Nginx服务rewrite模块功能说明 网站自动跳转功能

    实现域名地址信息跳转,用于做伪静态地址 www.impkk.com/oldboy?edu.html 动态地址 www.impkk.com/oldboy-edu.html 伪静态地址 rewrite ^ ...

  2. 纯js验证码

    纯js验证码 <!DOCTYPE html> <html> <head> <title>纯js验证码</title> </head&g ...

  3. JVM系列四:类加载

    类的生命周期 加载->验证->准备->解析->初始化->使用->卸载 类加载过程 类加载包括以上的前五个过程:加载,验证,准备,解析,初始化 加载 1.主要完成三个 ...

  4. 推荐系统(5)---大量项目topk近邻相似度

    Kd树+BBF(最邻近.次邻近查询)Python实现 kd树和BBF算法 精确Top-K检索及其加速方法探讨

  5. python函数名的应用、闭包和迭代器

    一.函数名的应用(第一类对象) 函数名是一个变量,但它是一个特殊的变量,与括号配合可以执行函数变量. 1.函数名的内存地址 def func(): print("哈哈") prin ...

  6. P1281 书的复制[二分]

    题目描述 现在要把m本有顺序的书分给k给人复制(抄写),每一个人的抄写速度都一样,一本书不允许给两个(或以上)的人抄写,分给每一个人的书,必须是连续的,比如不能把第一.第三.第四本书给同一个人抄写. ...

  7. eclipse中导入一个web项目

    首先 一般会因为环境不同而出错 所以还需要进一步配置,项目上右键properties

  8. vs2017 c# 控制台 输出中文显示问号 ; vs2017 c# 控制台 输出中文显示乱码

    问题: 解决: 在main方法最前面加一句就OK了! Console.OutputEncoding = Encoding.GetEncoding("gbk"); 或者 Consol ...

  9. 基于css文件编写一个简单的html前端页面

    因为文本原因,文件不能直接上传,以压缩包的形式上传上来 参考下载路径:https://i-beta.cnblogs.com/files

  10. LOJ P10150 括号配对 题解

    Analysis 区间dp裸题 初始化有点麻烦 i,j能匹配时要特判 #include<iostream> #include<cstdio> #include<cstri ...