最长的一帧学习 part2

文章目录

一、osgViewer:: ViewerBase::setUpThreading()

在osgViewer::Viewer::realize()中使用了该接口。

如果用户没有设置线程模型,则使用ViewerBase::suggestBestThreadingModel自动进行判断:

  • 如果定义了环境变量OSG_THREADING,且其中内容为四个字符串中的一个,则采用对应的模型,“SingleThreaded”(适用于配置低),“CullDrawThreadPerContext”(不建议使用),“DrawThreadPerContext”,“CullThreadPerCameraDrawThreadPerContext”。模型参考
  • 如果当前不存在图形设备或者摄像机,则采用SingleThreaded模型。
  • 如果当前只存在一个图形窗口(这是最常见的情况),则采用SingleThreaded模型(单核)或DrawThreadPerContext模型(多核)。
  • 如果系统CPU数超过当前图形设备和摄像机数总合,为了充分发挥每个CPU的能力,将采用CullThreadPerCameraDrawThreadPerContext模型。
  • 以上情形均不符合时,采用DrawThreadPerContext模型。
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();
    }

}

二、osgViewer:: ViewerBase::stopThreading()

  • 使用Viewer::getContexts函数获取当前所有的图形设备(GraphicsContext);
  • 使用Viewer::getCameras函数获取当前所有的摄像机(主摄像机和所有从摄像机)。
  • 并重置当前的图形线程和相机线程。
  • 重置对于单线程模型来说无用的值:
    1._threadsRunning
    2.ViewerBase::_startRenderingBarrier:可以理解为渲染启动的一个栅栏标志,用于同步开始所有的图形设备的线程操作。
    3.ViewerBase::_endRenderingDispatchBarrier:渲染结束的一个栅栏标志,用于同步结束所有的图形设备的线程操作。
    4.ViewerBase::_endDynamicDrawBlock:用于同步结束所有的动态对象绘制操作,这里所谓的动态对象,指得是使用Object::setDataVariance设置为DYNAMIC的场景对象。
void ViewerBase::stopThreading()
{
    if (!_threadsRunning) return;

    OSG_INFO<<"ViewerBase::stopThreading() - stopping threading"<<std::endl;

    Contexts contexts;
    getContexts(contexts);

    Cameras cameras;
    getCameras(cameras);

    Contexts::iterator gcitr;
    Cameras::iterator citr;

    for(Cameras::iterator camItr = cameras.begin();
        camItr != cameras.end();
        ++camItr)
    {
        osg::Camera* camera = *camItr;
        Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
        if (renderer) renderer->release();
    }

    // delete all the graphics threads.
    for(gcitr = contexts.begin();
        gcitr != contexts.end();
        ++gcitr)
    {
        (*gcitr)->setGraphicsThread(0);
    }

    // delete all the camera threads.
    for(citr = cameras.begin();
        citr != cameras.end();
        ++citr)
    {
        (*citr)->setCameraThread(0);
    }

    for(Cameras::iterator camItr = cameras.begin();
        camItr != cameras.end();
        ++camItr)
    {
        osg::Camera* camera = *camItr;
        Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
        if (renderer)
        {
            renderer->setGraphicsThreadDoesCull( true );
            renderer->setDone(false);
        }
    }


    _threadsRunning = false;
    _startRenderingBarrier = 0;
    _endRenderingDispatchBarrier = 0;
    _endDynamicDrawBlock = 0;

    OSG_INFO<<"Viewer::stopThreading() - stopped threading."<<std::endl;
}

三、osgViewer:: ViewerBase::startThreading()

详见这里

四、osgViewer:: ViewerBase::renderingTraversals()

OSG的场景渲染过程可以简单地分为三个阶段:

  • 用户(APP)阶段,更新用户数据,负责场景对象的运动和管理等等.
  • 筛选(CULL)阶段,负责对场景中的对象进行筛选裁减,略过那些不会被用户所见(因而不必渲染)的物体,并根据渲染状态的相似性对即将进入渲染管线的对象排序(从而避免OpenGL状态量的频繁切换.
  • 绘制(DRAW)阶段,执行各种OpenGL操作,将数据送入OpenGL渲染管线及显示系统处理.v

如果有多个图形设备(渲染窗口)时,需要分别为每个窗口的每个摄像机执行相应的筛选和绘制工作,因为各个摄像机的投影矩阵和观察矩阵均可能不同;不过用户(APP)阶段不需要被执行多次,因为用户数据应当是被各个图形设备所共享的。

  • 根据线程模式不同,用户/筛选/绘制可以将前后两帧的工作稍微有所交叠;
  • 用户更新(APP)和场景筛选(CULL),以及场景筛选和绘制(DRAW)的工作互相不能重叠;
  • 但是我们可以允许在上一帧的绘制没有结束之前,就开始下一帧的用户数据更新工作;我们还可以允许由不同的CPU来执行不同图形设备的筛选和绘制工作,从而提高整体渲染的效率,实现实时渲染的目的。
    参考

part1 首先使用ViewerBase::checkWindowStatus检查是否存在有效的图形设备,不存在的话,需要使用ViewerBase::stopThreading停止线程运行+记录渲染遍历开始/结束时的各种属性

void ViewerBase::renderingTraversals()
{
	Contexts contexts;
    getContexts(contexts);

    // check to see if windows are still valid
    checkWindowStatus(contexts);
    if (_done) return;

    double beginRenderingTraversals = elapsedTime();

    osg::FrameStamp* frameStamp = getViewerFrameStamp();
    unsigned int frameNumber = frameStamp ? frameStamp->getFrameNumber() : 0;

    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));
           }
        }
    }
   。。。。。。
	 if (getViewerStats() && getViewerStats()->collectStats("update"))
    {
        double endRenderingTraversals = elapsedTime();

        // update current frames stats
        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);
    }

    _requestRedraw = false;
	
}

part2 遍历视景器对应的所有Scene场景(Viewer单视景器只存在一个场景),记录分页数据库的更新启动帧(使用DatabasePager::signalBeginFrame,这将决定DatabasePager中的数据请求是否过期),并计算场景节点的边界球。

void ViewerBase::renderingTraversals()
{
。。。
	Scenes scenes;
    getScenes(scenes);

    for(Scenes::iterator sitr = scenes.begin();
        sitr != scenes.end();
        ++sitr)
    {
        Scene* scene = *sitr;
        if (!scene) continue;

        osgDB::DatabasePager* dp = scene->getDatabasePager();
        if (dp) dp->signalBeginFrame(frameStamp);

        osgDB::ImagePager* ip = scene->getImagePager();
        if (ip) ip->signalBeginFrame(frameStamp);

        if (scene->getSceneData())
        {
            // fire off a build of the bounding volumes while we
            // are still running single threaded.
            scene->getSceneData()->getBound();
        }
    }

。。。
}

part3 获取当前所有的图形设备(GraphicsContext,开头已经获得)和摄像机。

void ViewerBase::renderingTraversals()
{
。。。
	Cameras cameras;
    getCameras(cameras);

    Contexts::iterator itr;

    bool doneMakeCurrentInThisThread = false;

    if (_endDynamicDrawBlock.valid())
    {
        _endDynamicDrawBlock->reset();
    }

    // dispatch the rendering threads
    if (_startRenderingBarrier.valid()) _startRenderingBarrier->block();
。。。
}

part4 遍历所有摄像机的渲染器(Renderer),执行Renderer::cull场景筛选的操作!

void ViewerBase::renderingTraversals()
{
。。。
    // reset any double buffer graphics objects
    for(Cameras::iterator camItr = cameras.begin();
        camItr != cameras.end();
        ++camItr)
    {
        osg::Camera* camera = *camItr;
        Renderer* renderer = dynamic_cast<Renderer*>(camera->getRenderer());
        if (renderer)
        {
        //条件一判断是否使用图形线程来执行场景的筛选;条件二是判断是否设置了相机线程来cull,更多详见part5
            if (!renderer->getGraphicsThreadDoesCull() && !(camera->getCameraThread()))
            {
            	//只有DrawThreadPerContext将执行renderer->cull()
            	//SingleThreaded和CullDrawThreadPerContext的getGraphicsThreadDoesCull为true,在runOperations将调用cull_draw
            	//CullThreadPerCameraDrawThreadPerContext则用相机线程来cull
                renderer->cull();
            }
        }
    }
。。。
}

part5 遍历所有的图形设备,设置渲染上下文(使用ViewerBase::makeCurrent)并执行GraphicsContext::runOperations,实现场景绘制的操作!

void ViewerBase::renderingTraversals()
{
。。。
    for(itr = contexts.begin();
        itr != contexts.end() && !_done;
        ++itr)
    {
        if (!((*itr)->getGraphicsThread()) && (*itr)->valid())
        {
            doneMakeCurrentInThisThread = true;
            makeCurrent(*itr);
            (*itr)->runOperations();
        }
    }
。。。
}

part5.1 osg::GraphicsContext::runOperations()

执行过程如下:

  • 获取场景中所有注册的摄像机(包括主摄像机和从摄像机组),对它们执行排序,排序的原则根据摄像机的渲染顺序而定,可以通过Camera::setRenderOrder进行设置。设置为PRE_RENDER级别的摄像机排序在最前,而POST_RENDER级别的摄像机排序在最后;同一级别的摄像机根据setRenderOrder函数中传入的整数设置先后顺序,排序数较小的摄像机在前。

对主相机设置意义不大,因为都是以主相机为标准来排序。

  • 依次遍历排序过的各个摄像机,执行其渲染器Renderer的operator()操作,它有一个传入参数,即当前的GraphicsContext图形设备。这个重载的操作符实质上执行了场景在该图形设备中的绘制工作,因此前面的排序工作将决定哪个摄像机的内容先被绘制出来。

Renderer类成员函数operator()的工作仅仅是判断是否使用图形线程来执行场景的筛选(根据Renderer::_graphicsThreadDoesCull变量的值)。
在这里插入图片描述
结合part4可知,只有CullDrawThreadPerContext执行cull_draw,剩下三种模式都执行draw。

  • 遍历GraphicsContext::_operations队列中的各个Operation对象,执行其operator()操作。

我们可以重写自己的Operation派生对象,并通过GraphicsContext::add将其添加到图形设备的执行队列(图形设备线程)中,从而实现自己定义的OpenGL绘图功能(由于在执行runOperations函数之前已经执行了图形设备的makeCurrent函数,因此这里不必考虑渲染上下文的设置问题)。

void GraphicsContext::runOperations()
{
    // sort the cameras into order
    typedef std::vector<Camera*> CameraVector;
    CameraVector camerasCopy;
    std::copy(_cameras.begin(), _cameras.end(), std::back_inserter(camerasCopy));
    std::sort(camerasCopy.begin(), camerasCopy.end(), CameraRenderOrderSortOp());

    for(CameraVector::iterator itr = camerasCopy.begin();
        itr != camerasCopy.end();
        ++itr)
    {
        osg::Camera* camera = *itr;
        if (camera->getRenderer()) (*(camera->getRenderer()))(this);
    }

    for(GraphicsOperationQueue::iterator itr = _operations.begin();
        itr != _operations.end();
        )
    {
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_operationsMutex);
            _currentOperation = *itr;

            if (!_currentOperation->getKeep())
            {
                itr = _operations.erase(itr);

                if (_operations.empty())
                {
                    _operationsBlock->set(false);
                }
            }
            else
            {
                ++itr;
            }
        }

        if (_currentOperation.valid())
        {
            // OSG_INFO<<"Doing op "<<_currentOperation->getName()<<" "<<this<<std::endl;

            // call the graphics operation.
            (*_currentOperation)(this);

            {
                OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_operationsMutex);
                _currentOperation = 0;
            }
        }
    }
}

part6 再次遍历所有的图形设备,执行双缓存交换操作(GraphicsContext::swapBuffers),熟悉图形编程的朋友一看便知,这是避免动态绘图时产生闪烁的重要步骤。

void ViewerBase::renderingTraversals()
{
。。。
     // OSG_NOTICE<<"Joing _endRenderingDispatchBarrier block "<<_endRenderingDispatchBarrier.get()<<std::endl;

    // wait till the rendering dispatch is done.
    if (_endRenderingDispatchBarrier.valid()) _endRenderingDispatchBarrier->block();

    for(itr = contexts.begin();
        itr != contexts.end() && !_done;
        ++itr)
    {
        if (!((*itr)->getGraphicsThread()) && (*itr)->valid())
        {
            doneMakeCurrentInThisThread = true;
            makeCurrent(*itr);
            (*itr)->swapBuffers();
        }
    }
。。。
}

闪烁原因:
在这里插入图片描述

part7 遍历视景器中的场景,告知分页数据库更新已经结束(DatabasePager::signalEndFrame)

void ViewerBase::renderingTraversals()
{
。。。
      for(Scenes::iterator sitr = scenes.begin();
        sitr != scenes.end();
        ++sitr)
    {
        Scene* scene = *sitr;
        if (!scene) continue;

        osgDB::DatabasePager* dp = scene->getDatabasePager();
        if (dp) dp->signalEndFrame();

        osgDB::ImagePager* ip = scene->getImagePager();
        if (ip) ip->signalEndFrame();
    }
。。。
}

part8 释放当前的渲染上下文(ViewerBase::releaseContext)

void ViewerBase::renderingTraversals()
{
。。。
     if (_releaseContextAtEndOfFrameHint && doneMakeCurrentInThisThread)
    {
        //OSG_NOTICE<<"Doing release context"<<std::endl;
        releaseContext();
    }
。。。
}

可知part4和part5的操作是整个函数的,以致整个OSG系统的重点。

五、osgViewer::Renderer::cull()

更多参考

osgViewer::Renderer为摄像机渲染场景的工作提供了一个公有接口。当我们向视景器(Viewer)添加一个新的摄像机(Camera)时,一个与摄像机相关联的渲染器(Renderer)也会被自动创建,用于为相机节点与渲染台之间提供一个公有接口。而当我们准备渲染场景时,与特定图形设备(GraphicsContext)相关联的摄像机也会自动调用其渲染器的相应函数,执行场景筛选与绘制等工作。

  • 实际上渲染器的工作是创建并初始化场景视图SceneView,每帧更新场景视图SceneView的数据((负责传递场景与用户数据)),以及执行裁减和绘制(SceneView才是真正执行者)

osgUtil::SceneView场景视图,osgViewer::Renderer负责调用场景视图(SceneView)各种功能的,就是新增加的这个渲染器(Renderer)了,是裁减和绘制的真正执行者。

  • 每个渲染器当中都会自动创建两个SceneView对象(Renderer::_sceneView[2]),从而实现了渲染后台双缓存的支持,不过SingleThreaded和CullDrawThreadPerContext环境下只使用到第一个场景视图(SceneView)。

从_availableQueue获取一个可用的场景视图,这个队列通常会保存有两个场景视图,以实现我们刚刚提到的渲染后台双缓存支持。

  • 注意这里的双缓存并不是显示双缓存,而是由于多线程渲染时可能存在上一帧的DRAW工作与下一帧的CULL冲突的问题,因此使用两个初始内容一致的场景视图(SceneView),每次需要开始CULL/DRAW时,取出一个sceneView对象并用来处理场景;下一帧则取出另一个,以保证当前正在绘制过程中的sceneView不会被重写。
void Renderer::cull()
{
    DEBUG_MESSAGE<<"cull()"<<std::endl;

    if (_done || _graphicsThreadDoesCull) return;

    // note we assume lock has already been acquired.
    //从_availableQueue获取一个可用的场景视图
    osgUtil::SceneView* sceneView = _availableQueue.takeFront();

    DEBUG_MESSAGE<<"cull() got SceneView "<<sceneView<<std::endl;

    if (sceneView)
    {
    	// 更新这个场景视图的全局渲染状态
    	//(根据场景主摄像机的StateSet渲染状态集,更新成员变量(SceneView::_globalStateSet,后文介绍状态树详解),状态量(osg::State),
    	//显示设置(osg:: DisplaySettings)
        updateSceneView(sceneView);

        // OSG_NOTICE<<"Culling buffer "<<_currentCull<<std::endl;
        osg::Stats* stats = sceneView->getCamera()->getStats();
        const osg::FrameStamp* fs = sceneView->getFrameStamp();
        unsigned int frameNumber = fs ? fs->getFrameNumber() : 0;

        // do cull traversal
        osg::Timer_t beforeCullTick = osg::Timer::instance()->tick();
//更新场景视图(SceneView)的融合距离(Fusion Distance)和筛选设置(CullSettings)。
//所谓融合距离,指得是双眼所在平面到视线汇聚点的距离,可以通过View::setFusionDistance函数传递给SceneView,
//通常应用于立体显示的场合。
        sceneView->inheritCullSettings(*(sceneView->getCamera()));
        //真正的场景筛选(裁减)工作的所在
        sceneView->cull();

		//记录场景筛选所耗费的时间,并保存到统计器(osg::Stats)中
        osg::Timer_t afterCullTick = osg::Timer::instance()->tick();

#if 0
        osg::State* state = sceneView->getState();
        if (sceneView->getDynamicObjectCount()==0 && state->getDynamicObjectRenderingCompletedCallback())
        {
            // OSG_NOTICE<<"Completed in cull"<<std::endl;
            state->getDynamicObjectRenderingCompletedCallback()->completed(state);
        }
#endif
        if (stats && stats->collectStats("rendering"))
        {
            DEBUG_MESSAGE<<"Collecting rendering stats"<<std::endl;

            stats->setAttribute(frameNumber, "Cull traversal begin time", osg::Timer::instance()->delta_s(_startTick, beforeCullTick));
            stats->setAttribute(frameNumber, "Cull traversal end time", osg::Timer::instance()->delta_s(_startTick, afterCullTick));
            stats->setAttribute(frameNumber, "Cull traversal time taken", osg::Timer::instance()->delta_s(beforeCullTick, afterCullTick));
        }

        if (stats && stats->collectStats("scene"))
        {
            collectSceneViewStats(frameNumber, sceneView, stats);
        }

        _drawQueue.add(sceneView);

    }

    DEBUG_MESSAGE<<"end cull() "<<this<<std::endl;
}

六、osgViewer::Renderer::draw ()

http://bbs.osgchina.org/forum.php?mod=viewthread&tid=580&highlight=OSG%D4%AD%B4%B4%BD%CC%B3%CC%A3%BA%D7%EE%B3%A4%B5%C4%D2%BB%D6%A1

七、osgUtil:: SceneView::cull ()

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值