本文接着osg視窗的建立(一)中關于GraphicsContext的内容繼續閱讀。
還是先看看GraphicsContext中的繼承圖
由于是在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命名空間中的三個視窗配置類,它們的關系如下圖所示:
基類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出來,讓我們在系統中可以看到建立好的場景。