【问题标题】:c++ Directx11 capture screen and save to filec++ Directx11 截屏并保存到文件
【发布时间】:2014-02-07 17:55:04
【问题描述】:

我在将 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;
    }
}

我做错了什么?

【问题讨论】:

    标签: c++ save screenshot directx-11 texture2d


    【解决方案1】:

    首先,您需要明确检查所有返回 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 DEBUG 设备并查找任何错误或警告消息。

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

    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 都已弃用,旧版 DirectX SDK 本身也是如此(请参阅MSDN)。有许多容易获得的replacements

    除了 MSAA 问题之外,您可能还会遇到D3DX11 选择 WIC 格式的问题。根据您的渲染目标格式和渲染,它可能会写出具有全 0 alpha 通道的图像,这可能会导致“空白”输出图像。 DirectX 工具包 ScreenGrab 模块使您能够显式指定输出格式,并且出于这个原因默认尝试使用非 alpha 输出文件格式。

    不使用旧版D3DX11 的另一个原因:它从未针对 DXGI 1.1 格式进行更新,因此即使底层 WIC 容器文件格式支持,它也不支持写出 BGRA 格式资源,例如 DXGI_FORMAT_B8G8R8A8_UNORMDXGI_FORMAT_B8G8R8A8_UNORM他们。如果上面代码中的渲染目标是 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" );
    }
    

    【讨论】:

      【解决方案2】:

      在设置所有值之前,您的描述已声明但未初始化为任何内容。它的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();
      }
      

      【讨论】:

      • 如果表面是 MSAA 或使用 BGRA 格式,您的代码也会失败。它使用的是旧版 D3DX11 库。
      • 是的,我的回答与 OP 实际尝试执行/使用的内容最接近,但没有对其代码进行太多更改,但我同意您的观点,并将您的观点视为更明智的答案。
      【解决方案3】:

      要写入 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();
                
               
      

      【讨论】:

        猜你喜欢
        • 2013-05-05
        • 2015-10-03
        • 1970-01-01
        • 2014-12-01
        • 1970-01-01
        • 2021-06-18
        • 2018-02-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多