DirectX 11 帧缓冲捕获(C++,无 Win32 或 D3DX)

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

本文关键字:Win32 D3DX C++ 缓冲 DirectX      更新时间:2023-10-16

我想使用 DirectX 11 将前端或后端缓冲区的内容捕获到一个字节数组中,然后我可以将其用作纹理或创建文件的源。到目前为止,我有一个交换链设置,发生了大量渲染和以下代码 - 我确保在调用 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 );
}

它总是给我零阿尔法的黑色。

我通常可以选择 GDI 互操作来使用 BitBlt 将位图从交换链中复制出来 - 但是我有限制,这意味着这不是一个有效的解决方案。

此外,D3DX库,其中包含用于执行此操作的功能也是不可能的。

所以。再做一点实验就发现了"问题"。通过获取帧缓冲纹理的描述并将其用作创建新纹理的基础,问题得到了解决......

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

要复制正确的大小,请使用下面的代码。

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

交换链缓冲区可以使用 D3D11 轻松保存,如下所示。

  1. 创建一个与您尝试保存的交换链的后台缓冲区相同的 Texture2D
  2. 在设备上下文中调用 CopyResource 以从后台缓冲区复制到新创建的纹理
  3. 使用文件名调用 D3DX11SaveTextureToFile(...)

人为的代码片段:

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