OpenGL在GPU上就是不能多线程, 白折腾了
1.OperationQueue
OperationQueue 这个在哪里被用到?
getNextOperation需要传一个bool值, 如果是true 表示block Empty吗?
线程锁都是Mutex block() 包含Mutex调用
_operationsBlock是成员变量
OperationQueue类包含_operationsBlock (osg::RefBlock)
OperationQueue如果只是一个队列, 那么 runOperations却在这个类里, 这样子设计合理吗?
runOperations只执行一个Operation并不是执行队列中的所有
addOperationThread 却没有用锁, 难道是在启动线程之前
在新建OperationThread时 , 会新建一个OperationQueue , 然后把自己加到这OperationQueue里.GraphicsThread继承OperationThreadGraphicsContext里在初始化时会给自己new 一个GraphicsThread那些Barrier 栅栏就是Operation对象? 有个特别的类: osg::BarrierOperationBarrier是如何运作的? 是条件吗?
线程的_parent指的是什么? 在Graphics这个线程里, 是GraphicsContext
这回算是找到了启动线程的函数, 在ViewerBase.cpp里, startThreading()函数中. 这里调用gc->createGraphicsThread();新建线程.(*operation)(_parent.get());
直接在operation作执行吗? operation不只是栅栏作用是吗?_endDynamicDrawBlock->block();
这是把权限给线程的关键. 目前是这样的.EndOfDynamicDrawBlock
这个类不是继承Operation 而是继承 OpenThreads::BlockCountgc->getState()->setDynamicObjectRenderingCompletedCallback(_endDynamicDrawBlock.get());
把这个_endDynamicDrawBlock设置到哪去?也是gc里?_endDynamicDrawBlock->block();
主线程到子线程是知道的, 但子线程停下到主线程执行, 这里找不到. 这个问题暂时搁置.底层有三种Operation: GraphicsOperation 、 BarrierOperation 、 BlockOperationRunOperations 继承自 GraphicsOperation可能有这么一个规范: 读写函数, 在函数内上锁, 而不是在调用处上锁. 想必调用处上锁也不够稳妥.swapReadyBarrier 这个不命中断点, 不知怎么回事.SwapBuffersOperation 也继承自 GraphicsOperationGraphicsContext::runOperations() 这里的摄像机的相关处理,三个Operation 分别是:RunOperations BarrierOperation SwapBuffersOperation在BarrierOperation里, operator()里, 有执行block函数, 但是这个函数的默认参数是0 . 不知怎么算的, 执行完也没把子线程挡住.RunOperations执行完operator ()后, 会回到主线程一下,RunOperations -> 主线程 -> BarrierOperation -> SwapBuffersOperation
在找线程切换的关键点
所以, 一步步接近了, operation中RunOperations会回到主线程, 这是个关键点,这些线程中的操作中, 没有执行cull , cull 在主线程执行, 这也是个关键点.Barrier不懂swap 可能是双缓冲用的RunOperations中会不会绘制所有摄像机呢, 先把这部份读完再去看其它线程模式.
Renderer
继续从GraphicsContext::runOperations()读下去
在View.cpp中 createRenderer(getCamera())
为摄像机添加一个Rendererstate->popAllStateSets();
在osgUtil::SceneView里 这句在处理渲染状态的堆栈, 读到这里是不是过头了?
我想看到单个摄像机的渲染, 但只有一个摄像机的情况下, 好像没能看到顺序问题.
drawImplementation
从渲染台往下找, 找不到确切的绘图代码
从drawIm...回头找, 找到一个compile函数的调用, 意为编译GLObjectsVisitor::compile
GLObjectsVisitor::apply
GLObjectsVisitor继承自NodeVisitor, 遍历所有节点GLObjectsVisitor::apply(osg::StateSet& stateset)
针对StateSet的处理 _ext->glBindBuffer(GL_ARRAY_BUFFER_ARB,0);
通过Geometry的drawImplementation传顶点数据到显存
控制台输出
同样的线程模式, 默认的线程模式下,DEBUG_MESSAGE<<"end draw() "<<this<<std::endl;
这句执行完就是一个摄像机绘制完了.DEBUG_MESSAGE<<"draw() "<<this<<std::endl;
与DEBUG_MESSAGE<<"end draw() "<<this<<std::endl;
成对出现从输出控制台的信息中 end draw() 后面跟Render的指针, 两摄像机的指针是一样的, 这是为何? Render可以共用?现在建一个子项目来研究多摄像机问题, draw() got SceneView 0000015BC441C190
发现摄像机有三个, SceneView的指针却有两个, 且输出时间点后发现, 一帧只渲染一个SceneView, 难道是双缓冲?traits->doubleBuffer 设置为false 也是两个SceneView的情况, 且画面不能动了.
原本并不能每个摄像机各自一个线程
三个摄像机只有一个Renderer:
难道一个Renderer绘制三个摄像机的画面?所以VR应用中Slave摄像机的性能也就解释得通了.
我想研究什么?
各摄像机能否分开处理? 现在只有一个Renderer, 怎么折腾?
再去读最长的一帧:
最长的一帧讲到:
执行 ViewerBase::startThreading——多线程渲染的最重要函数之一,这个函数将在我们追踪
到 renderingTraversals 函数的时候重新进行解析。
确实有不一样的地方, 但是, 调试时感觉Renderer每帧只被调用一次
试过复制代码, 这次失败了
如果不再看代码 , 现在就着手去做呢?直接重写ViewBase吗? 因为分配线程的代码在这里想复制一份viewer出来改, 发现偶合的地方太多了.Viewer继承于ViewerBase, ViewerBase又是View的成员之一, View有用到ViewConfig, 有其它地方继承了ViewConfig. 绕个没完.
StateGraph
new StateGraph 会在SceneView::Cull -> CullVisitor::pushStateSet 里调用在这里新建渲染状态排序摄像机不动时, 不会再新建这渲染排序, 有动的话, 也不是全建一遍,第二次CullVisitor遍历时, 有些渲染状态并没有再新建StateGraph对象当物体在摄像机背后时, 会不断新建StateGraph对象, 会不会是想一但显示到摄像机前时,能马上改状态.当有物体在摄像机背后时, 会不断执行: StateGraph::find_or_insert
有个Condition对象
能啃得动代码的话, 就继续啃吧.
Renderer::ThreadSafeQueue这个类中包含_cond成员, 也就是OpenThreads::Condition对象.
这个条件成员需要操作_mutex进行等待.ThreadSafeQueue在Renderer有两个成员
_availableQueue 与 _drawQueue 作什么用, 我用猜的话, 不安全, 不猜了.
问题在于, 一个摄像机一个线程是怎么整的?
在View.cpp中 执行getCamera()->setRenderer(createRenderer(getCamera()));
所以只会有一个Renderer
ViewerBase::startThreading()这边, 会为摄像机建立线程, 但是, 只会对有Renderer的摄像机建立线程.
这函数内有个cameras列表, 但列表中只有一个摄像机.
Viewer::getCameras 这个函数是用于在这个列表中插入摄像机的, 可是, 只能插入主摄像机与从摄像机
场景中的摄像机, 即不是主摄像机也不是从摄像机, 如何触发渲染的? 摄像机的渲染次序排序是有效的.
在CullVisitor.cpp中, 会把摄像机根据是否会PRE_RENDER排序, 分到两个RenderState里, 这个操作每帧都执行
又一个问题是, 这一段只处理一个摄像机. 一共有两个摄像机, 主摄像机与场景中的摄像机.
加入渲染台的这个摄像机是正交摄像机(场景中的), 不是主摄像机
也对, CullVisitor::apply这种只遍历场景中的东西, 主摄像机不在场景中.
当时我不用Slave摄像机是因为 没法操作场景树,没法控制要显示哪些节点.
如果只有主摄像机有线程的话, 那么为什么线程模式会影响渲染采样?
在csdn搜索, 会有OpenThread相关的读代码记录
OpenThreads::Condition有broadcast()成员 用于解除条件限制的.
BlockCount
BlockCount有用到Condition
EndOfDynamicDrawBlock 包含BlockCount
BlockCount 类里, 调试中有执行到--_currentCount .
这句完后, 就执行_cond.broadcast(); 但是并没有马上跳回主线程.
王锐最长的一帧讲到completed()函数的作用, 就是执行这里的减减计数操作.
如果为树结构中的摄像机分配线程呢?
每帧的遍历又会在CullVisitor插入摄像机, 插入的摄像机会何处理?
CompositViewer或许能解决摄像机线程问题:
https://blog.csdn.net/dazhi_1314/article/details/5716104
再去读最长的一帧
有讲到绘制的关键实现:
在第十八日 这一章:
成的!看来代码解读的重心无疑要偏移到这个历史悠久的“场景视图类”当中了。
研究CompositeViewer
从这句看来, Stats是与Camera绑定的, 所以并不能从一个Stats取得各摄像机的运行时间从源代码改的话, 能写在哪? 摄像机的运行时间记录写在哪?getCameraThread 当取到绑定摄像机的线程时, 就不在主线程执行cull了camera->createCameraThread(); 这句在ViewBase被调用 ,当线程模式为CullThreadPerCameraDrawThreadPerContext时.在 GraphicsContext::runOperations()
这句还会执行, 这不对, 用循环的话, 就不像是多线程异步了.
用Stats辅助
Renderer中会执行这一句:
有这句才会出现draw的记录.在ViewerBase的startThreading()函数中 在遍历摄像机时加一段:
// START Arcadia Test 2020-04-03
camera->getStats()->collectStats("frame_rate", true);
camera->getStats()->collectStats("event", true);
camera->getStats()->collectStats("update", true);
camera->getStats()->collectStats("compile", true);
camera->getStats()->collectStats("rendering", true);
camera->getStats()->collectStats("scene", true);
camera->getStats()->collectStats("gpu", true);
// END Arcadia Test 2020-04-03
在ViewerBase的renderingTraversals()函数中 在遍历摄像机时加一段:
// START Arcadia test 2020-04-03
osg::Stats* statRecord = camera->getStats();
OSG_NOTICE << "#### statRecord Arcadia 2020-04-03" << std::endl;
OSG_NOTICE << "#### LatestFrameNumber:"<< statRecord->getLatestFrameNumber() <<"\tEarliestFrameNumbe"<< statRecord->getEarliestFrameNumber() << std::endl;
const osg::Stats::AttributeMap &mapAttr = statRecord->getAttributeMap(statRecord->getLatestFrameNumber());
for (auto iterA = mapAttr.begin(); iterA != mapAttr.end(); ++iterA)
{
OSG_NOTICE << "#### "<< iterA->first<<"\t:\t"<< iterA->second << std::endl;
}
camera->getStats()->collectStats("frame_rate", true);
camera->getStats()->collectStats("event", true);
camera->getStats()->collectStats("update", true);
camera->getStats()->collectStats("compile", true);
camera->getStats()->collectStats("rendering", true);
camera->getStats()->collectStats("scene", true);
camera->getStats()->collectStats("gpu", true);
// END Arcadia test 2020-04-03
两段都加, 才会出现draw用时的统计
从Stats模块看出, 是"Draw traversal time taken"是在draw或cull_draw函数之报记录的.
也就是这里能捕获到帧绘制的结束点.
发现第一个摄像机能取得Draw traversal end time 这个统计, 第二个摄像机就不行.
很奇特, 在draw()函数中, 直接控制台Log输出, 两个摄像机都是有的.
那么, 是不是Draw traversal end time的记录不在这里?
在draw()里, 在else分支控制台输出 , 并不会有输出. 也就是draw()里走的分支都是执行了主要代码段的.
但Stats数据却没有写入. 没有Stats就没法制作图表了.试试直接上锁:
在Debug版, 大场景35帧, 但用Release版就满满的144帧. 不管有没有上锁
本以为已经突破了, 知道了多线程的控制 , 可又证实是在单线程中:
就是在这个循环中实现的对Renderer::draw()的调用.