在Ogre中获取渲染结果可以使用RenderTarget的copyContentsToMemory方法, 例:
char* src = new char[mWindow->getWidth() * mWindow->getHeight() * 4]; Ogre::PixelBox* pixbox = new Ogre::PixelBox(mWindow->getWidth(), mWindow->getHeight(), 1, Ogre::PF_X8R8G8B8, src );mWindow->copyContentsToMemory(*pixbox, Ogre::RenderTarget::FB_AUTO);
mWindow为当前渲染窗口RenderWindow,如果使用RTT(渲染到纹理)也是同样的处理
需要注意的是导出格式应该是PF_X8R8G8B8,避免格式转换带来性能损耗
不幸的是,实际中发现这个方法出奇的慢
一个上百帧的场景竟然因为这一句话下降到了二三十帧
一般来说从GPU显存将数据复制到CPU内存是个很慢的过程,但这么慢也实在是太夸张了
查看copyContentsToMemory的实现,大概是下面的样子(做了简化):
void D3D9Device::copyContentsToMemory(const PixelBox &dst) { IDirect3DSurface9 *surface = NULL; D3DLOCKED_RECT lrect; mDevice->CreateOffscreenPlainSurface(width, height, format, D3DPOOL_SYSTEMMEM, surface, 0); IDirect3DSurface9 *backSurface; mDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,&backSurface); mDevice->GetRenderTargetData(backSurface, surface); surface->LockRect(&lrect, NULL, D3DLOCK_READONLY); memcpy(dst.data, lrect.pBits, dst.getWidth() * dst.getHeight()*4); surface->UnlockRect(); surface->Release(); tmp->Release(); }
大意是在内存(D3DPOOL_SYSTEMMEM)中创建离屏表面
然后将后缓冲中数据复制到该表面中(GetRenderTargetData)
最后锁定该表面获取内存指针完成数据复制
其中完成将数据从GPU显存复制到CPU内存的步骤为GetRenderTargetData
这么实现可以说没有任何问题,简直就是标准做法
测试了一下发现GetRenderTargetData方法用时为0毫秒,也就是说足够的快
实际瓶颈出在LockRect方法上,测试场景中每次调用用时达到30多毫秒
原则上来说,该表面在内存中创建,并没有在场景中使用,锁定不应该有任何消耗
直接使用D3D测试时,也发现该方法用时为0,实在想不通是什么原因
...
一番周折,最后想到一个非常规的解决办法:
仅在第一次创建离屏表面时锁定、解锁以获取数据区指针,之后直接利用该指针完成数据复制
这么做之所以成立是因为创建的离屏表面实际上也并不需要锁定
每次只要调用GetRenderTargetData将数据从GPU显存复制出来即可
使用Ogre的话,实现这个稍微有点麻烦,因为Ogre并不支持直接访问Direct3D
需要修改Ogre和RenderSystem_Direct3D9插件的代码
不过这是目前个人想到的解决这个问题的最好办法了