【问题标题】:Doing readback from Direct3D textures and surfaces从 Direct3D 纹理和表面进行回读
【发布时间】:2010-09-12 07:32:24
【问题描述】:

我需要弄清楚如何将数据从 D3D 纹理和曲面返回到系统内存。做这些事情的最快方法是什么以及如何做?

另外,如果我只需要一个子区域,如何只读回该部分而不必将整个内容读回系统内存?

简而言之,我正在寻找关于如何将以下内容复制到系统内存的简明描述:

  1. 一个纹理
  2. 纹理子集
  3. 表面
  4. 表面子集
  5. D3DUSAGE_RENDERTARGET 纹理
  6. D3DUSAGE_RENDERTARGET 纹理的子集

这是 Direct3D 9,但也欢迎提供有关 D3D 较新版本的答案。

【问题讨论】:

    标签: directx textures gpgpu geometry-surface


    【解决方案1】:

    最复杂的部分是从视频内存(“默认池”)中的某个表面读取。这通常是渲染目标。

    让我们先来看看简单的部分:

    1. 从纹理读取与从该纹理的 0 级表面读取相同。见下文。
    2. 纹理子集也是如此。
    3. 从非默认内存池(“系统”或“托管”)中的表面读取只是锁定它并读取字节。
    4. 表面子集也是如此。只需锁定相关部分并阅读即可。

    所以现在我们留下了在显存中的表面(“默认池”)。这将是标记为渲染目标的任何表面/纹理,或您在默认池中创建的任何常规表面/纹理,或后备缓冲区本身。这里的复杂部分是你不能锁定它。

    简答:GetRenderTargetData D3D 设备上的方法。

    更长的答案(下面是代码的粗略轮廓):

    1. rt = 获取渲染目标表面(可以是纹理表面,也可以是后缓冲区等)
    2. 如果 rt 是多重采样(GetDesc,检查 D3DSURFACE_DESC.MultiSampleType),则:a) 创建另一个相同大小、相同格式但没有多重采样的渲染目标表面; b) 从 rt 到这个新表面的 StretchRect; c) rt = 这个新表面(即在这个新表面上继续)。
    3. off = 创建屏幕外平面(CreateOffscreenPlainSurface,D3DPOOL_SYSTEMMEM 池)
    4. device->GetRenderTargetData(rt, off)
    5. 现在 off 包含渲染目标数据。 LockRect(),读取数据,UnlockRect()就可以了。
    6. 清理

    下面是更长的答案(从我正在处理的代码库中粘贴)。这个不会开箱即用地编译,因为它使用了代码库其余部分的一些类、函数、宏和实用程序;但它应该让你开始。我还省略了大部分错误检查(例如,给定的宽度/高度是否超出范围)。我还省略了读取实际像素并可能将它们转换为合适的目标格式的部分(这很容易,但可能会变长,具体取决于您想要支持的格式转换数量)。

    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 非常相似。

    上面的代码读回整个表面。如果你想有效地阅读其中的一部分,那么我认为最快的方法大致是:

    1. 创建一个具有所需大小的默认池表面。
    2. 将原始曲面的一部分拉伸到较小的曲面。
    3. 照常处理较小的。

    事实上,这与上面处理多采样表面的代码非常相似。如果您只想获取多采样表面的一部分,我认为可以进行多采样解析并在一个 StretchRect 中获取其中的一部分。

    编辑:删除了实际读取像素和格式转换的代码。和问题没有直接关系,代码比较长。

    编辑:更新以匹配已编辑的问题。

    【讨论】:

    • 谢谢。这确实为我指明了正确的道路。在发布后不久,我确实在 MSDN 中找到了您不能在 D3DUSAGE_RENDERTARGET 上使用 LockRect 并且必须改用 GetRenderTargetData 的评论。真可惜。不过,如果有一个更简洁、更简洁的代码示例,那就太好了。 :-)
    • 没有像素复制代码肯定好多了。用实际的 D3D 名称替换非 D3D 类型 SurfacePointer 怎么样? LPDIRECT3DSurface9 或 IDirect3DSurface9*
    • 如果我想从 DEFAULT 池中获取纹理,但它不是 rendertarget,该怎么办。锁定不起作用,我不能将 GetRenderTargetData() 用于非渲染目标表面。有时可以创建中间渲染目标,但如果原始纹理以某种格式(如 D3DFORMAT_L8) - 我无法以这种格式创建渲染目标。所以看起来不可能得到这样的纹理。我说的对吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 1970-01-01
    • 2013-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多