c ++ Directx11捕获屏幕并保存到文件

Zap*_*lka 4 c++ screenshot save directx-11 texture2d

我有将texture2d保存到文件的问题,它总是给我黑色图像.这是代码:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

Chu*_*urn 7

首先,您需要显式检查返回HRESULT的所有函数的返回码

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) )
Run Code Online (Sandbox Code Playgroud)

一个可能的失败点是CopyResource返回无效,因此您无法在代码中检测到问题.相反,您需要启用Direct3D DEBUG设备并查找任何ERROR或WARNING消息.

特别是,如果您的交换链缓冲区是MSAA资源,则无法获取任何数据.您必须ResolveSubresource在执行复制之前明确使用.反过来因为ResolveSubresource返回void,您需要D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE在使用之前检查格式支持.以下是DirectX Tool KitScreenGrab模块中的代码,用于执行此处理:

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 );
    }
Run Code Online (Sandbox Code Playgroud)

实际上,您应该使用DirectX Tool Kit而不是旧版D3DX11库.与传统的DirectX SDK本身一样,不推荐使用D3DX的所有版本(请参阅MSDN).有许多容易获得的替代品.

除了MSAA问题,您可能会遇到D3DX11选择WIC格式的问题.根据您的渲染目标格式和渲染,它可能会写出一个全0字母通道的图像,这可能会导致"空白"输出图像.在DirectX的工具包 ScreenGrab模块,使您能够明确地指定输出格式,默认为尝试使用非字母输出文件格式因为这个原因的能力.

不使用遗留的另一个原因D3DX11:它从未针对DXGI 1.1格式进行更新,因此它不支持写出BGRA格式资源DXGI_FORMAT_B8G8R8A8_UNORM,DXGI_FORMAT_B8G8R8A8_UNORM甚至是基础WIC容器文件格式支持它们.如果你的代码中的渲染目标DXGI_FORMAT_B8G8R8A8_UNORM不是DXGI_FORMAT_R8G8B8A8_UNORM那么D3DX11会失败,而ScreenGrab会正常工作.

我提到的D3DX11是疯了吗?自2009年以来没有对它做过任何修改?

以下是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" );
}
Run Code Online (Sandbox Code Playgroud)