从Direct3D纹理和曲面进行回读

时间:2008-09-23 09:40:11

标签: directx textures gpgpu geometry-surface

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

另外如果我只需要一个subrect,那么如何只读回那部分而不必将整个内容读回系统内存?

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

  1. 纹理
  2. 纹理
  3. 子集
  4. 表面
  5. 表面
  6. 子集
  7. D3DUSAGE_RENDERTARGET纹理
  8. D3DUSAGE_RENDERTARGET纹理的子集
  9. 这是Direct3D 9,但关于D3D的新版本的答案也将受到赞赏。

1 个答案:

答案 0 :(得分:29)

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

让我们先得到简单的部分:

  1. 从纹理读取与从该纹理的0级表面读取相同。见下文。
  2. 与纹理子集相同。
  3. 从非默认内存池(“system”或“managed”)中的表面读取只是锁定它并读取字节。
  4. 表面子集相同。只需锁定相关部分并阅读即可。
  5. 所以现在我们已经离开了视频内存中的表面(“默认池”)。这将是标记为渲染目标的任何表面/纹理,或您在默认池中创建的任何常规表面/纹理,或后备缓冲区本身。这里的复杂部分是你无法锁定它。

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

    更长的答案(下面的代码的大致轮廓):

    1. rt =获取渲染目标表面(这可以是纹理的表面,或后退缓冲等)。
    2. 如果 rt 是多重采样的(GetDesc,请检查D3DSURFACE_DESC.MultiSampleType),则:a)创建另一个相同大小,相同格式但没有多重采样的渲染目标表面; b)StretchRect从 rt 进入这个新的表面; c) rt =这个新表面(即在这个新表面上继续)。
    3. 关闭 =创建屏幕外平面(CreateOffscreenPlainSurface,D3DPOOL_SYSTEMMEM池)
    4. device-> GetRenderTargetData( rt 关闭
    5. 现在关闭包含渲染目标数据。 LockRect(),读取数据,UnlockRect()就可以了。
    6. 清理
    7. 更长的答案(从我正在处理的代码库中粘贴)如下。这个不会开箱即用,因为它使用了代码库其余部分的一些类,函数,宏和实用程序;但它应该让你开始。我还省略了大多数错误检查(例如,给定的宽度/高度是否超出范围)。我还省略了读取实际像素的部分,并可能将它们转换为合适的目标格式(这非常简单,但可能会变长,具体取决于您要支持的格式转换次数)。

      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. StretchRect从原始表面的一部分到较小的表面。
      3. 与较小的一样正常进行。
      4. 实际上,这与上面处理多采样表面的代码非常相似。如果你想获得多采样表面的一部分,我认为你可以在一个StretchRect中进行多重采样解析并获得它的一部分。

        修改:删除了实际读取像素和格式转换的代码段。与问题没有直接关系,代码很长。

        修改:已更新以匹配已修改的问题。