天天看點

osg視窗的建立(二)

本文接着osg視窗的建立(一)中關于GraphicsContext的内容繼續閱讀。

還是先看看GraphicsContext中的繼承圖

osg視窗的建立(二)

由于是在Windows平台下分析視窗的建立,是以本文選取Win32中的實作來探讨,其他平台的原理類似,隻是在細節上稍有不同。

前文說到了 isRealized的實作,我們在看看實作過程:

GraphicsContext的isRealized實作如下:

inline bool isRealized() const 
 { 
 	return isRealizedImplementation();
 } 
           

isRealizedImplementation函數在GraphicsContext中是一個純虛函數,在GraphicsWindow中的實作僅僅列印出一個提示資訊(等同于沒實作),真正的實作在GraphicsWindowWin32之中,實作如下:

virtual bool GraphicsWindowWin32::isRealizedImplementation() const 
{ 
	return _realized; 
}
           

僅僅是傳回一個辨別,于是我們有理由認為在 realize函數中設定了該辨別。(實際上确實是這樣的,後續再realize代碼中再分析)

繼續看frame中的實作:

if (!isRealized())
        {
            realize();
        }
           

可以看到如果沒有設定好視窗(isRealized傳回值為false),那麼就調用 realize函數來設定視窗,事實上osg的視窗建立過程就是在realize函數中完成的,在代碼調試中到斷點越過realize函數體,可以在工作列上看到新的視窗出現。realize的詳細實作如下:

1. realize在ViewerBase中是純虛函數

2. realize的具體實作在osgViewer::Viewer類中實作:

void Viewer::realize()
{
    Contexts contexts;
    getContexts(contexts);

    if (contexts.empty())
    {
        OSG_INFO<<"Viewer::realize() - No valid contexts found, setting up view across all screens."<<std::endl;
        const char* ptr = 0;
        if ((ptr = getenv("OSG_CONFIG_FILE")) != 0)
        {
            readConfiguration(ptr);
        }
        else
        {
            int screenNum = -1;
            if ((ptr = getenv("OSG_SCREEN")) != 0)
            {
                if (strlen(ptr)!=0) screenNum = atoi(ptr);
                else screenNum = -1;
            }

            int x = -1, y = -1, width = -1, height = -1;
            if ((ptr = getenv("OSG_WINDOW")) != 0)
            {
                std::istringstream iss(ptr);
                iss >> x >> y >> width >> height;
            }

            if (width>0 && height>0)
            {
                if (screenNum>=0) setUpViewInWindow(x, y, width, height, screenNum);
                else setUpViewInWindow(x,y,width,height);
            }
            else if (screenNum>=0)
            {
                setUpViewOnSingleScreen(screenNum);
            }
            else
            {
                setUpViewAcrossAllScreens();
            }
        }

        getContexts(contexts);
    }

    if (contexts.empty())
    {
        OSG_NOTICE<<"Viewer::realize() - failed to set up any windows"<<std::endl;
        _done = true;
        return;
    }

    unsigned int maxTexturePoolSize = osg::DisplaySettings::instance()->getMaxTexturePoolSize();
    if (_camera->getDisplaySettings()) maxTexturePoolSize = std::max(maxTexturePoolSize, _camera->getDisplaySettings()->getMaxTexturePoolSize());
    if (_displaySettings.valid()) maxTexturePoolSize = std::max(maxTexturePoolSize, _displaySettings->getMaxTexturePoolSize());

    unsigned int maxBufferObjectPoolSize = osg::DisplaySettings::instance()->getMaxBufferObjectPoolSize();
    if (_displaySettings.valid()) maxBufferObjectPoolSize = std::max(maxBufferObjectPoolSize, _displaySettings->getMaxBufferObjectPoolSize());
    if (_camera->getDisplaySettings()) maxBufferObjectPoolSize = std::max(maxBufferObjectPoolSize, _camera->getDisplaySettings()->getMaxBufferObjectPoolSize());

    for(Contexts::iterator citr = contexts.begin();
        citr != contexts.end();
        ++citr)
    {
        osg::GraphicsContext* gc = *citr;

        // set the pool sizes, 0 the default will result in no GL object pools.
        gc->getState()->setMaxTexturePoolSize(maxTexturePoolSize);
        gc->getState()->setMaxBufferObjectPoolSize(maxBufferObjectPoolSize);

        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::instance()->setStartTick();

    // pass on the start tick to all the associated event queues
    setStartTick(osg::Timer::instance()->getStartTick());

    setUpThreading();

    if (osg::DisplaySettings::instance()->getCompileContextsHint())
    {
        int numProcessors = OpenThreads::GetNumberOfProcessors();
        int processNum = 0;

        for(unsigned int i=0; i<= osg::GraphicsContext::getMaxContextID(); ++i)
        {
            osg::GraphicsContext* gc = osg::GraphicsContext::getOrCreateCompileContext(i);

            if (gc)
            {
                gc->createGraphicsThread();
                gc->getGraphicsThread()->setProcessorAffinity(processNum % numProcessors);
                gc->getGraphicsThread()->startThread();

                ++processNum;
            }
        }
    }
}
           

函數體的實作代碼相對較長,分段進行分析:

1. 首先收集目前視景器Viewer中相機的渲染上下文,代碼和isRealized中的實作一樣

2. 如果不存在渲染上下文,我們進入第一段if判斷中,可以看到主要進行的工作如下:

  • 讀取環境變量OSG_CONFIG_FILE,如果設定了OSG_CONFIG_FILE指向一個.view字尾的檔案,可以建立該檔案描述的視窗,格式如下:(在openscenegraph-data目錄中的configuration目錄下有.view類型的檔案)
osgViewer::Viewer
{
    setUpViewInWindow 100 200 600 400 0
}
           

如果設定了OSG_CONFIG_FILE環境變量,但是環境變量所對應的檔案解析有錯,那麼整個程式就會退出。

  • 如果沒有設定OSG_CONFIG_FILE環境變量,那麼程式會檢查 OSG_SCREEN和OSG_WINDOW這兩個環境變量的值,OSG_SCREEN對應視窗的個數(值是一個整型數)OSG_WINDOW對應視窗的大小和位置,格式是(x, y, w, h) 分别是視窗左上角點坐标(x, y)以及視窗的長和寬(w, h),具體來說是以下情況:
Screen取值      Window中width和height取值 視窗設定函數
大于或等于0 長和寬都大于0 setUpViewInWindow(x, y, width, height, screenNum)
小于0 長和寬都大于0 setUpViewInWindow(x, y, width, height)
大于0 長和寬為0或者負值 setUpViewOnSingleScreen(screenNum)

其他情況下視窗設定函數使用setUpViewAcrossAllScreens()

當視窗設定完成之後,osg會再次調用getContexts(contexts) 搜集目前可用的裝置渲染上下文,如果這時候還沒有視窗産生,也就是說設定視窗的各種方式都失敗了,那麼程式就會退出,如果成功,那麼會針對已經常見的視窗進行一些設定,完成後續的操作

這三個函數的實作如下所示:

void View::setUpViewAcrossAllScreens()
{
    apply(new osgViewer::AcrossAllScreens());
}

void View::setUpViewInWindow(int x, int y, int width, int height, unsigned int screenNum)
{
    apply(new osgViewer::SingleWindow(x, y, width, height, screenNum));
}

void View::setUpViewOnSingleScreen(unsigned int screenNum)
{
    apply(new osgViewer::SingleScreen(screenNum));
}
           

這桑函數調用了osgViewer命名空間中的三個視窗配置類,它們的關系如下圖所示:

osg視窗的建立(二)

基類osgViewer::ViewConfig提供了一個虛函數 virtual void  configure (osgViewer::View &) const,在子類中通過實作該函數來配置渲染的視窗大小和位置。

SingleScreen在實作過程中會調用SingleWindow來完成設定,AcrossAllScreen在未設定視窗的大小和顯示屏數目的時候會被調用(一般就是我們預設使用osg的場景),那麼它會調用SingleWindow來設定,在設定過程中會讀取外接顯示器的分辨率,産生一個全屏的視窗,代碼如下:

//來自于osgViewer::SingleWindow::configure
    if (traits->width<=0 || traits->height<=0 ) 
    {
        osg::GraphicsContext::ScreenIdentifier si;
        si.readDISPLAY();

        // displayNum has not been set so reset it to 0.
        if (si.displayNum<0) si.displayNum = 0;

        si.screenNum = _screenNum;

        unsigned int width, height;
        wsi->getScreenResolution(si, width, height);
        if (traits->width<=0) traits->width = width;
        if (traits->height<=0) traits->height = height;
    }
           

下面繼續realize後面的代碼,在完成視窗參數的設定之後,後面的代碼無非是一些關于OpenGL緩沖區對象的設定等,暫時與視窗無關。在此之後進入到for循環之中,這裡面就是視窗設定的内容了,實作的内容是對所有的相機中的裝置上下文進行檢查,并真正的産生視窗,視窗的産生調用在

gc->realize();
           

我們檢視一下它的實作:

bool GraphicsContext::realize()
{
    if (realizeImplementation())
    {
        return true;
    }
    else
    {
        return false;
    }
}
           

實作中調用了另一個函數,繼續往下看,它與isRealizedImplementation函數類似,在GraphicsContext中是一個純虛函數,在視窗系統中實作相應的代碼:

bool GraphicsWindowWin32::realizeImplementation()
{
    if (_realized) return true;

    if (!_initialized)
    {
        init();
        if (!_initialized) return false;
    }

    if (_traits.valid() && (_traits->sharedContext.valid() || _traits->vsync || _traits->swapGroupEnabled))
    {
        // make context current so we can test capabilities and set up context sharing
        struct RestoreContext
        {
            RestoreContext()
            {
                _hdc = wglGetCurrentDC();
                _hglrc = wglGetCurrentContext();
            }
            ~RestoreContext()
            {
                wglMakeCurrent(_hdc,_hglrc);
            }
        protected:
            HDC      _hdc;
            HGLRC    _hglrc;
        } restoreContext;

        _realized = true;
        bool result = makeCurrent();
        _realized = false;

        if (!result)
        {
            return false;
        }

        // set up sharing of contexts if required
        GraphicsHandleWin32* graphicsHandleWin32 = dynamic_cast<GraphicsHandleWin32*>(_traits->sharedContext.get());
        if (graphicsHandleWin32)
        {
            if (!wglShareLists(graphicsHandleWin32->getWGLContext(), getWGLContext()))
            {
                reportErrorForScreen("GraphicsWindowWin32::realizeImplementation() - Unable to share OpenGL context", _traits->screenNum, ::GetLastError());
                return false;
            }
        }

        // if vysnc should be on then enable it.
        if (_traits->vsync)
        {
            setSyncToVBlank(_traits->vsync);
        }

        // If the swap group is active then enable it.
        if (_traits->swapGroupEnabled)
        {
            setSwapGroup(_traits->swapGroupEnabled, _traits->swapGroup, _traits->swapBarrier);
        }
    }

    if (_ownsWindow)
    {
        //
        // Bring the window on top of other ones (including the taskbar if it covers it completely)
        //
        // NOTE: To cover the taskbar with a window that does not completely cover it, the HWND_TOPMOST
        // Z-order must be used in the code below instead of HWND_TOP.
        // @todo: This should be controlled through a flag in the traits (topMostWindow)
        //

        if (!::SetWindowPos(_hwnd,
                            HWND_TOP,
                            _windowOriginXToRealize,
                            _windowOriginYToRealize,
                            _windowWidthToRealize,
                            _windowHeightToRealize,
                            SWP_SHOWWINDOW))
        {
            reportErrorForScreen("GraphicsWindowWin32::realizeImplementation() - Unable to show window", _traits->screenNum, ::GetLastError());
            return false;
        }

        if (!::UpdateWindow(_hwnd))
        {
            reportErrorForScreen("GraphicsWindowWin32::realizeImplementation() - Unable to update window", _traits->screenNum, ::GetLastError());
            return false;
        }
    }

    _realized = true;

    // make sure the event queue has the correct window rectangle size and input range
    getEventQueue()->syncWindowRectangleWithGraphcisContext();

    return true;
}
           

可以看到視窗的産生是在這個函數中,在Win32程式設計中的常用函數UpdateWindow與SetWindowPos就是在這裡調用的,SetWindowPos最終将視窗Show出來,讓我們在系統中可以看到建立好的場景。