lowpoly-walking-simulator/directx11_hellovr/DirectXTK/Src/ScreenGrab.cpp

1087 lines
38 KiB
C++
Raw Permalink Normal View History

2024-11-14 11:54:38 +00:00
//--------------------------------------------------------------------------------------
// File: ScreenGrab.cpp
//
// Function for capturing a 2D texture and saving it to a file (aka a 'screenshot'
// when used on a Direct3D 11 Render Target).
//
// Note these functions are useful as a light-weight runtime screen grabber. For
// full-featured texture capture, DDS writer, and texture processing pipeline,
// see the 'Texconv' sample and the 'DirectXTex' library.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------
// Does not capture 1D textures or 3D textures (volume maps)
// Does not capture mipmap chains, only the top-most texture level is saved
// For 2D array textures and cubemaps, it captures only the first image in the array
#include "pch.h"
#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (_WIN32_WINNT > _WIN32_WINNT_WIN8)
// VS 2010's stdint.h conflicts with intsafe.h
#pragma warning(push)
#pragma warning(disable : 4005)
#include <wincodec.h>
#pragma warning(pop)
#endif
#include "ScreenGrab.h"
#include "dds.h"
#include "PlatformHelpers.h"
using Microsoft::WRL::ComPtr;
using namespace DirectX;
namespace
{
class auto_delete_file
{
public:
auto_delete_file(HANDLE hFile) : m_handle(hFile) {}
~auto_delete_file()
{
if (m_handle)
{
FILE_DISPOSITION_INFO info = {0};
info.DeleteFile = TRUE;
(void)SetFileInformationByHandle(m_handle, FileDispositionInfo, &info, sizeof(info));
}
}
void clear() { m_handle = 0; }
private:
HANDLE m_handle;
auto_delete_file(const auto_delete_file&) DIRECTX_CTOR_DELETE;
auto_delete_file& operator=(const auto_delete_file&) DIRECTX_CTOR_DELETE;
};
#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (_WIN32_WINNT > _WIN32_WINNT_WIN8)
class auto_delete_file_wic
{
public:
auto_delete_file_wic(ComPtr<IWICStream>& hFile, LPCWSTR szFile) : m_handle(hFile), m_filename(szFile) {}
~auto_delete_file_wic()
{
if (m_filename)
{
m_handle.Reset();
DeleteFileW(m_filename);
}
}
void clear() { m_filename = 0; }
private:
LPCWSTR m_filename;
ComPtr<IWICStream>& m_handle;
auto_delete_file_wic(const auto_delete_file_wic&) DIRECTX_CTOR_DELETE;
auto_delete_file_wic& operator=(const auto_delete_file_wic&) DIRECTX_CTOR_DELETE;
};
#endif
}
//--------------------------------------------------------------------------------------
// Return the BPP for a particular format
//--------------------------------------------------------------------------------------
static size_t BitsPerPixel( _In_ DXGI_FORMAT fmt )
{
switch( fmt )
{
case DXGI_FORMAT_R32G32B32A32_TYPELESS:
case DXGI_FORMAT_R32G32B32A32_FLOAT:
case DXGI_FORMAT_R32G32B32A32_UINT:
case DXGI_FORMAT_R32G32B32A32_SINT:
return 128;
case DXGI_FORMAT_R32G32B32_TYPELESS:
case DXGI_FORMAT_R32G32B32_FLOAT:
case DXGI_FORMAT_R32G32B32_UINT:
case DXGI_FORMAT_R32G32B32_SINT:
return 96;
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
case DXGI_FORMAT_R16G16B16A16_FLOAT:
case DXGI_FORMAT_R16G16B16A16_UNORM:
case DXGI_FORMAT_R16G16B16A16_UINT:
case DXGI_FORMAT_R16G16B16A16_SNORM:
case DXGI_FORMAT_R16G16B16A16_SINT:
case DXGI_FORMAT_R32G32_TYPELESS:
case DXGI_FORMAT_R32G32_FLOAT:
case DXGI_FORMAT_R32G32_UINT:
case DXGI_FORMAT_R32G32_SINT:
case DXGI_FORMAT_R32G8X24_TYPELESS:
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
case DXGI_FORMAT_Y416:
case DXGI_FORMAT_Y210:
case DXGI_FORMAT_Y216:
return 64;
case DXGI_FORMAT_R10G10B10A2_TYPELESS:
case DXGI_FORMAT_R10G10B10A2_UNORM:
case DXGI_FORMAT_R10G10B10A2_UINT:
case DXGI_FORMAT_R11G11B10_FLOAT:
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
case DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_R8G8B8A8_UINT:
case DXGI_FORMAT_R8G8B8A8_SNORM:
case DXGI_FORMAT_R8G8B8A8_SINT:
case DXGI_FORMAT_R16G16_TYPELESS:
case DXGI_FORMAT_R16G16_FLOAT:
case DXGI_FORMAT_R16G16_UNORM:
case DXGI_FORMAT_R16G16_UINT:
case DXGI_FORMAT_R16G16_SNORM:
case DXGI_FORMAT_R16G16_SINT:
case DXGI_FORMAT_R32_TYPELESS:
case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32_FLOAT:
case DXGI_FORMAT_R32_UINT:
case DXGI_FORMAT_R32_SINT:
case DXGI_FORMAT_R24G8_TYPELESS:
case DXGI_FORMAT_D24_UNORM_S8_UINT:
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
case DXGI_FORMAT_R8G8_B8G8_UNORM:
case DXGI_FORMAT_G8R8_G8B8_UNORM:
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_B8G8R8X8_UNORM:
case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_TYPELESS:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
case DXGI_FORMAT_AYUV:
case DXGI_FORMAT_Y410:
case DXGI_FORMAT_YUY2:
return 32;
case DXGI_FORMAT_P010:
case DXGI_FORMAT_P016:
return 24;
case DXGI_FORMAT_R8G8_TYPELESS:
case DXGI_FORMAT_R8G8_UNORM:
case DXGI_FORMAT_R8G8_UINT:
case DXGI_FORMAT_R8G8_SNORM:
case DXGI_FORMAT_R8G8_SINT:
case DXGI_FORMAT_R16_TYPELESS:
case DXGI_FORMAT_R16_FLOAT:
case DXGI_FORMAT_D16_UNORM:
case DXGI_FORMAT_R16_UNORM:
case DXGI_FORMAT_R16_UINT:
case DXGI_FORMAT_R16_SNORM:
case DXGI_FORMAT_R16_SINT:
case DXGI_FORMAT_B5G6R5_UNORM:
case DXGI_FORMAT_B5G5R5A1_UNORM:
case DXGI_FORMAT_A8P8:
case DXGI_FORMAT_B4G4R4A4_UNORM:
return 16;
case DXGI_FORMAT_NV12:
case DXGI_FORMAT_420_OPAQUE:
case DXGI_FORMAT_NV11:
return 12;
case DXGI_FORMAT_R8_TYPELESS:
case DXGI_FORMAT_R8_UNORM:
case DXGI_FORMAT_R8_UINT:
case DXGI_FORMAT_R8_SNORM:
case DXGI_FORMAT_R8_SINT:
case DXGI_FORMAT_A8_UNORM:
case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8:
return 8;
case DXGI_FORMAT_R1_UNORM:
return 1;
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
return 4;
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return 8;
#if defined(_XBOX_ONE) && defined(_TITLE)
case DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT:
case DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT:
case DXGI_FORMAT_R10G10B10_SNORM_A2_UNORM:
return 32;
case DXGI_FORMAT_D16_UNORM_S8_UINT:
case DXGI_FORMAT_R16_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X16_TYPELESS_G8_UINT:
return 24;
case DXGI_FORMAT_R4G4_UNORM:
return 8;
#endif // _XBOX_ONE && _TITLE
default:
return 0;
}
}
//--------------------------------------------------------------------------------------
// Determines if the format is block compressed
//--------------------------------------------------------------------------------------
static bool IsCompressed( _In_ DXGI_FORMAT fmt )
{
switch ( fmt )
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return true;
default:
return false;
}
}
//--------------------------------------------------------------------------------------
// Get surface information for a particular format
//--------------------------------------------------------------------------------------
static void GetSurfaceInfo( _In_ size_t width,
_In_ size_t height,
_In_ DXGI_FORMAT fmt,
_Out_opt_ size_t* outNumBytes,
_Out_opt_ size_t* outRowBytes,
_Out_opt_ size_t* outNumRows )
{
size_t numBytes = 0;
size_t rowBytes = 0;
size_t numRows = 0;
bool bc = false;
bool packed = false;
bool planar = false;
size_t bpe = 0;
switch (fmt)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
bc=true;
bpe = 8;
break;
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
bc = true;
bpe = 16;
break;
case DXGI_FORMAT_R8G8_B8G8_UNORM:
case DXGI_FORMAT_G8R8_G8B8_UNORM:
case DXGI_FORMAT_YUY2:
packed = true;
bpe = 4;
break;
case DXGI_FORMAT_Y210:
case DXGI_FORMAT_Y216:
packed = true;
bpe = 8;
break;
case DXGI_FORMAT_NV12:
case DXGI_FORMAT_420_OPAQUE:
planar = true;
bpe = 2;
break;
case DXGI_FORMAT_P010:
case DXGI_FORMAT_P016:
planar = true;
bpe = 4;
break;
#if defined(_XBOX_ONE) && defined(_TITLE)
case DXGI_FORMAT_D16_UNORM_S8_UINT:
case DXGI_FORMAT_R16_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X16_TYPELESS_G8_UINT:
planar = true;
bpe = 4;
break;
#endif
}
if (bc)
{
size_t numBlocksWide = 0;
if (width > 0)
{
numBlocksWide = std::max<size_t>( 1, (width + 3) / 4 );
}
size_t numBlocksHigh = 0;
if (height > 0)
{
numBlocksHigh = std::max<size_t>( 1, (height + 3) / 4 );
}
rowBytes = numBlocksWide * bpe;
numRows = numBlocksHigh;
numBytes = rowBytes * numBlocksHigh;
}
else if (packed)
{
rowBytes = ( ( width + 1 ) >> 1 ) * bpe;
numRows = height;
numBytes = rowBytes * height;
}
else if ( fmt == DXGI_FORMAT_NV11 )
{
rowBytes = ( ( width + 3 ) >> 2 ) * 4;
numRows = height * 2; // Direct3D makes this simplifying assumption, although it is larger than the 4:1:1 data
numBytes = rowBytes * numRows;
}
else if (planar)
{
rowBytes = ( ( width + 1 ) >> 1 ) * bpe;
numBytes = ( rowBytes * height ) + ( ( rowBytes * height + 1 ) >> 1 );
numRows = height + ( ( height + 1 ) >> 1 );
}
else
{
size_t bpp = BitsPerPixel( fmt );
rowBytes = ( width * bpp + 7 ) / 8; // round up to nearest byte
numRows = height;
numBytes = rowBytes * height;
}
if (outNumBytes)
{
*outNumBytes = numBytes;
}
if (outRowBytes)
{
*outRowBytes = rowBytes;
}
if (outNumRows)
{
*outNumRows = numRows;
}
}
//--------------------------------------------------------------------------------------
static DXGI_FORMAT EnsureNotTypeless( DXGI_FORMAT fmt )
{
// Assumes UNORM or FLOAT; doesn't use UINT or SINT
switch( fmt )
{
case DXGI_FORMAT_R32G32B32A32_TYPELESS: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case DXGI_FORMAT_R32G32B32_TYPELESS: return DXGI_FORMAT_R32G32B32_FLOAT;
case DXGI_FORMAT_R16G16B16A16_TYPELESS: return DXGI_FORMAT_R16G16B16A16_UNORM;
case DXGI_FORMAT_R32G32_TYPELESS: return DXGI_FORMAT_R32G32_FLOAT;
case DXGI_FORMAT_R10G10B10A2_TYPELESS: return DXGI_FORMAT_R10G10B10A2_UNORM;
case DXGI_FORMAT_R8G8B8A8_TYPELESS: return DXGI_FORMAT_R8G8B8A8_UNORM;
case DXGI_FORMAT_R16G16_TYPELESS: return DXGI_FORMAT_R16G16_UNORM;
case DXGI_FORMAT_R32_TYPELESS: return DXGI_FORMAT_R32_FLOAT;
case DXGI_FORMAT_R8G8_TYPELESS: return DXGI_FORMAT_R8G8_UNORM;
case DXGI_FORMAT_R16_TYPELESS: return DXGI_FORMAT_R16_UNORM;
case DXGI_FORMAT_R8_TYPELESS: return DXGI_FORMAT_R8_UNORM;
case DXGI_FORMAT_BC1_TYPELESS: return DXGI_FORMAT_BC1_UNORM;
case DXGI_FORMAT_BC2_TYPELESS: return DXGI_FORMAT_BC2_UNORM;
case DXGI_FORMAT_BC3_TYPELESS: return DXGI_FORMAT_BC3_UNORM;
case DXGI_FORMAT_BC4_TYPELESS: return DXGI_FORMAT_BC4_UNORM;
case DXGI_FORMAT_BC5_TYPELESS: return DXGI_FORMAT_BC5_UNORM;
case DXGI_FORMAT_B8G8R8A8_TYPELESS: return DXGI_FORMAT_B8G8R8A8_UNORM;
case DXGI_FORMAT_B8G8R8X8_TYPELESS: return DXGI_FORMAT_B8G8R8X8_UNORM;
case DXGI_FORMAT_BC7_TYPELESS: return DXGI_FORMAT_BC7_UNORM;
default: return fmt;
}
}
//--------------------------------------------------------------------------------------
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;
#if defined(_XBOX_ONE) && defined(_TITLE)
HRESULT hr = pSource->QueryInterface(IID_GRAPHICS_PPV_ARGS(pTexture.GetAddressOf()));
#else
HRESULT hr = pSource->QueryInterface(IID_PPV_ARGS(pTexture.GetAddressOf()));
#endif
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 );
}
#if defined(_XBOX_ONE) && defined(_TITLE)
if ( d3dDevice->GetCreationFlags() & D3D11_CREATE_DEVICE_IMMEDIATE_CONTEXT_FAST_SEMANTICS )
{
ComPtr<ID3D11DeviceX> d3dDeviceX;
hr = d3dDevice.As( &d3dDeviceX );
if ( FAILED(hr) )
return hr;
ComPtr<ID3D11DeviceContextX> d3dContextX;
hr = pContext->QueryInterface(IID_GRAPHICS_PPV_ARGS(d3dContextX.GetAddressOf()));
if ( FAILED(hr) )
return hr;
UINT64 copyFence = d3dContextX->InsertFence(0);
while ( d3dDeviceX->IsFencePending( copyFence ) )
{
SwitchToThread();
}
}
#endif
return S_OK;
}
//--------------------------------------------------------------------------------------
HRESULT DirectX::SaveDDSTextureToFile( _In_ ID3D11DeviceContext* pContext,
_In_ ID3D11Resource* pSource,
_In_z_ LPCWSTR fileName )
{
if ( !fileName )
return E_INVALIDARG;
D3D11_TEXTURE2D_DESC desc = { 0 };
ComPtr<ID3D11Texture2D> pStaging;
HRESULT hr = CaptureTexture( pContext, pSource, desc, pStaging );
if ( FAILED(hr) )
return hr;
// Create file
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile( safe_handle( CreateFile2( fileName, GENERIC_WRITE | DELETE, 0, CREATE_ALWAYS, 0 ) ) );
#else
ScopedHandle hFile( safe_handle( CreateFileW( fileName, GENERIC_WRITE | DELETE, 0, 0, CREATE_ALWAYS, 0, 0 ) ) );
#endif
if ( !hFile )
return HRESULT_FROM_WIN32( GetLastError() );
auto_delete_file delonfail(hFile.get());
// Setup header
const size_t MAX_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10);
uint8_t fileHeader[ MAX_HEADER_SIZE ];
*reinterpret_cast<uint32_t*>(&fileHeader[0]) = DDS_MAGIC;
auto header = reinterpret_cast<DDS_HEADER*>( &fileHeader[0] + sizeof(uint32_t) );
size_t headerSize = sizeof(uint32_t) + sizeof(DDS_HEADER);
memset( header, 0, sizeof(DDS_HEADER) );
header->size = sizeof( DDS_HEADER );
header->flags = DDS_HEADER_FLAGS_TEXTURE | DDS_HEADER_FLAGS_MIPMAP;
header->height = desc.Height;
header->width = desc.Width;
header->mipMapCount = 1;
header->caps = DDS_SURFACE_FLAGS_TEXTURE;
// Try to use a legacy .DDS pixel format for better tools support, otherwise fallback to 'DX10' header extension
DDS_HEADER_DXT10* extHeader = nullptr;
switch( desc.Format )
{
case DXGI_FORMAT_R8G8B8A8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A8B8G8R8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R16G16_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_G16R16, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R8G8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A8L8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R16_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_L16, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_L8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_A8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R8G8_B8G8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_R8G8_B8G8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_G8R8_G8B8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_G8R8_G8B8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC1_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_DXT1, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC2_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_DXT3, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC3_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_DXT5, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC4_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_BC4_UNORM, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC4_SNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_BC4_SNORM, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC5_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_BC5_UNORM, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_BC5_SNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_BC5_SNORM, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_B5G6R5_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_R5G6B5, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_B5G5R5A1_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A1R5G5B5, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R8G8_SNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_V8U8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R8G8B8A8_SNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_Q8W8V8U8, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_R16G16_SNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_V16U16, sizeof(DDS_PIXELFORMAT) ); break;
case DXGI_FORMAT_B8G8R8A8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A8R8G8B8, sizeof(DDS_PIXELFORMAT) ); break; // DXGI 1.1
case DXGI_FORMAT_B8G8R8X8_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_X8R8G8B8, sizeof(DDS_PIXELFORMAT) ); break; // DXGI 1.1
case DXGI_FORMAT_YUY2: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_YUY2, sizeof(DDS_PIXELFORMAT) ); break; // DXGI 1.2
case DXGI_FORMAT_B4G4R4A4_UNORM: memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_A4R4G4B4, sizeof(DDS_PIXELFORMAT) ); break; // DXGI 1.2
// Legacy D3DX formats using D3DFMT enum value as FourCC
case DXGI_FORMAT_R32G32B32A32_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 116; break; // D3DFMT_A32B32G32R32F
case DXGI_FORMAT_R16G16B16A16_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 113; break; // D3DFMT_A16B16G16R16F
case DXGI_FORMAT_R16G16B16A16_UNORM: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 36; break; // D3DFMT_A16B16G16R16
case DXGI_FORMAT_R16G16B16A16_SNORM: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 110; break; // D3DFMT_Q16W16V16U16
case DXGI_FORMAT_R32G32_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 115; break; // D3DFMT_G32R32F
case DXGI_FORMAT_R16G16_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 112; break; // D3DFMT_G16R16F
case DXGI_FORMAT_R32_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 114; break; // D3DFMT_R32F
case DXGI_FORMAT_R16_FLOAT: header->ddspf.size = sizeof(DDS_PIXELFORMAT); header->ddspf.flags = DDS_FOURCC; header->ddspf.fourCC = 111; break; // D3DFMT_R16F
case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8:
case DXGI_FORMAT_A8P8:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
default:
memcpy_s( &header->ddspf, sizeof(header->ddspf), &DDSPF_DX10, sizeof(DDS_PIXELFORMAT) );
headerSize += sizeof(DDS_HEADER_DXT10);
extHeader = reinterpret_cast<DDS_HEADER_DXT10*>( reinterpret_cast<uint8_t*>(&fileHeader[0]) + sizeof(uint32_t) + sizeof(DDS_HEADER) );
memset( extHeader, 0, sizeof(DDS_HEADER_DXT10) );
extHeader->dxgiFormat = desc.Format;
extHeader->resourceDimension = D3D11_RESOURCE_DIMENSION_TEXTURE2D;
extHeader->arraySize = 1;
break;
}
size_t rowPitch, slicePitch, rowCount;
GetSurfaceInfo( desc.Width, desc.Height, desc.Format, &slicePitch, &rowPitch, &rowCount );
if ( IsCompressed( desc.Format ) )
{
header->flags |= DDS_HEADER_FLAGS_LINEARSIZE;
header->pitchOrLinearSize = static_cast<uint32_t>( slicePitch );
}
else
{
header->flags |= DDS_HEADER_FLAGS_PITCH;
header->pitchOrLinearSize = static_cast<uint32_t>( rowPitch );
}
// Setup pixels
std::unique_ptr<uint8_t[]> pixels( new (std::nothrow) uint8_t[ slicePitch ] );
if (!pixels)
return E_OUTOFMEMORY;
D3D11_MAPPED_SUBRESOURCE mapped;
hr = pContext->Map( pStaging.Get(), 0, D3D11_MAP_READ, 0, &mapped );
if ( FAILED(hr) )
return hr;
auto sptr = reinterpret_cast<const uint8_t*>( mapped.pData );
if ( !sptr )
{
pContext->Unmap( pStaging.Get(), 0 );
return E_POINTER;
}
uint8_t* dptr = pixels.get();
size_t msize = std::min<size_t>( rowPitch, mapped.RowPitch );
for( size_t h = 0; h < rowCount; ++h )
{
memcpy_s( dptr, rowPitch, sptr, msize );
sptr += mapped.RowPitch;
dptr += rowPitch;
}
pContext->Unmap( pStaging.Get(), 0 );
// Write header & pixels
DWORD bytesWritten;
if ( !WriteFile( hFile.get(), fileHeader, static_cast<DWORD>( headerSize ), &bytesWritten, 0 ) )
return HRESULT_FROM_WIN32( GetLastError() );
if ( bytesWritten != headerSize )
return E_FAIL;
if ( !WriteFile( hFile.get(), pixels.get(), static_cast<DWORD>( slicePitch ), &bytesWritten, 0 ) )
return HRESULT_FROM_WIN32( GetLastError() );
if ( bytesWritten != slicePitch )
return E_FAIL;
delonfail.clear();
return S_OK;
}
//--------------------------------------------------------------------------------------
#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (_WIN32_WINNT > _WIN32_WINNT_WIN8)
namespace DirectX
{
extern bool _IsWIC2();
extern IWICImagingFactory* _GetWIC();
}
HRESULT DirectX::SaveWICTextureToFile( _In_ ID3D11DeviceContext* pContext,
_In_ ID3D11Resource* pSource,
_In_ REFGUID guidContainerFormat,
_In_z_ LPCWSTR fileName,
_In_opt_ const GUID* targetFormat,
_In_opt_ std::function<void(IPropertyBag2*)> setCustomProps )
{
if ( !fileName )
return E_INVALIDARG;
D3D11_TEXTURE2D_DESC desc = { 0 };
ComPtr<ID3D11Texture2D> pStaging;
HRESULT hr = CaptureTexture( pContext, pSource, desc, pStaging );
if ( FAILED(hr) )
return hr;
// Determine source format's WIC equivalent
WICPixelFormatGUID pfGuid;
bool sRGB = false;
switch ( desc.Format )
{
case DXGI_FORMAT_R32G32B32A32_FLOAT: pfGuid = GUID_WICPixelFormat128bppRGBAFloat; break;
case DXGI_FORMAT_R16G16B16A16_FLOAT: pfGuid = GUID_WICPixelFormat64bppRGBAHalf; break;
case DXGI_FORMAT_R16G16B16A16_UNORM: pfGuid = GUID_WICPixelFormat64bppRGBA; break;
case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: pfGuid = GUID_WICPixelFormat32bppRGBA1010102XR; break; // DXGI 1.1
case DXGI_FORMAT_R10G10B10A2_UNORM: pfGuid = GUID_WICPixelFormat32bppRGBA1010102; break;
case DXGI_FORMAT_B5G5R5A1_UNORM: pfGuid = GUID_WICPixelFormat16bppBGRA5551; break;
case DXGI_FORMAT_B5G6R5_UNORM: pfGuid = GUID_WICPixelFormat16bppBGR565; break;
case DXGI_FORMAT_R32_FLOAT: pfGuid = GUID_WICPixelFormat32bppGrayFloat; break;
case DXGI_FORMAT_R16_FLOAT: pfGuid = GUID_WICPixelFormat16bppGrayHalf; break;
case DXGI_FORMAT_R16_UNORM: pfGuid = GUID_WICPixelFormat16bppGray; break;
case DXGI_FORMAT_R8_UNORM: pfGuid = GUID_WICPixelFormat8bppGray; break;
case DXGI_FORMAT_A8_UNORM: pfGuid = GUID_WICPixelFormat8bppAlpha; break;
case DXGI_FORMAT_R8G8B8A8_UNORM:
pfGuid = GUID_WICPixelFormat32bppRGBA;
break;
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
pfGuid = GUID_WICPixelFormat32bppRGBA;
sRGB = true;
break;
case DXGI_FORMAT_B8G8R8A8_UNORM: // DXGI 1.1
pfGuid = GUID_WICPixelFormat32bppBGRA;
break;
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: // DXGI 1.1
pfGuid = GUID_WICPixelFormat32bppBGRA;
sRGB = true;
break;
case DXGI_FORMAT_B8G8R8X8_UNORM: // DXGI 1.1
pfGuid = GUID_WICPixelFormat32bppBGR;
break;
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: // DXGI 1.1
pfGuid = GUID_WICPixelFormat32bppBGR;
sRGB = true;
break;
default:
return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
}
IWICImagingFactory* pWIC = _GetWIC();
if ( !pWIC )
return E_NOINTERFACE;
ComPtr<IWICStream> stream;
hr = pWIC->CreateStream( stream.GetAddressOf() );
if ( FAILED(hr) )
return hr;
hr = stream->InitializeFromFilename( fileName, GENERIC_WRITE );
if ( FAILED(hr) )
return hr;
auto_delete_file_wic delonfail(stream, fileName);
ComPtr<IWICBitmapEncoder> encoder;
hr = pWIC->CreateEncoder( guidContainerFormat, 0, encoder.GetAddressOf() );
if ( FAILED(hr) )
return hr;
hr = encoder->Initialize( stream.Get(), WICBitmapEncoderNoCache );
if ( FAILED(hr) )
return hr;
ComPtr<IWICBitmapFrameEncode> frame;
ComPtr<IPropertyBag2> props;
hr = encoder->CreateNewFrame( frame.GetAddressOf(), props.GetAddressOf() );
if ( FAILED(hr) )
return hr;
if ( targetFormat && memcmp( &guidContainerFormat, &GUID_ContainerFormatBmp, sizeof(WICPixelFormatGUID) ) == 0 && _IsWIC2() )
{
// Opt-in to the WIC2 support for writing 32-bit Windows BMP files with an alpha channel
PROPBAG2 option = { 0 };
option.pstrName = L"EnableV5Header32bppBGRA";
VARIANT varValue;
varValue.vt = VT_BOOL;
varValue.boolVal = VARIANT_TRUE;
(void)props->Write( 1, &option, &varValue );
}
if ( setCustomProps )
{
setCustomProps( props.Get() );
}
hr = frame->Initialize( props.Get() );
if ( FAILED(hr) )
return hr;
hr = frame->SetSize( desc.Width , desc.Height );
if ( FAILED(hr) )
return hr;
hr = frame->SetResolution( 72, 72 );
if ( FAILED(hr) )
return hr;
// Pick a target format
WICPixelFormatGUID targetGuid;
if ( targetFormat )
{
targetGuid = *targetFormat;
}
else
{
// Screenshots don<6F>t typically include the alpha channel of the render target
switch ( desc.Format )
{
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE)
case DXGI_FORMAT_R32G32B32A32_FLOAT:
case DXGI_FORMAT_R16G16B16A16_FLOAT:
if ( _IsWIC2() )
{
targetGuid = GUID_WICPixelFormat96bppRGBFloat;
}
else
{
targetGuid = GUID_WICPixelFormat24bppBGR;
}
break;
#endif
case DXGI_FORMAT_R16G16B16A16_UNORM: targetGuid = GUID_WICPixelFormat48bppBGR; break;
case DXGI_FORMAT_B5G5R5A1_UNORM: targetGuid = GUID_WICPixelFormat16bppBGR555; break;
case DXGI_FORMAT_B5G6R5_UNORM: targetGuid = GUID_WICPixelFormat16bppBGR565; break;
case DXGI_FORMAT_R32_FLOAT:
case DXGI_FORMAT_R16_FLOAT:
case DXGI_FORMAT_R16_UNORM:
case DXGI_FORMAT_R8_UNORM:
case DXGI_FORMAT_A8_UNORM:
targetGuid = GUID_WICPixelFormat8bppGray;
break;
default:
targetGuid = GUID_WICPixelFormat24bppBGR;
break;
}
}
hr = frame->SetPixelFormat( &targetGuid );
if ( FAILED(hr) )
return hr;
if ( targetFormat && memcmp( targetFormat, &targetGuid, sizeof(WICPixelFormatGUID) ) != 0 )
{
// Requested output pixel format is not supported by the WIC codec
return E_FAIL;
}
// Encode WIC metadata
ComPtr<IWICMetadataQueryWriter> metawriter;
if ( SUCCEEDED( frame->GetMetadataQueryWriter( metawriter.GetAddressOf() ) ) )
{
PROPVARIANT value;
PropVariantInit( &value );
value.vt = VT_LPSTR;
value.pszVal = "DirectXTK";
if ( memcmp( &guidContainerFormat, &GUID_ContainerFormatPng, sizeof(GUID) ) == 0 )
{
// Set Software name
(void)metawriter->SetMetadataByName( L"/tEXt/{str=Software}", &value );
// Set sRGB chunk
if ( sRGB )
{
value.vt = VT_UI1;
value.bVal = 0;
(void)metawriter->SetMetadataByName( L"/sRGB/RenderingIntent", &value );
}
}
#if defined(_XBOX_ONE) && defined(_TITLE)
else if ( memcmp( &guidContainerFormat, &GUID_ContainerFormatJpeg, sizeof(GUID) ) == 0 )
{
// Set Software name
(void)metawriter->SetMetadataByName( L"/app1/ifd/{ushort=305}", &value );
if ( sRGB )
{
// Set EXIF Colorspace of sRGB
value.vt = VT_UI2;
value.uiVal = 1;
(void)metawriter->SetMetadataByName( L"/app1/ifd/exif/{ushort=40961}", &value );
}
}
else if ( memcmp( &guidContainerFormat, &GUID_ContainerFormatTiff, sizeof(GUID) ) == 0 )
{
// Set Software name
(void)metawriter->SetMetadataByName( L"/ifd/{ushort=305}", &value );
if ( sRGB )
{
// Set EXIF Colorspace of sRGB
value.vt = VT_UI2;
value.uiVal = 1;
(void)metawriter->SetMetadataByName( L"/ifd/exif/{ushort=40961}", &value );
}
}
#else
else
{
// Set Software name
(void)metawriter->SetMetadataByName( L"System.ApplicationName", &value );
if ( sRGB )
{
// Set EXIF Colorspace of sRGB
value.vt = VT_UI2;
value.uiVal = 1;
(void)metawriter->SetMetadataByName( L"System.Image.ColorSpace", &value );
}
}
#endif
}
D3D11_MAPPED_SUBRESOURCE mapped;
hr = pContext->Map( pStaging.Get(), 0, D3D11_MAP_READ, 0, &mapped );
if ( FAILED(hr) )
return hr;
if ( memcmp( &targetGuid, &pfGuid, sizeof(WICPixelFormatGUID) ) != 0 )
{
// Conversion required to write
ComPtr<IWICBitmap> source;
hr = pWIC->CreateBitmapFromMemory( desc.Width, desc.Height, pfGuid,
mapped.RowPitch, mapped.RowPitch * desc.Height,
reinterpret_cast<BYTE*>( mapped.pData ), source.GetAddressOf() );
if ( FAILED(hr) )
{
pContext->Unmap( pStaging.Get(), 0 );
return hr;
}
ComPtr<IWICFormatConverter> FC;
hr = pWIC->CreateFormatConverter( FC.GetAddressOf() );
if ( FAILED(hr) )
{
pContext->Unmap( pStaging.Get(), 0 );
return hr;
}
BOOL canConvert = FALSE;
hr = FC->CanConvert( pfGuid, targetGuid, &canConvert );
if ( FAILED(hr) || !canConvert )
{
return E_UNEXPECTED;
}
hr = FC->Initialize( source.Get(), targetGuid, WICBitmapDitherTypeNone, 0, 0, WICBitmapPaletteTypeCustom );
if ( FAILED(hr) )
{
pContext->Unmap( pStaging.Get(), 0 );
return hr;
}
WICRect rect = { 0, 0, static_cast<INT>( desc.Width ), static_cast<INT>( desc.Height ) };
hr = frame->WriteSource( FC.Get(), &rect );
if ( FAILED(hr) )
{
pContext->Unmap( pStaging.Get(), 0 );
return hr;
}
}
else
{
// No conversion required
hr = frame->WritePixels( desc.Height, mapped.RowPitch, mapped.RowPitch * desc.Height, reinterpret_cast<BYTE*>( mapped.pData ) );
if ( FAILED(hr) )
return hr;
}
pContext->Unmap( pStaging.Get(), 0 );
hr = frame->Commit();
if ( FAILED(hr) )
return hr;
hr = encoder->Commit();
if ( FAILED(hr) )
return hr;
delonfail.clear();
return S_OK;
}
#endif // !WINAPI_FAMILY || (WINAPI_FAMILY != WINAPI_FAMILY_PHONE_APP) || (_WIN32_WINNT > _WIN32_WINNT_WIN8)