c++ Directx11 capture screen and save to file

12,522

Solution 1

First, you need to be explicitly checking the return code for all functions that returned HRESULTs

HRESULT hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ),
    reinterpret_cast< void** >( &g_pSurface ) );
if( SUCCEEDED(hr) )
{
...
    HRESULT hr = d3d11Device->CreateTexture2D( &description, NULL, &pNewTexture );
    if( SUCCEEDED(hr) )

One possible point of failure is CopyResource which returns void so you can't detect the problem in your code. Instead you need to enable the Direct3D DEBUG device and look for any ERROR or WARNING messages.

In particular, if your swap chain buffer is a MSAA resource this will fail to get any data. You have to explicitly use ResolveSubresource before doing the copy. In turn since ResolveSubresource returns a void, you need to check the format supports D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE before using it. Here's the code in the ScreenGrab module in DirectX Tool Kit that does this handling:

static HRESULT CaptureTexture( _In_ ID3D11DeviceContext* pContext,
                               _In_ ID3D11Resource* pSource,
                               _Inout_ D3D11_TEXTURE2D_DESC& desc,
                               _Inout_ ComPtr<ID3D11Texture2D>& pStaging )
{
    if ( !pContext || !pSource )
        return E_INVALIDARG;

    D3D11_RESOURCE_DIMENSION resType = D3D11_RESOURCE_DIMENSION_UNKNOWN;
    pSource->GetType( &resType );

    if ( resType != D3D11_RESOURCE_DIMENSION_TEXTURE2D )
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );

    ComPtr<ID3D11Texture2D> pTexture;
    HRESULT hr = pSource->QueryInterface( __uuidof(ID3D11Texture2D), reinterpret_cast<void**>( pTexture.GetAddressOf() ) );
    if ( FAILED(hr) )
        return hr;

    assert( pTexture );

    pTexture->GetDesc( &desc );

    ComPtr<ID3D11Device> d3dDevice;
    pContext->GetDevice( d3dDevice.GetAddressOf() );

    if ( desc.SampleDesc.Count > 1 )
    {
        // MSAA content must be resolved before being copied to a staging texture
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;

        ComPtr<ID3D11Texture2D> pTemp;
        hr = d3dDevice->CreateTexture2D( &desc, 0, pTemp.GetAddressOf() );
        if ( FAILED(hr) )
            return hr;

        assert( pTemp );

        DXGI_FORMAT fmt = EnsureNotTypeless( desc.Format );

        UINT support = 0;
        hr = d3dDevice->CheckFormatSupport( fmt, &support );
        if ( FAILED(hr) )
            return hr;

        if ( !(support & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE) )
            return E_FAIL;

        for( UINT item = 0; item < desc.ArraySize; ++item )
        {
            for( UINT level = 0; level < desc.MipLevels; ++level )
            {
                UINT index = D3D11CalcSubresource( level, item, desc.MipLevels );
                pContext->ResolveSubresource( pTemp.Get(), index, pSource, index, fmt );
            }
        }

        desc.BindFlags = 0;
        desc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
        desc.Usage = D3D11_USAGE_STAGING;

        hr = d3dDevice->CreateTexture2D( &desc, 0, pStaging.GetAddressOf() );
        if ( FAILED(hr) )
            return hr;

        assert( pStaging );

        pContext->CopyResource( pStaging.Get(), pTemp.Get() );
    }
    else if ( (desc.Usage == D3D11_USAGE_STAGING) && (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) )
    {
        // Handle case where the source is already a staging texture we can use directly
        pStaging = pTexture;
    }
    else
    {
        // Otherwise, create a staging texture from the non-MSAA source
        desc.BindFlags = 0;
        desc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
        desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
        desc.Usage = D3D11_USAGE_STAGING;

        hr = d3dDevice->CreateTexture2D( &desc, 0, pStaging.GetAddressOf() );
        if ( FAILED(hr) )
            return hr;

        assert( pStaging );

        pContext->CopyResource( pStaging.Get(), pSource );
    }

In fact, you should be using DirectX Tool Kit instead of the legacy D3DX11 library. All versions of D3DX are deprecated as is the legacy DirectX SDK itself (see MSDN). There are a number of easily available replacements.

In addition to the MSAA issue, you might be running into problems with D3DX11's choice of WIC formats. Depending on your render target format and rendering, it might be writing out an image with an all 0 alpha channel which can result in a 'blank' output image. The DirectX Tool Kit ScreenGrab module gives you the ability to explicitly specify the output format and defaults to trying to use a non-alpha output file format for just this reason.

One more reason not to use legacy D3DX11: It was never updated for DXGI 1.1 formats so it won't support writing out BGRA format resources like DXGI_FORMAT_B8G8R8A8_UNORM or DXGI_FORMAT_B8G8R8A8_UNORM even when the underlying WIC container file format supports them. If your render target in your code above is DXGI_FORMAT_B8G8R8A8_UNORM rather than DXGI_FORMAT_R8G8B8A8_UNORM then D3DX11 will fail while ScreenGrab will work fine.

Did I mention D3DX11 is crazy old and hasn't had any fixes made to it since ~2009?

Here's some example usage for ScreenGrab:

ComPtr<ID3D11Texture2D> backBufferTex;
hr = swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&backBufferTex);
if ( SUCCEEDED(hr) )
{
    // Write out the render target as a PNG
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatPng, L"SCREENSHOT.PNG");

    // Write out the render target as JPG
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatJpeg, L"SCREENSHOT.JPG" );

    // Write out the render target as BMP
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatBmp, L"SCREENSHOT.BMP" );

    // Write out the render target as BMP and explicitly use a 16-bit format
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatBmp, L"SCREENSHOT.BMP", &GUID_WICPixelFormat16bppBGR565 );

    // Write out the render target as a TIF
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatTiff, L"SCREENSHOT.TIF" );

    // Write out the render target as a TIF with explicit WIC codec properties
    hr = SaveWICTextureToFile( context.Get(), backBufferTex.Get(), GUID_ContainerFormatTiff, L"SCREENSHOT.TIF", nullptr,
                                [&](IPropertyBag2* props)
                                {
                                    PROPBAG2 options[2] = { 0, 0 };
                                    options[0].pstrName = L"CompressionQuality";
                                    options[1].pstrName = L"TiffCompressionMethod";

                                    VARIANT varValues[2];
                                    varValues[0].vt = VT_R4;
                                    varValues[0].fltVal = 0.75f;

                                    varValues[1].vt = VT_UI1;
                                    varValues[1].bVal = WICTiffCompressionNone;

                                    (void)props->Write( 2, options, varValues ); 
                                });

    // Write out the render target as a DDS
    hr = SaveDDSTextureToFile( context.Get(), backBufferTex.Get(), L"SCREENSHOT.DDS" );
}

Solution 2

Your description is declared but not initialized to anything before setting all your values. Its MiscFlags member could have garbage in it that's mucking up your creation. Try setting description.MiscFlags = 0; or zeroing out the entire description first. If that doesn't do it, here's a stripped down code sample from one of my current libraries that works:

Try this:

HRESULT hr;

ID3D11Resource* pSurface = nullptr;
m_pRenderTargetView->GetResource(&pSurface);

if (pSurface)
{
    D3D11_TEXTURE2D_DESC desc;
    ZeroMemory(&desc, sizeof(desc));
    desc.ArraySize = 1;
    desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.Width = clientWidth;
    desc.Height = clientHeight;
    desc.MipLevels = 1;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.BindFlags = 0;
    desc.CPUAccessFlags = 0;
    desc.Usage = D3D11_USAGE_DEFAULT;

    ID3D11Texture2D* pTexture = nullptr;
    hr = m_pDevice->CreateTexture2D(&desc, nullptr, &pTexture);
    if (pTexture)
    {
        m_pContext->CopyResource(pTexture, pSurface);
        hr = D3DX11SaveTextureToFileA(m_pContext, pTexture, D3DX11_IFF_PNG, "ss.png");
        pTexture->Release();
    }
    pSurface->Release();
}
Share:
12,522
Admin
Author by

Admin

Updated on June 17, 2022

Comments

  • Admin
    Admin almost 2 years

    i've got problem with saving texture2d to file, it always gives me black image. Here is code:

    HRESULT hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast< void** >( &g_pSurface ) );
    if( g_pSurface )
    {
        ID3D11Texture2D* pNewTexture = NULL;
    
        D3D11_TEXTURE2D_DESC description;
        g_pSurface->GetDesc( &description );
        description.BindFlags = 0;
        description.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
        description.Usage = D3D11_USAGE_STAGING;
    
        HRESULT hr = d3d11Device->CreateTexture2D( &description, NULL, &pNewTexture );
        if( pNewTexture )
        {
            d3d11DevCon->CopyResource( pNewTexture, g_pSurface );
    
            hr=D3DX11SaveTextureToFileA(d3d11DevCon, pNewTexture, D3DX11_IFF_BMP, "screen.bmp");
            return;
        }
    }
    

    What am i doing wrong?

  • Chuck Walbourn
    Chuck Walbourn over 8 years
    Your code will also fail if the surface is MSAA or if it is using a BGRA format. And it is using the legacy D3DX11 library.
  • orfdorf
    orfdorf over 8 years
    Yes, my answer most closely matches what it appears the OP was actually attempting to do/use without introducing much change to their code, but I agree with your sentiments and upvoted yours as the more sensible answer.