DirectX 11 帧缓存捕获(C++,无 Win32 或 D3DX)

8
我希望通过DirectX 11将我的前端或后端缓冲区的内容捕获到字节数组中,然后我可以将其用作纹理或用作创建文件的源。我已经设置了交换链并进行了大量渲染,目前有以下代码 - 我确保在调用Present之后调用它。请注意,html标签已被保留。
ID3D11Texture2D* pSurface;
HRESULT hr = m_swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast< void** >( &pSurface ) );
if( pSurface )
{
    const int width = static_cast<int>(m_window->Bounds.Width * m_dpi / 96.0f);
    const int height = static_cast<int>(m_window->Bounds.Height * m_dpi / 96.0f);
    unsigned int size = width * height;
    if( m_captureData )
    {
        freeFramebufferData( m_captureData );
    }
    m_captureData = new unsigned char[ width * height * 4 ];

    ID3D11Texture2D* pNewTexture = NULL;

    D3D11_TEXTURE2D_DESC description =
    {
        width, height, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
        { 1, 0 }, // DXGI_SAMPLE_DESC
        D3D11_USAGE_STAGING,
        0, D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE, 0
    };

    HRESULT hr = m_d3dDevice->CreateTexture2D( &description, NULL, &pNewTexture );
    if( pNewTexture )
    {
        m_d3dContext->CopyResource( pNewTexture, pSurface );
        D3D11_MAPPED_SUBRESOURCE resource;
        unsigned int subresource = D3D11CalcSubresource( 0, 0, 0 );
        HRESULT hr = m_d3dContext->Map( pNewTexture, subresource, D3D11_MAP_READ, 0, &resource );
        //resource.pData; // TEXTURE DATA IS HERE

        const int pitch = width << 2;
        const unsigned char* source = static_cast< const unsigned char* >( resource.pData );
        unsigned char* dest = m_captureData;
        for( int i = 0; i < height; ++i )
        {
            memcpy( dest, source, width * 4 );
            source += pitch;
            dest += pitch;
        }

        m_captureSize = size;
        m_captureWidth = width;
        m_captureHeight = height;

        return;
    }

    freeFramebufferData( m_captureData );
}

当alpha值为零时,它总是给我黑色。

通常我可以使用GDI互操作选项来使用BitBlt将位图从交换链中复制出来 - 但由于我的限制,这不是一个有效的解决方案。

同时,包含执行此操作的功能的D3DX库也不可行。

3个回答

9

稍微进行了一些实验后,发现了“问题”。通过获取帧缓冲纹理的描述并以此为基础创建新纹理来解决了这个问题...

ID3D11Texture2D* pSurface;
HRESULT hr = m_swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast< void** >( &pSurface ) );
if( pSurface )
{
    const int width = static_cast<int>(m_window->Bounds.Width * m_dpi / 96.0f);
    const int height = static_cast<int>(m_window->Bounds.Height * m_dpi / 96.0f);
    unsigned int size = width * height;
    if( m_captureData )
    {
        freeFramebufferData( m_captureData );
    }
    m_captureData = new unsigned char[ width * height * 4 ];

    ID3D11Texture2D* pNewTexture = NULL;

    D3D11_TEXTURE2D_DESC description;
    pSurface->GetDesc( &description );
    description.BindFlags = 0;
    description.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
    description.Usage = D3D11_USAGE_STAGING;

    HRESULT hr = m_d3dDevice->CreateTexture2D( &description, NULL, &pNewTexture );
    if( pNewTexture )
    {
        m_d3dContext->CopyResource( pNewTexture, pSurface );
        D3D11_MAPPED_SUBRESOURCE resource;
        unsigned int subresource = D3D11CalcSubresource( 0, 0, 0 );
        HRESULT hr = m_d3dContext->Map( pNewTexture, subresource, D3D11_MAP_READ_WRITE, 0, &resource );
        //resource.pData; // TEXTURE DATA IS HERE

        const int pitch = width << 2;
        const unsigned char* source = static_cast< const unsigned char* >( resource.pData );
        unsigned char* dest = m_captureData;
        for( int i = 0; i < height; ++i )
        {
            memcpy( dest, source, width * 4 );
            source += pitch;
            dest += pitch;
        }

        m_captureSize = size;
        m_captureWidth = width;
        m_captureHeight = height;

        return;
    }

    freeFramebufferData( m_captureData );
}

1
这段代码对我来说效果不佳。捕获的图像数据似乎没有对齐(我不太确定),因此图像不是应该的样子。如果有人只想捕获一帧并将其保存到文件中,使用DirectXTK或DirectXTex会是更好的选择。https://github.com/microsoft/DirectXTK https://github.com/Microsoft/DirectXTex - Rex L
这可能是事实。当我写下这个答案时,几乎没有什么相关的信息。我所针对的操作系统,引入了我提到的限制,甚至还没有发布。 - jheriko
(还要检查下面修改后的版本,它遵循了行间距疯狂的规则,并且很可能解决您的问题 - 如果它仍然有效2年后:P) - jheriko

3

要复制正确的大小,请使用下面的代码。

ID3D11Texture2D* pSurface;
HRESULT hr = m_swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast< void** >( &pSurface ) );
if( pSurface )
{
    const int width = static_cast<int>(m_window->Bounds.Width * m_dpi / 96.0f);
    const int height = static_cast<int>(m_window->Bounds.Height * m_dpi / 96.0f);
    unsigned int size = width * height;
    if( m_captureData )
    {
        freeFramebufferData( m_captureData );
    }
    m_captureData = new unsigned char[ width * height * 4 ];

    ID3D11Texture2D* pNewTexture = NULL;

    D3D11_TEXTURE2D_DESC description;
    pSurface->GetDesc( &description );
    description.BindFlags = 0;
    description.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
    description.Usage = D3D11_USAGE_STAGING;

    HRESULT hr = m_d3dDevice->CreateTexture2D( &description, NULL, &pNewTexture );
    if( pNewTexture )
    {
        m_d3dContext->CopyResource( pNewTexture, pSurface );
        D3D11_MAPPED_SUBRESOURCE resource;
        unsigned int subresource = D3D11CalcSubresource( 0, 0, 0 );
        HRESULT hr = m_d3dContext->Map( pNewTexture, subresource, D3D11_MAP_READ_WRITE, 0, &resource );
        //resource.pData; // TEXTURE DATA IS HERE

        const int pitch = width << 2;
        const unsigned char* source = static_cast< const unsigned char* >( resource.pData );
        unsigned char* dest = m_captureData;
        for( int i = 0; i < height; ++i )
        {
            memcpy( dest, source, width * 4 );
            source += resource.RowPitch; // <------
            dest += pitch;
        }

        m_captureSize = size;
        m_captureWidth = width;
        m_captureHeight = height;

        return;
    }

    freeFramebufferData( m_captureData );
}

1
请添加更多的信息。仅包含代码和“尝试这个”答案是不被鼓励的(因为它们不包含可搜索的内容,并且没有解释为什么应该“尝试这个”)。我们在这里致力于成为知识资源。 - IKavanagh
这是一个很好的答案。我不确定缺少什么。 - jheriko

2

使用D3D11可以轻松地保存交换链缓冲区,具体操作如下:

  1. 创建一个与您要保存的交换链背景缓冲区相同的Texture2D
  2. 调用设备上下文中的CopyResource方法,将图像从背景缓冲区复制到新创建的纹理中
  3. 调用D3DX11SaveTextureToFile(...)方法并指定文件名

参考代码片段:

ID3D11Texture2D* pBuffer;

swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBuffer);

if(texture_to_save == NULL)
{
    D3D11_TEXTURE2D_DESC td;
    pBuffer->GetDesc(&td);
    device->CreateTexture2D(&td, NULL, &texture_to_save);
}

deviceContext->CopyResource(texture_to_save, pBuffer);

D3DX11SaveTextureToFile(deviceContext,texture_to_save,D3DX11_IFF_PNG,filename);

问题在于编写时d3dx11在WinRT应用程序中不可用/不存在 - 引用自己的话:“还有D3DX库,其中包含执行此操作的功能也是不可能的。” - jheriko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接