概述
对于一个已有的图形应用,利用Equalizer对其进行扩展的过程通常需要经过以下步骤:
- 在eq::Window所创建的窗口中对图形应用进行OpenGL render constext的初始化;
- 利用eq::Window实现应用的Scene Graph数据的本地共享;
- 在eq::Channel中渲染图形应用的场景;
- 利用eq所提供的分布式数据共享方案实现节点间的静态与动态数据共享与同步;
- 利用eq::ConfigEvent实现系统内图形应用的消息响应;
需要注意的是,由于Equalizer采用自动创建的窗口与线程以便于其对系统进行高效的管理,因此在与图形应用结合时需要小心避免二者间的线程冲突。这曾经困扰了比较长的时间。
关于程序的框架可以参考Equalizer0.4版本中的eqPly与eqHello示例,这里说的不清楚的可以从“Equalizer Programming Guide”找到更详细的解释。
Ogre与Equalizer的结构对比
作为一个完整的三维渲染引擎,Ogre可以自动创建窗口并管理窗口与视口资源,也可以通过SceneManager对象实现对场景资源的管理,而Equalizer同样要创建窗口,并要求图形应用在其所创建的窗口中渲染。
同时,一个Ogre应用通常会在主循环中周期性的执行场景渲染,而这在Equalzier中由服务器通过事件触发各个渲染客户端来执行。
一步步扩展
1.复制简单的应用框架
随着版本的不断更新,Equalizer越来越重视对图形应用进行快速的扩展过程进行简化。在0.4版本中增加的eqHello示例只用了150行代码就实现了一个可以在各种系统配置下运行的简单应用。
对于一般的应用而言,eqHello框架中的大部分内容都可以不做修改的直接应用,而其中真正需要关注的步骤是继承eq::NodeFactory类,并通过重载其中的createXXX函数对其它类进行实例化,真正的图形应用就是在这些实例对象中实现的。
这些实体类包括:
-
eq::Config,管理系统配置并驱动事件响应;
-
eq::Node,对应独立的应用进程;
-
eq::Pipe,对应系统的显卡,并具备独立线程;
-
eq::Window,对应操作系统的窗口,并实现窗口间的数据共享;
-
eq::Channel,对应渲染视口,根据系统配置进行动态调节;
2.在eq::Node中初始化Ogre::Root
对于一个Ogre应用来说,首先需要创建的是Ogre::Root对象,这是整个Ogre应用的基础,并且每个进程中只能有一个实例。而在Equalizer中,每个应用客户端(app client)都是一个独立进程,同时会创建惟一的一个节点对象,因此可以把Root对象放在eq::Node对象中实例化。
eoNode.h 的部分代码:
{
……
protected:
virtual bool configInit( const uint32_t initID )
{
m_pRoot = new OgreRoot( "plugins.cfg",
"../../common/ogre.cfg",
"Ogre.log");
……
return eq::Node::configInit( initID );
}
OgreRoot* m_pRoot;
……
} ;
OgreRoot.h的部分代码如下:
{
public:
OgreRoot(const Ogre::String& pluginFileName = "plugins.cfg",
const Ogre::String& configFileName = "ogre.cfg",
const Ogre::String& logFileName = "Ogre.log");
protected:
void setupResources(void);
} ;
OgreRoot::OgreRoot ( const Ogre::String & pluginFileName,
const Ogre::String & configFileName,
const Ogre::String & logFileName)
: Ogre::Root(pluginFileName, configFileName, logFileName)
{
setupResources();
restoreConfig();
initialise(true);
}
OgreRoot.cpp的部分代码如下:
void OgreRoot::setupResources( void )
{
// Load resource paths from config file
Ogre::ConfigFile cf;
cf.load("../../common/resources.cfg");
// Go through all sections & settings in the file
Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
Ogre::String secName, typeName, archName;
while (seci.hasMoreElements())
{
secName = seci.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator i;
for (i = settings->begin(); i != settings->end(); ++i)
{
typeName = i->first;
archName = i->second;
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
archName, typeName, secName);
}
}
}
按理说Ogre::RenderWindow对象将要在后面的eq::Window中创建,此时对Ogre::Root对象进行初始化时就不需要自动创建新的窗口,但是由于在我的应用中这种情况会导致窗口初始化失败,所用在这里我仍然采用Ogre::Root::initialize(true),即由Ogre自动创建一个窗口,由其先完成对OpenGL的初始化,从而使其它窗口能够正确创建,等以后有时间在解决这个临时方案。
3.在eq::Window中初始化Ogre资源
Ogre的环境初始化完成后,就需要创建渲染窗口并完成资源的加载与初始化。由于在Equalizer中,每个eq::Window实例会自动创建一个窗口,因此无需让Ogre再重复创建,只需通过获得eq::Window的窗口句柄,利用Ogre::Root:: createRenderWindow就可以在该窗口基础上创建Ogre::RenderWindow对象。
窗口的初始化可以通过eq::Window::configInit函数中实现:
{
// create os specific on-screen window and
// initialize generic OpenGL state.
if( !eq::Window::configInit( initID ))
return false;
// attach a new ogre window and scene manager to eq::Window.
char name[32];
HWND handle = getWGLWindowHandle();
sprintf(name, "%i", (int) handle);
eo::Node* pNode = static_cast <eo::Node*> ( getNode() );
m_pOgreWindow = new EqOgreWindow( (Ogre::Root*)pNode->getRoot(), (unsigned int)handle, name );
// setup data to be shared in the pipe.
// the first window create all data and the mananger.
// other windows use the first window's manager.
eo::Pipe* pipe = static_cast< eo::Pipe*> ( getPipe() );
Window* firstWindow = static_cast< Window* >( pipe->getWindow( 0 ));
if( firstWindow == this )
{
m_pOgreWindow->go();
m_pOgreWindow->setEoConfig( (eo::Config*) getConfig() );
pipe->setOgreWindow(m_pOgreWindow);
}
else
m_pOgreWindow->go(pipe->getOgreWindow());
return true;
}
4.在eq::Channel中进行渲染
最后需要在eq::Channel中进行渲染,只需在eq::Channel::frameDraw函数中执行对应Ogre窗口的渲染函数即可。
{
eo::Window* pWin = (eo::Window*)getWindow();
eo::EqOgreWindow* pOgreWin = pWin->getOgreWindow();
if (pOgreWin != NULL)
{
// render one frame.
pOgreWin->render();
}
}