DirectX 11 framebuffer capture (C++, no Win32 or D3DX)

21,090

Solution 1

So. A little more experimentation revealed the "problem". By getting the description of the framebuffer texture and using that as the basis to create the new texture the problem was resolved...

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 );
}

Solution 2

To copy the correct size, use the code below.

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 );
}

Solution 3

Swap chain buffers can be easily saved with D3D11 as shown below.

  1. Create a Texture2D as same as the swap chain's back buffer you are trying to save
  2. Call CopyResource on the device context to copy from back buffer to the newly created texture
  3. Call D3DX11SaveTextureToFile(...) with file name

contrived code fragment:

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);
Share:
21,090
jheriko
Author by

jheriko

Updated on December 31, 2020

Comments

  • jheriko
    jheriko over 3 years

    I would like to capture the contents of my front or back buffer using DirectX 11 into an array of bytes which I can then use as a texture or as a source for creating a file. I have a swap chain setup, lots of rendering happening and the following code so far - which I make sure to call after the call to Present.

    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 );
    }
    

    It always gives me black with zero alphas.

    I would normally have the option of GDI interop to use BitBlt to copy a bitmap out of the swap chain - however I have restrictions which means this is not a valid solution.

    Also the D3DX library, which contains functionality for doing bits of this is also out of the question.

  • jheriko
    jheriko over 11 years
    the problem is that d3dx11 is/was not available for winrt apps at the time of writing - to quote myself: "Also the D3DX library, which contains functionality for doing bits of this is also out of the question."
  • IKavanagh
    IKavanagh over 8 years
    Please edit with more information. Code-only and "try this" answers are discouraged, because they contain no searchable content, and don't explain why someone should "try this". We make an effort here to be a resource for knowledge.
  • Rex L
    Rex L almost 5 years
    This code does not work well for me. The captured image data seems not aligned(I'm not quit sure), so that the image is not what it should to be. If someone just want to capture a frame and save it to file, using DirectXTK or DirectXTex would be a better choice. github.com/microsoft/DirectXTK github.com/Microsoft/DirectXTex
  • jheriko
    jheriko about 3 years
    this is probably the case. when i wrote this answer there was nearly nothing out there. the OS I was targetting, which introduced the constraints I mentioned, hadn't even shipped as I recall.
  • jheriko
    jheriko about 3 years
    this is a good answer. im not sure what is missing.
  • jheriko
    jheriko about 3 years
    (also check the below modified version that respects row pitch madness, and will most likely solve your problem - if its still valid 2 years later :P)