I would like to capture the contents of my front or back buffer using DirectX 11 into an array of bytes which I can then use as a texture or as a source for creating a file.
To copy the correct size, use the code below.
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 );
}
Swap chain buffers can be easily saved with D3D11 as shown below.
- Create a Texture2D as same as the swap chain's back buffer you are trying to save
- Call CopyResource on the device context to copy from back buffer to the newly created texture
- Call D3DX11SaveTextureToFile(...) with file name
contrived code fragment:
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);
So. A little more experimentation revealed the "problem". By getting the description of the framebuffer texture and using that as the basis to create the new texture the problem was resolved...
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 );
}