从Direct3D纹理和表面进行回读
我需要弄清楚如何从D3D纹理和表面获取数据回到系统内存。做这些事情的最快方法是什么?如何做?
同样,如果我只需要一个subrect,那么又怎能只读那部分而不需要将整个内容读回系统内存呢?
简而言之,我正在寻找有关如何将以下内容复制到系统内存的简要说明:
- 质地
- 纹理的子集
- 表面
- 表面的一个子集
- D3DUSAGE_RENDERTARGET纹理
- D3DUSAGE_RENDERTARGET纹理的子集
这是Direct3D 9,但有关D3D较新版本的答案也将不胜感激。
解决方案
最涉及的部分是从视频内存("默认池")中的某个表面读取。这通常是渲染目标。
让我们首先获得简单的部分:
- 从纹理读取与从纹理的0级表面读取相同。见下文。
- 对于纹理的子集相同。
- 从非默认内存池("系统"或者"托管")中的表面读取数据只是将其锁定并读取字节。
- 对于表面子集相同。只需锁定相关部分并阅读即可。
因此,现在我们将剩余的曲面保留在视频内存中("默认池")。这将是标记为渲染目标的任何表面/纹理,或者是我们在默认池中创建的任何常规表面/纹理,或者是后缓冲区本身。这里的复杂部分是我们无法锁定它。
简短的答案是:D3D设备上的GetRenderTargetData方法。
更长的答案(下面是代码的粗略概述):
- rt =获取渲染目标表面(可以是纹理的表面或者后缓冲等)
- 如果rt被多重采样(GetDesc,请检查D3DSURFACE_DESC.MultiSampleType),则:a)创建另一个具有相同大小,相同格式但没有多重采样的渲染目标表面; b)从rt到这个新表面的StretchRect; c)rt =这个新表面(即在这个新表面上进行)。
- off =创建屏幕外平面(CreateOffscreenPlainSurface,D3DPOOL_SYSTEMMEM池)
- device-> GetRenderTargetData(rt,off)
- 现在关闭包含渲染目标数据。 LockRect(),读取数据,在其上解锁。
- 清理
甚至有更长的答案(从我正在研究的代码库中粘贴)。这不会立即进行编译,因为它使用了其余代码库中的某些类,函数,宏和实用程序。但这应该可以入门。我也省略了大多数错误检查(例如,给定的宽度/高度是否超出范围)。我还省略了读取实际像素并可能将其转换为合适的目标格式的部分(这很容易,但可能会很长,具体取决于我们要支持的格式转换数)。
bool GfxDeviceD3D9::ReadbackImage( /* params */ ) { HRESULT hr; IDirect3DDevice9* dev = GetD3DDevice(); SurfacePointer renderTarget; hr = dev->GetRenderTarget( 0, &renderTarget ); if( !renderTarget || FAILED(hr) ) return false; D3DSURFACE_DESC rtDesc; renderTarget->GetDesc( &rtDesc ); SurfacePointer resolvedSurface; if( rtDesc.MultiSampleType != D3DMULTISAMPLE_NONE ) { hr = dev->CreateRenderTarget( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DMULTISAMPLE_NONE, 0, FALSE, &resolvedSurface, NULL ); if( FAILED(hr) ) return false; hr = dev->StretchRect( renderTarget, NULL, resolvedSurface, NULL, D3DTEXF_NONE ); if( FAILED(hr) ) return false; renderTarget = resolvedSurface; } SurfacePointer offscreenSurface; hr = dev->CreateOffscreenPlainSurface( rtDesc.Width, rtDesc.Height, rtDesc.Format, D3DPOOL_SYSTEMMEM, &offscreenSurface, NULL ); if( FAILED(hr) ) return false; hr = dev->GetRenderTargetData( renderTarget, offscreenSurface ); bool ok = SUCCEEDED(hr); if( ok ) { // Here we have data in offscreenSurface. D3DLOCKED_RECT lr; RECT rect; rect.left = 0; rect.right = rtDesc.Width; rect.top = 0; rect.bottom = rtDesc.Height; // Lock the surface to read pixels hr = offscreenSurface->LockRect( &lr, &rect, D3DLOCK_READONLY ); if( SUCCEEDED(hr) ) { // Pointer to data is lt.pBits, each row is // lr.Pitch bytes apart (often it is the same as width*bpp, but // can be larger if driver uses padding) // Read the data here! offscreenSurface->UnlockRect(); } else { ok = false; } } return ok; }
上面代码中的" SurfacePointer"是指向COM对象的智能指针(它在分配或者析构函数时释放对象)。大大简化了错误处理。这与Visual C ++中的" _comptr_t"事物非常相似。
上面的代码读回整个表面。如果我们想高效地阅读其中的一部分,那么我认为最快的方法大致是:
- 创建一个具有所需大小的默认池表面。
- 从原始表面的一部分到较小的表面拉伸。
- 与较小的一样正常进行。
实际上,这与上面的代码处理多重采样表面非常相似。我想,如果只想获取多采样表面的一部分,则可以进行多采样解析并将其包含在一个StretchRect中。
编辑:删除了一段实际读取像素和格式转换的代码。与问题没有直接关系,并且代码很长。
编辑:已更新以匹配已编辑的问题。