最近一直在用 Ogre 做项目,为了提高描画效率问题大大头痛了一番。
然后用 GPA 和 PerfHUD 反复检查,意外的发现每帧有很多的资源创建调用。
很莫名啊~
所有资源都在初始化的时候完成了啊,怎么会在运行期每帧都有呢~
检查了一百遍啊一百遍,最后发现原来 Ogre RenderSystem 的 D3D9 实现又一次忽悠了我~哭!!
Ogre在 DepthStencil 的处理上很保守,基本不开放任何相关接口。
这意味着,你只能任由它来管理你的 DepthStencil,而不能自己指定某RenderTarget的 DepthStencilBuffer。对于某些懒人来说也可以算是个福音,不过我更倾向于在提供自动管理的基础上,也能提供自定义的途径。
如果这个自动管理很完美也就算了,可惜很不幸的,在 D3D9RenderSystem 的实现里存在一个很严重的隐患。
为了管理不同格式的DepthStencilBuffer,D3D9RenderSystem 使用了一个名为 mZBufferHash 的 map 来存放所有的DepthStencilBuffer,采用的策略是,每次从map中查找符合格式和大小的 DepthStencilBuffer,如果没找到符合要求的结果,就进行创建过程。
实现部分的代码如下
ZBufferFormat zbfmt(dsfmt, multisample);
ZBufferHash::iterator i = mZBufferHash.find(zbfmt);
if(i != mZBufferHash.end())
{
/// Check if size is larger or equal
if(i->second.width >= width && i->second.height >= height)
{
surface = i->second.surface;
}
else
{
/// If not, destroy current buffer
i->second.surface->Release();
mZBufferHash.erase(i);
}
}
if(!surface)
{
/// If not, create the depthstencil surface
HRESULT hr = mpD3DDevice->CreateDepthStencilSurface(
width,
height,
dsfmt,
multisample,
NULL,
TRUE, // discard true or false?
&surface,
NULL);
if(FAILED(hr))
{
String msg = DXGetErrorDescription9(hr);
OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR, "Error CreateDepthStencilSurface : " + msg, "D3D9RenderSystem::_getDepthStencilFor" );
}
/// And cache it
ZBufferRef zb;
zb.surface = surface;
zb.width = width;
zb.height = height;
mZBufferHash[zbfmt] = zb;
}
return surface;
在这里,它认为如果原有的Buffer在宽度和高度上都能满足要求,则直接使用即有的Buffer,否则,则销毁旧的Buffer,并采用新大小创建新的Buffer。
基本思路没有问题,问题在于判断条件和创建大小之间的关系:
if(i->second.width >= width && i->second.height >= height)
/// If not, create the depthstencil surface
HRESULT hr = mpD3DDevice->CreateDepthStencilSurface(
width,
height,
假设存在如下情况:
RenderTargetA 请求 DepthBuffer 大小为 1360 X 768 ,作为 主画面
RenderTargetB 请求 DepthBuffer 大小为 1024 X 1024,作为 ShadowMap,
在这种情况下, 每次切换到描画 RenderTargetA的时候,因为 width 为 1360 > 1024 ,所以会重新创建DepthBuffer,大小为 1360 X 768,当再切回 RenderTargetB描画时, 因为 Height 为 1024 > 768,又要重新创建DepthBuffer,大小为 1024 X 1024。结果每帧都要创建两次资源。
这还不是最坏情况。
我们都知道,游戏如果需要好的效果,那么会大量使用后期。对于 Ogre 来说,就是 Compositor 系统,这里面大量采用中间过渡 RenderTarget 的方式来实现效果。这些 RenderTarget 的切换会很频繁(比如 HDR)。如此下去,我们就必须面对大量重复创建 DepthBuffer 的风险。
这都是拜此 DepthStencilBuffer管理方式所赐。
其实修改也很方便,每次创建 DepthStencilBuffer 的时候,重新计算大小,采用最大的维度来创建就可以了。
基本实现如下,在删除旧有的Buffer之时,同时调整新Buffer的大小,以兼容新旧两个Buffer:
if( i->second.width > width )
{
width = i->second.width;
}
if( i->second.height > height )
{
height = i->second.height;
}
//
i->second.surface->Release();
mZBufferHash.erase(i);
至于如果你很有XX情节,一定要 2^n的话,自我调整一下也不是很难的事情。(玩笑,这基本都看显卡能不能适应啦,哈哈)