]> git.cworth.org Git - apitrace/blobdiff - thirdparty/directxtex/DirectXTex/DirectXTexCompress.cpp
thirdparty/directxtex: Import DirectXTex library.
[apitrace] / thirdparty / directxtex / DirectXTex / DirectXTexCompress.cpp
diff --git a/thirdparty/directxtex/DirectXTex/DirectXTexCompress.cpp b/thirdparty/directxtex/DirectXTex/DirectXTexCompress.cpp
new file mode 100644 (file)
index 0000000..b529086
--- /dev/null
@@ -0,0 +1,697 @@
+//-------------------------------------------------------------------------------------
+// DirectXTexCompress.cpp
+//  
+// DirectX Texture Library - Texture compression
+//
+// 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
+//-------------------------------------------------------------------------------------
+
+#include "directxtexp.h"
+
+#ifdef _OPENMP
+#include <omp.h>
+#pragma warning(disable : 4616 6001 6993)
+#endif
+
+#include "bc.h"
+
+namespace DirectX
+{
+
+inline static DWORD _GetBCFlags( _In_ DWORD compress )
+{
+    static_assert( TEX_COMPRESS_RGB_DITHER == BC_FLAGS_DITHER_RGB, "TEX_COMPRESS_* flags should match BC_FLAGS_*" );
+    static_assert( TEX_COMPRESS_A_DITHER == BC_FLAGS_DITHER_A, "TEX_COMPRESS_* flags should match BC_FLAGS_*"  );
+    static_assert( TEX_COMPRESS_DITHER == (BC_FLAGS_DITHER_RGB | BC_FLAGS_DITHER_A), "TEX_COMPRESS_* flags should match BC_FLAGS_*"  );
+    static_assert( TEX_COMPRESS_UNIFORM == BC_FLAGS_UNIFORM, "TEX_COMPRESS_* flags should match BC_FLAGS_*"  );
+    return ( compress & (BC_FLAGS_DITHER_RGB|BC_FLAGS_DITHER_A|BC_FLAGS_UNIFORM) );
+}
+
+
+//-------------------------------------------------------------------------------------
+static HRESULT _CompressBC( _In_ const Image& image, _In_ const Image& result, _In_ DWORD bcflags,
+                            _In_ float alphaRef, _In_ bool degenerate )
+{
+    if ( !image.pixels || !result.pixels )
+        return E_POINTER;
+
+    assert( image.width == result.width );
+    assert( image.height == result.height );
+
+    const DXGI_FORMAT format = image.format;
+    size_t sbpp = BitsPerPixel( format );
+    if ( !sbpp )
+        return E_FAIL;
+
+    if ( sbpp < 8 )
+    {
+        // We don't support compressing from monochrome (DXGI_FORMAT_R1_UNORM)
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    // Round to bytes
+    sbpp = ( sbpp + 7 ) / 8;
+
+    uint8_t *pDest = result.pixels;
+
+    // Determine BC format encoder
+    BC_ENCODE pfEncode;
+    size_t blocksize;
+    switch(result.format)
+    {
+    case DXGI_FORMAT_BC1_UNORM:
+    case DXGI_FORMAT_BC1_UNORM_SRGB:    pfEncode = nullptr;         blocksize = 8;   break;
+    case DXGI_FORMAT_BC2_UNORM:
+    case DXGI_FORMAT_BC2_UNORM_SRGB:    pfEncode = D3DXEncodeBC2;   blocksize = 16;  break;
+    case DXGI_FORMAT_BC3_UNORM:
+    case DXGI_FORMAT_BC3_UNORM_SRGB:    pfEncode = D3DXEncodeBC3;   blocksize = 16;  break;
+    case DXGI_FORMAT_BC4_UNORM:         pfEncode = D3DXEncodeBC4U;  blocksize = 8;   break;
+    case DXGI_FORMAT_BC4_SNORM:         pfEncode = D3DXEncodeBC4S;  blocksize = 8;   break;
+    case DXGI_FORMAT_BC5_UNORM:         pfEncode = D3DXEncodeBC5U;  blocksize = 16;  break;
+    case DXGI_FORMAT_BC5_SNORM:         pfEncode = D3DXEncodeBC5S;  blocksize = 16;  break;
+    case DXGI_FORMAT_BC6H_UF16:         pfEncode = D3DXEncodeBC6HU; blocksize = 16;  break;
+    case DXGI_FORMAT_BC6H_SF16:         pfEncode = D3DXEncodeBC6HS; blocksize = 16;  break;
+    case DXGI_FORMAT_BC7_UNORM:
+    case DXGI_FORMAT_BC7_UNORM_SRGB:    pfEncode = D3DXEncodeBC7;   blocksize = 16;  break;
+    default:
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    XMVECTOR temp[16];
+    const uint8_t *pSrc = image.pixels;
+    const size_t rowPitch = image.rowPitch;
+    for( size_t h=0; h < image.height; h += 4 )
+    {
+        const uint8_t *sptr = pSrc;
+        uint8_t* dptr = pDest;
+        for( size_t count = 0; count < rowPitch; count += sbpp*4 )
+        {
+            if ( !_LoadScanline( &temp[0], 4, sptr, rowPitch, format ) )
+                return E_FAIL;
+
+            if ( image.height > 1 )
+            {
+                if ( !_LoadScanline( &temp[4], 4, sptr + rowPitch, rowPitch, format ) )
+                    return E_FAIL;
+
+                if ( image.height > 2 )
+                {
+                    if ( !_LoadScanline( &temp[8], 4, sptr + rowPitch*2, rowPitch, format ) )
+                        return E_FAIL;
+
+                    if ( !_LoadScanline( &temp[12], 4, sptr + rowPitch*3, rowPitch, format ) )
+                        return E_FAIL;
+                }
+            }
+
+            if ( degenerate )
+            {
+                assert( image.width < 4 || image.height < 4 );
+                const size_t uSrc[] = { 0, 0, 0, 1 };
+
+                if ( image.width < 4 )
+                {
+                    for( size_t t=0; t < image.height && t < 4; ++t )
+                    {
+                        for( size_t s = image.width; s < 4; ++s )
+                        {
+                            temp[ t*4 + s ] = temp[ t*4 + uSrc[s] ]; 
+                        }
+                    }
+                }
+
+                if ( image.height < 4 )
+                {
+                    for( size_t t=image.height; t < 4; ++t )
+                    {
+                        for( size_t s =0; s < 4; ++s )
+                        {
+                            temp[ t*4 + s ] = temp[ uSrc[t]*4 + s ]; 
+                        }
+                    }
+                }
+            }
+
+            _ConvertScanline( temp, 16, result.format, format, 0 );
+            
+            if ( pfEncode )
+                pfEncode( dptr, temp, bcflags );
+            else
+                D3DXEncodeBC1( dptr, temp, alphaRef, bcflags );
+
+            sptr += sbpp*4;
+            dptr += blocksize;
+        }
+
+        pSrc += rowPitch*4;
+        pDest += result.rowPitch;
+    }
+
+    return S_OK;
+}
+
+
+//-------------------------------------------------------------------------------------
+#ifdef _OPENMP
+static HRESULT _CompressBC_Parallel( _In_ const Image& image, _In_ const Image& result, _In_ DWORD bcflags,
+                                     _In_ float alphaRef )
+{
+    if ( !image.pixels || !result.pixels )
+        return E_POINTER;
+
+    // Parallel version doesn't support degenerate case
+    assert( ((image.width % 4) == 0) && ((image.height % 4) == 0 ) );
+
+    assert( image.width == result.width );
+    assert( image.height == result.height );
+
+    const DXGI_FORMAT format = image.format;
+    size_t sbpp = BitsPerPixel( format );
+    if ( !sbpp )
+        return E_FAIL;
+
+    if ( sbpp < 8 )
+    {
+        // We don't support compressing from monochrome (DXGI_FORMAT_R1_UNORM)
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    // Round to bytes
+    sbpp = ( sbpp + 7 ) / 8;
+
+    // Determine BC format encoder
+    BC_ENCODE pfEncode;
+    size_t blocksize;
+    switch(result.format)
+    {
+    case DXGI_FORMAT_BC1_UNORM:
+    case DXGI_FORMAT_BC1_UNORM_SRGB:    pfEncode = nullptr;         blocksize = 8;   break;
+    case DXGI_FORMAT_BC2_UNORM:
+    case DXGI_FORMAT_BC2_UNORM_SRGB:    pfEncode = D3DXEncodeBC2;   blocksize = 16;  break;
+    case DXGI_FORMAT_BC3_UNORM:
+    case DXGI_FORMAT_BC3_UNORM_SRGB:    pfEncode = D3DXEncodeBC3;   blocksize = 16;  break;
+    case DXGI_FORMAT_BC4_UNORM:         pfEncode = D3DXEncodeBC4U;  blocksize = 8;   break;
+    case DXGI_FORMAT_BC4_SNORM:         pfEncode = D3DXEncodeBC4S;  blocksize = 8;   break;
+    case DXGI_FORMAT_BC5_UNORM:         pfEncode = D3DXEncodeBC5U;  blocksize = 16;  break;
+    case DXGI_FORMAT_BC5_SNORM:         pfEncode = D3DXEncodeBC5S;  blocksize = 16;  break;
+    case DXGI_FORMAT_BC6H_UF16:         pfEncode = D3DXEncodeBC6HU; blocksize = 16;  break;
+    case DXGI_FORMAT_BC6H_SF16:         pfEncode = D3DXEncodeBC6HS; blocksize = 16;  break;
+    case DXGI_FORMAT_BC7_UNORM:
+    case DXGI_FORMAT_BC7_UNORM_SRGB:    pfEncode = D3DXEncodeBC7;   blocksize = 16;  break;
+    default:
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    // Refactored version of loop to support parallel independance
+    const size_t nBlocks = std::max<size_t>(1, image.width / 4) * std::max<size_t>(1, image.height / 4);
+
+    bool fail = false;
+
+#pragma omp parallel for
+    for( int nb=0; nb < static_cast<int>( nBlocks ); ++nb )
+    {
+        const size_t nbWidth = std::max<size_t>(1, image.width / 4);
+
+        const size_t y = nb / nbWidth;
+        const size_t x = nb - (y*nbWidth);
+
+        assert( x < image.width && y < image.height );
+
+        size_t rowPitch = image.rowPitch;
+        const uint8_t *pSrc = image.pixels + (y*4*rowPitch) + (x*4*sbpp);
+
+        uint8_t *pDest = result.pixels + (nb*blocksize);
+
+        XMVECTOR temp[16];
+        if ( !_LoadScanline( &temp[0], 4, pSrc, rowPitch, format ) )
+            fail = true;
+
+        if ( !_LoadScanline( &temp[4], 4, pSrc + rowPitch, rowPitch, format ) )
+            fail = true;
+
+        if ( !_LoadScanline( &temp[8], 4, pSrc + rowPitch*2, rowPitch, format ) )
+            fail = true;
+
+        if ( !_LoadScanline( &temp[12], 4, pSrc + rowPitch*3, rowPitch, format ) )
+            fail = true;
+
+        _ConvertScanline( temp, 16, result.format, format, 0 );
+            
+        if ( pfEncode )
+            pfEncode( pDest, temp, bcflags );
+        else
+            D3DXEncodeBC1( pDest, temp, alphaRef, bcflags );
+    }
+
+    return (fail) ? E_FAIL : S_OK;
+}
+
+#endif // _OPENMP
+
+
+//-------------------------------------------------------------------------------------
+static DXGI_FORMAT _DefaultDecompress( _In_ DXGI_FORMAT format )
+{
+    switch( format )
+    {
+    case DXGI_FORMAT_BC1_TYPELESS:
+    case DXGI_FORMAT_BC1_UNORM:
+    case DXGI_FORMAT_BC2_TYPELESS:
+    case DXGI_FORMAT_BC2_UNORM:
+    case DXGI_FORMAT_BC3_TYPELESS:
+    case DXGI_FORMAT_BC3_UNORM:
+    case DXGI_FORMAT_BC7_TYPELESS:
+    case DXGI_FORMAT_BC7_UNORM:
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+    case DXGI_FORMAT_BC1_UNORM_SRGB:
+    case DXGI_FORMAT_BC2_UNORM_SRGB:
+    case DXGI_FORMAT_BC3_UNORM_SRGB:
+    case DXGI_FORMAT_BC7_UNORM_SRGB:
+        return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+
+    case DXGI_FORMAT_BC4_TYPELESS:
+    case DXGI_FORMAT_BC4_UNORM:
+        return DXGI_FORMAT_R8_UNORM;
+
+    case DXGI_FORMAT_BC4_SNORM:
+        return DXGI_FORMAT_R8_SNORM;
+
+    case DXGI_FORMAT_BC5_TYPELESS:
+    case DXGI_FORMAT_BC5_UNORM:
+        return DXGI_FORMAT_R8G8_UNORM;
+
+    case DXGI_FORMAT_BC5_SNORM:
+        return DXGI_FORMAT_R8G8_SNORM;
+
+    case DXGI_FORMAT_BC6H_TYPELESS:
+    case DXGI_FORMAT_BC6H_UF16:
+    case DXGI_FORMAT_BC6H_SF16:
+        // We could use DXGI_FORMAT_R32G32B32_FLOAT here since BC6H is always Alpha 1.0,
+        // but this format is more supported by viewers
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+
+    default:
+        return DXGI_FORMAT_UNKNOWN;
+    }
+}
+
+
+//-------------------------------------------------------------------------------------
+static HRESULT _DecompressBC( _In_ const Image& cImage, _In_ const Image& result )
+{
+    if ( !cImage.pixels || !result.pixels )
+        return E_POINTER;
+
+    assert( cImage.width == result.width );
+    assert( cImage.height == result.height );
+
+    // Image must be a multiple of 4 (degenerate cases of 1x1, 1x2, 2x1, and 2x2 are allowed)
+    size_t width = cImage.width;
+    if ( (width % 4) != 0 )
+    {
+        if ( width != 1 && width != 2 )
+            return E_INVALIDARG;
+    }
+
+    size_t height = cImage.height;
+    if ( (height % 4) != 0 )
+    {
+        if ( height != 1 && height != 2 )
+            return E_INVALIDARG;
+    }
+
+    const DXGI_FORMAT format = result.format;
+    size_t dbpp = BitsPerPixel( format );
+    if ( !dbpp )
+        return E_FAIL;
+
+    if ( dbpp < 8 )
+    {
+        // We don't support decompressing to monochrome (DXGI_FORMAT_R1_UNORM)
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    // Round to bytes
+    dbpp = ( dbpp + 7 ) / 8;
+
+    uint8_t *pDest = result.pixels;
+    if ( !pDest )
+        return E_POINTER;
+
+    // Promote "typeless" BC formats
+    DXGI_FORMAT cformat;
+    switch( cImage.format )
+    {
+    case DXGI_FORMAT_BC1_TYPELESS:  cformat = DXGI_FORMAT_BC1_UNORM; break;
+    case DXGI_FORMAT_BC2_TYPELESS:  cformat = DXGI_FORMAT_BC2_UNORM; break;
+    case DXGI_FORMAT_BC3_TYPELESS:  cformat = DXGI_FORMAT_BC3_UNORM; break;
+    case DXGI_FORMAT_BC4_TYPELESS:  cformat = DXGI_FORMAT_BC4_UNORM; break;
+    case DXGI_FORMAT_BC5_TYPELESS:  cformat = DXGI_FORMAT_BC5_UNORM; break;
+    case DXGI_FORMAT_BC6H_TYPELESS: cformat = DXGI_FORMAT_BC6H_UF16; break;
+    case DXGI_FORMAT_BC7_TYPELESS:  cformat = DXGI_FORMAT_BC7_UNORM; break;
+    default:                        cformat = cImage.format;         break;
+    }
+
+    // Determine BC format decoder
+    BC_DECODE pfDecode;
+    size_t sbpp;
+    switch(cformat)
+    {
+    case DXGI_FORMAT_BC1_UNORM:
+    case DXGI_FORMAT_BC1_UNORM_SRGB:    pfDecode = D3DXDecodeBC1;   sbpp = 8;   break;
+    case DXGI_FORMAT_BC2_UNORM:
+    case DXGI_FORMAT_BC2_UNORM_SRGB:    pfDecode = D3DXDecodeBC2;   sbpp = 16;  break;
+    case DXGI_FORMAT_BC3_UNORM:
+    case DXGI_FORMAT_BC3_UNORM_SRGB:    pfDecode = D3DXDecodeBC3;   sbpp = 16;  break;
+    case DXGI_FORMAT_BC4_UNORM:         pfDecode = D3DXDecodeBC4U;  sbpp = 8;   break;
+    case DXGI_FORMAT_BC4_SNORM:         pfDecode = D3DXDecodeBC4S;  sbpp = 8;   break;
+    case DXGI_FORMAT_BC5_UNORM:         pfDecode = D3DXDecodeBC5U;  sbpp = 16;  break;
+    case DXGI_FORMAT_BC5_SNORM:         pfDecode = D3DXDecodeBC5S;  sbpp = 16;  break;
+    case DXGI_FORMAT_BC6H_UF16:         pfDecode = D3DXDecodeBC6HU; sbpp = 16;  break;
+    case DXGI_FORMAT_BC6H_SF16:         pfDecode = D3DXDecodeBC6HS; sbpp = 16;  break;
+    case DXGI_FORMAT_BC7_UNORM:
+    case DXGI_FORMAT_BC7_UNORM_SRGB:    pfDecode = D3DXDecodeBC7;   sbpp = 16;  break;
+    default:
+        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
+    }
+
+    XMVECTOR temp[16];
+    const uint8_t *pSrc = cImage.pixels;
+    const size_t rowPitch = result.rowPitch;
+    for( size_t h=0; h < cImage.height; h += 4 )
+    {
+        const uint8_t *sptr = pSrc;
+        uint8_t* dptr = pDest;
+        for( size_t count = 0; count < cImage.rowPitch; count += sbpp )
+        {
+            pfDecode( temp, sptr );
+            _ConvertScanline( temp, 16, format, cformat, 0 );
+
+            if ( !_StoreScanline( dptr, rowPitch, format, &temp[0], 4 ) )
+                return E_FAIL;
+
+            if ( result.height > 1 )
+            {
+                if ( !_StoreScanline( dptr + rowPitch, rowPitch, format, &temp[4], 4 ) )
+                    return E_FAIL;
+
+                if ( result.height > 2 )
+                {
+                    if ( !_StoreScanline( dptr + rowPitch*2, rowPitch, format, &temp[8], 4 ) )
+                        return E_FAIL;
+
+                    if ( !_StoreScanline( dptr + rowPitch*3, rowPitch, format, &temp[12], 4 ) )
+                        return E_FAIL;
+                }
+            }
+
+            sptr += sbpp;
+            dptr += dbpp*4;
+        }
+
+        pSrc += cImage.rowPitch;
+        pDest += rowPitch*4;
+    }
+
+    return S_OK;
+}
+
+
+//=====================================================================================
+// Entry-points
+//=====================================================================================
+
+//-------------------------------------------------------------------------------------
+// Compression
+//-------------------------------------------------------------------------------------
+HRESULT Compress( const Image& srcImage, DXGI_FORMAT format, DWORD compress, float alphaRef, ScratchImage& image )
+{
+    if ( IsCompressed(srcImage.format) || !IsCompressed(format) || IsTypeless(format) )
+        return E_INVALIDARG;
+
+    // Image size must be a multiple of 4 (degenerate cases for mipmaps are allowed)
+    bool degenerate = false;
+
+    size_t width = srcImage.width;
+    if ( (width % 4) != 0 )
+    {
+        if ( width != 1 && width != 2 )
+            return E_INVALIDARG;
+
+        degenerate = true;
+    }
+
+    size_t height = srcImage.height;
+    if ( (height % 4) != 0 )
+    {
+        if ( height != 1 && height != 2 )
+            return E_INVALIDARG;
+
+        degenerate = true;
+    }
+
+    // Create compressed image
+    HRESULT hr = image.Initialize2D( format, width, height, 1, 1 );
+    if ( FAILED(hr) )
+        return hr;
+
+    const Image *img = image.GetImage( 0, 0, 0 );
+    if ( !img )
+    {
+        image.Release();
+        return E_POINTER;
+    }
+
+    // Compress single image
+    if ( (compress & TEX_COMPRESS_PARALLEL) && !degenerate )
+    {
+#ifndef _OPENMP
+        return E_NOTIMPL;
+#else
+        hr = _CompressBC_Parallel( srcImage, *img, _GetBCFlags( compress ), alphaRef );
+#endif // _OPENMP
+    }
+    else
+    {
+        hr = _CompressBC( srcImage, *img, _GetBCFlags( compress ), alphaRef, degenerate );
+    }
+
+    if ( FAILED(hr) )
+        image.Release();
+
+    return hr;
+}
+
+HRESULT Compress( const Image* srcImages, size_t nimages, const TexMetadata& metadata,
+                  DXGI_FORMAT format, DWORD compress, float alphaRef, ScratchImage& cImages )
+{
+    if ( !srcImages || !nimages )
+        return E_INVALIDARG;
+
+    if ( !IsCompressed(format) || IsTypeless(format) )
+        return E_INVALIDARG;
+
+    // Image size must be a multiple of 4 (degenerate cases for mipmaps are allowed)
+    size_t width = srcImages[0].width;
+    if ( (width % 4) != 0 )
+    {
+        if ( width != 1 && width != 2 )
+            return E_INVALIDARG;
+    }
+
+    size_t height = srcImages[0].height;
+    if ( (height % 4) != 0 )
+    {
+        if ( height != 1 && height != 2 )
+            return E_INVALIDARG;
+    }
+
+    cImages.Release();
+
+    TexMetadata mdata2 = metadata;
+    mdata2.format = format;
+    HRESULT hr = cImages.Initialize( mdata2 );
+    if ( FAILED(hr) )
+        return hr;
+
+    if ( nimages != cImages.GetImageCount() )
+    {
+        cImages.Release();
+        return E_FAIL;
+    }
+
+    const Image* dest = cImages.GetImages();
+    if ( !dest  )
+    {
+        cImages.Release();
+        return E_POINTER;
+    }
+
+    for( size_t index=0; index < nimages; ++index )
+    {
+        assert( dest[ index ].format == format );
+
+        const Image& src = srcImages[ index ];
+
+        height = src.height;
+        width = src.width;
+        if ( width != dest[ index ].width || height != dest[ index ].height )
+        {
+            cImages.Release();
+            return E_FAIL;
+        }
+
+        bool degenerate = ((height < 4) || (width < 4)) != 0;
+
+        if ( (compress & TEX_COMPRESS_PARALLEL) && !degenerate)
+        {
+#ifndef _OPENMP
+            return E_NOTIMPL;
+#else
+            if ( compress & TEX_COMPRESS_PARALLEL )
+            {
+                hr = _CompressBC_Parallel( src, dest[ index ], _GetBCFlags( compress ), alphaRef );
+                if ( FAILED(hr) )
+                {
+                    cImages.Release();
+                    return  hr;
+                }
+            }
+#endif // _OPENMP
+        }
+        else
+        {
+            hr = _CompressBC( src, dest[ index ], _GetBCFlags( compress ), alphaRef, degenerate );
+            if ( FAILED(hr) )
+            {
+                cImages.Release();
+                return hr;
+            }
+        }
+    }
+
+    return S_OK;
+}
+
+
+//-------------------------------------------------------------------------------------
+// Decompression
+//-------------------------------------------------------------------------------------
+HRESULT Decompress( const Image& cImage, DXGI_FORMAT format, ScratchImage& image )
+{
+    if ( IsCompressed(format) || IsTypeless(format) )
+        return E_INVALIDARG;
+
+    if ( format == DXGI_FORMAT_UNKNOWN )
+    {
+        // Pick a default decompressed format based on BC input format
+        format = _DefaultDecompress( cImage.format );
+        if ( format == DXGI_FORMAT_UNKNOWN )
+        {
+            // Input is not a compressed format
+            return E_INVALIDARG;
+        }
+    }
+    else if ( !IsCompressed(cImage.format) || !IsValid(format) )
+        return E_INVALIDARG;
+
+    // Create decompressed image
+    HRESULT hr = image.Initialize2D( format, cImage.width, cImage.height, 1, 1 );
+    if ( FAILED(hr) )
+        return hr;
+
+    const Image *img = image.GetImage( 0, 0, 0 );
+    if ( !img )
+    {
+        image.Release();
+        return E_POINTER;
+    }
+
+    // Decompress single image
+    hr = _DecompressBC( cImage, *img );
+    if ( FAILED(hr) )
+        image.Release();
+
+    return hr;
+}
+
+HRESULT Decompress( const Image* cImages, size_t nimages, const TexMetadata& metadata,
+                    DXGI_FORMAT format, ScratchImage& images )
+{
+    if ( !cImages || !nimages )
+        return E_INVALIDARG;
+
+    if ( IsCompressed(format) || IsTypeless(format) )
+        return E_INVALIDARG;
+
+    if ( format == DXGI_FORMAT_UNKNOWN )
+    {
+        // Pick a default decompressed format based on BC input format
+        format = _DefaultDecompress( cImages[0].format );
+        if ( format == DXGI_FORMAT_UNKNOWN )
+        {
+            // Input is not a compressed format
+            return E_FAIL;
+        }
+    }
+    else if ( !IsValid(format) )
+        return E_INVALIDARG;
+
+    images.Release();
+
+    TexMetadata mdata2 = metadata;
+    mdata2.format = format;
+    HRESULT hr = images.Initialize( mdata2 );
+    if ( FAILED(hr) )
+        return hr;
+
+    if ( nimages != images.GetImageCount() )
+    {
+        images.Release();
+        return E_FAIL;
+    }
+
+    const Image* dest = images.GetImages();
+    if ( !dest )
+    {
+        images.Release();
+        return E_POINTER;
+    }
+
+    for( size_t index=0; index < nimages; ++index )
+    {
+        assert( dest[ index ].format == format );
+
+        const Image& src = cImages[ index ];
+        if ( !IsCompressed( src.format ) )
+        {
+            images.Release();
+            return E_FAIL;
+        }
+
+        if ( src.width != dest[ index ].width || src.height != dest[ index ].height )
+        {
+            images.Release();
+            return E_FAIL;
+        }
+
+        hr = _DecompressBC( src, dest[ index ] );
+        if ( FAILED(hr) )
+        {
+            images.Release();
+            return hr;
+        }
+    }
+
+    return S_OK;
+}
+
+}; // namespace