从 Direct3D 纹理和表面进行读回

发布于 2024-07-06 02:39:23 字数 518 浏览 13 评论 0原文

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

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

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

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

这是 Direct3D 9,但有关较新版本的 D3D 的答案也将不胜感激。

I need to figure out how to get the data from D3D textures and surfaces back to system memory. What's the fastest way to do such things and how?

Also if I only need one subrect, how can one read back only that portion without having to read back the entire thing to system memory?

In short I'm looking for concise descriptions of how to copy the following to system memory:

  1. a texture
  2. a subset of a texture
  3. a surface
  4. a subset of a surface
  5. a D3DUSAGE_RENDERTARGET texture
  6. a subset of a D3DUSAGE_RENDERTARGET texture

This is Direct3D 9, but answers about newer versions of D3D would be appreciated too.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

不语却知心 2024-07-13 02:39:23

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

让我们首先了解简单的部分:

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

现在我们已经在视频内存中留下了表面(“默认池”)。 这可以是标记为渲染目标的任何表面/纹理,或者您在默认池中创建的任何常规表面/纹理,或者后台缓冲区本身。 这里复杂的部分是你无法锁定它。

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

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

  1. rt = 获取渲染目标表面(这可以是纹理的表面,或后缓冲区等)
  2. if rt进行多重采样(GetDesc,检查 D3DSURFACE_DESC.MultiSampleType),然后: a) 创建另一个具有相同大小、相同格式但没有多重采样的渲染目标表面; b) 从rt拉伸矩形到这个新表面; c) rt = 这个新表面(即在这个新表面上继续)。
  3. 关闭 = 创建离屏平面(CreateOffscreenPlainSurface、D3DPOOL_SYSTEMMEM 池)
  4. 设备->GetRenderTargetData( rt, off )
  5. 现在关闭 包含渲染目标数据。 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 中获取其中的一部分。

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

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

The most involved part is reading from some surface that is in video memory ("default pool"). This is most often render targets.

Let's get the easy parts first:

  1. reading from a texture is the same as reading from 0-level surface of that texture. See below.
  2. the same for subset of a texture.
  3. reading from a surface that is in non-default memory pool ("system" or "managed") is just locking it and reading bytes.
  4. the same for subset of surface. Just lock relevant portion and read it.

So now we have left surfaces that are in video memory ("default pool"). This would be any surface/texture marked as render target, or any regular surface/texture that you have created in default pool, or the backbuffer itself. The complex part here is that you can't lock it.

Short answer is: GetRenderTargetData method on D3D device.

Longer answer (a rough outline of the code that will be below):

  1. rt = get render target surface (this can be surface of the texture, or backbuffer, etc.)
  2. if rt is multisampled (GetDesc, check D3DSURFACE_DESC.MultiSampleType), then: a) create another render target surface of same size, same format but without multisampling; b) StretchRect from rt into this new surface; c) rt = this new surface (i.e. proceed on this new surface).
  3. off = create offscreen plain surface (CreateOffscreenPlainSurface, D3DPOOL_SYSTEMMEM pool)
  4. device->GetRenderTargetData( rt, off )
  5. now off contains render target data. LockRect(), read data, UnlockRect() on it.
  6. cleanup

Even longer answer (paste from the codebase I'm working on) follows. This will not compile out of the box, because it uses some classes, functions, macros and utilities from the rest of codebase; but it should get you started. I also ommitted most of error checking (e.g. whether given width/height is out of bounds). I also omitted the part that reads actual pixels and possibly converts them into suitable destination format (that is quite easy, but can get long, depending on number of format conversions you want to support).

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 in the code above is a smart pointer to a COM object (it releases object on assignment or destructor). Simplifies error handling a lot. This is very similar to _comptr_t things in Visual C++.

The code above reads back whole surface. If you want to read just a part of it efficiently, then I believe fastest way is roughly:

  1. create a default pool surface that is of the needed size.
  2. StretchRect from part of original surface to that smaller one.
  3. proceed as normal with the smaller one.

In fact this is quite similar to what code above does to handle multi-sampled surfaces. If you want to get just a part of a multi-sampled surface, you can do a multisample resolve and get part of it in one StretchRect, I think.

Edit: removed piece of code that does actual read of pixels and format conversions. Was not directly related to question, and the code was long.

Edit: updated to match edited question.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文