Захват экрана c++ Directx11 и сохранение в файл

у меня проблема с сохранением 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;
    }
}

Что я делаю не так?


person Zapalka    schedule 18.01.2014    source источник


Ответы (3)


Во-первых, вам нужно явно проверить код возврата для всех функций, которые вернули 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) )

Одной из возможных точек отказа является CopyResource, которая возвращает void, поэтому вы не можете обнаружить проблему в своем коде. Вместо этого вам нужно включить Direct3D ОТЛАДИТЬ устройство и найти сообщения об ОШИБКАХ или ПРЕДУПРЕЖДЕНИЯХ.

В частности, если ваш буфер цепочки подкачки является ресурсом MSAA, он не сможет получить какие-либо данные. Вы должны явно использовать ResolveSubresource перед копированием. В свою очередь, поскольку ResolveSubresource возвращает пустоту, вам необходимо проверить, поддерживает ли формат D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE перед его использованием. Вот код в модуле ScreenGrab в DirectX Tool Kit, который выполняет следующую обработку:

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

На самом деле вам следует использовать DirectX Tool Kit вместо устаревшей библиотеки D3DX11. Все версии D3DX устарели, как и сам устаревший SDK DirectX (см. MSDN). Существует ряд легкодоступных заменителей< /а>.

Помимо проблемы с MSAA, у вас могут возникнуть проблемы с выбором D3DX11 форматов WIC. В зависимости от вашего формата цели рендеринга и рендеринга, это может быть запись изображения с нулевым альфа-каналом, что может привести к «пустому» выходному изображению. Модуль DirectX Tool Kit 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" );
}
person Chuck Walbourn    schedule 13.10.2015

Ваше описание объявлено, но не инициализировано до установки всех ваших значений. В его члене MiscFlags может быть мусор, который портит ваше творение. Попробуйте сначала установить description.MiscFlags = 0; или обнулить все описание. Если это не поможет, вот урезанный пример кода из одной из моих текущих библиотек, который работает:

Попробуй это:

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();
}
person orfdorf    schedule 13.10.2015
comment
Ваш код также не будет работать, если поверхность использует MSAA или формат BGRA. И он использует устаревшую библиотеку D3DX11. - person Chuck Walbourn; 14.10.2015
comment
Да, мой ответ наиболее точно соответствует тому, что, по-видимому, ОП на самом деле пытался сделать/использовать, не внося значительных изменений в свой код, но я согласен с вашим мнением и проголосовал за ваш ответ как за более разумный. - person orfdorf; 14.10.2015

для записи буфера MSAA (который является обратным буфером в цепочке подкачки) нужно использовать «ResolveSubresource» вместо «CopyResource».

Это простой сегмент кода:

          D3D11_TEXTURE2D_DESC desc;
          HRESULT hr = S_OK;
          ID3D11Resource* pSurface = nullptr;

          // get pointer to back-texture from the swap chain referred from the pointer IDXGISwapChain* g_pswapChain
          hr=g_pswapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&pSurface));
          if (FAILED(hr) || pSurface==nullptr)
          {
             // Throw Error  
      
          }
        
          D3D11_TEXTURE2D_DESC desc;
          ZeroMemory(&desc, sizeof(desc));
          desc.ArraySize = 1;
          desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
          desc.Width = 640; // i specified hard-coded the window size 640x480 in CreateWindowA
           desc.Height = 480;
           desc.MipLevels = 1;
           desc.SampleDesc.Count = 1;
           desc.SampleDesc.Quality = 0;
           desc.BindFlags = 0;
           desc.CPUAccessFlags = 0;  // data directly will be copied from GPU to a file
          desc.Usage = D3D11_USAGE_DEFAULT;

          ID3D11Texture2D* pTexture = nullptr;

          // g_pdevice is pointer to device ID3D11Device
          hr = g_pdevice->CreateTexture2D(&desc, nullptr, &pTexture);
          if (pTexture)
          {
                  // instead of CopyResource(pTexture, pSurface), g_pd3dContext pointer to context ID3D11DeviceContext
                  g_pd3dContext->ResolveSubresource(pTexture, 0, pSurface, 0, DXGI_FORMAT_R8G8B8A8_UNORM);

                  // save rendered image as bmp-file
                  hr = D3DX11SaveTextureToFileA(g_pd3dContext, pTexture, D3DX11_IFF_BMP, "test.bmp");
                  pTexture->Release();
              }
              pSurface->Release();
          
         
person Shevach Riabtsev    schedule 15.12.2020