//------------------------------------------------------------------------------------- // DirectXTexCompress.cpp // // DirectX Texture Library - Texture compression // // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "DirectXTexP.h" #ifdef _OPENMP #include #pragma warning(disable : 4616 6993) #endif #include "BC.h" using namespace DirectX; using namespace DirectX::Internal; namespace { constexpr uint32_t GetBCFlags(_In_ TEX_COMPRESS_FLAGS compress) noexcept { static_assert(static_cast(TEX_COMPRESS_RGB_DITHER) == static_cast(BC_FLAGS_DITHER_RGB), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); static_assert(static_cast(TEX_COMPRESS_A_DITHER) == static_cast(BC_FLAGS_DITHER_A), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); static_assert(static_cast(TEX_COMPRESS_DITHER) == static_cast(BC_FLAGS_DITHER_RGB | BC_FLAGS_DITHER_A), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); static_assert(static_cast(TEX_COMPRESS_UNIFORM) == static_cast(BC_FLAGS_UNIFORM), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); static_assert(static_cast(TEX_COMPRESS_BC7_USE_3SUBSETS) == static_cast(BC_FLAGS_USE_3SUBSETS), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); static_assert(static_cast(TEX_COMPRESS_BC7_QUICK) == static_cast(BC_FLAGS_FORCE_BC7_MODE6), "TEX_COMPRESS_* flags should match BC_FLAGS_*"); return (compress & (BC_FLAGS_DITHER_RGB | BC_FLAGS_DITHER_A | BC_FLAGS_UNIFORM | BC_FLAGS_USE_3SUBSETS | BC_FLAGS_FORCE_BC7_MODE6)); } constexpr TEX_FILTER_FLAGS GetSRGBFlags(_In_ TEX_COMPRESS_FLAGS compress) noexcept { static_assert(TEX_FILTER_SRGB_IN == 0x1000000, "TEX_FILTER_SRGB flag values don't match TEX_FILTER_SRGB_MASK"); static_assert(static_cast(TEX_COMPRESS_SRGB_IN) == static_cast(TEX_FILTER_SRGB_IN), "TEX_COMPRESS_SRGB* should match TEX_FILTER_SRGB*"); static_assert(static_cast(TEX_COMPRESS_SRGB_OUT) == static_cast(TEX_FILTER_SRGB_OUT), "TEX_COMPRESS_SRGB* should match TEX_FILTER_SRGB*"); static_assert(static_cast(TEX_COMPRESS_SRGB) == static_cast(TEX_FILTER_SRGB), "TEX_COMPRESS_SRGB* should match TEX_FILTER_SRGB*"); return static_cast(compress & TEX_FILTER_SRGB_MASK); } inline bool DetermineEncoderSettings(_In_ DXGI_FORMAT format, _Out_ BC_ENCODE& pfEncode, _Out_ size_t& blocksize, _Out_ TEX_FILTER_FLAGS& cflags) noexcept { switch (format) { case DXGI_FORMAT_BC1_UNORM: case DXGI_FORMAT_BC1_UNORM_SRGB: pfEncode = nullptr; blocksize = 8; cflags = TEX_FILTER_DEFAULT; break; case DXGI_FORMAT_BC2_UNORM: case DXGI_FORMAT_BC2_UNORM_SRGB: pfEncode = D3DXEncodeBC2; blocksize = 16; cflags = TEX_FILTER_DEFAULT; break; case DXGI_FORMAT_BC3_UNORM: case DXGI_FORMAT_BC3_UNORM_SRGB: pfEncode = D3DXEncodeBC3; blocksize = 16; cflags = TEX_FILTER_DEFAULT; break; case DXGI_FORMAT_BC4_UNORM: pfEncode = D3DXEncodeBC4U; blocksize = 8; cflags = TEX_FILTER_RGB_COPY_RED; break; case DXGI_FORMAT_BC4_SNORM: pfEncode = D3DXEncodeBC4S; blocksize = 8; cflags = TEX_FILTER_RGB_COPY_RED; break; case DXGI_FORMAT_BC5_UNORM: pfEncode = D3DXEncodeBC5U; blocksize = 16; cflags = TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_GREEN; break; case DXGI_FORMAT_BC5_SNORM: pfEncode = D3DXEncodeBC5S; blocksize = 16; cflags = TEX_FILTER_RGB_COPY_RED | TEX_FILTER_RGB_COPY_GREEN; break; case DXGI_FORMAT_BC6H_UF16: pfEncode = D3DXEncodeBC6HU; blocksize = 16; cflags = TEX_FILTER_DEFAULT; break; case DXGI_FORMAT_BC6H_SF16: pfEncode = D3DXEncodeBC6HS; blocksize = 16; cflags = TEX_FILTER_DEFAULT; break; case DXGI_FORMAT_BC7_UNORM: case DXGI_FORMAT_BC7_UNORM_SRGB: pfEncode = D3DXEncodeBC7; blocksize = 16; cflags = TEX_FILTER_DEFAULT; break; default: pfEncode = nullptr; blocksize = 0; cflags = TEX_FILTER_DEFAULT; return false; } return true; } //------------------------------------------------------------------------------------- HRESULT CompressBC( const Image& image, const Image& result, uint32_t bcflags, TEX_FILTER_FLAGS srgb, float threshold, const std::function& statusCallback) noexcept { 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_E_NOT_SUPPORTED; } // Round to bytes sbpp = (sbpp + 7) / 8; uint8_t *pDest = result.pixels; // Determine BC format encoder BC_ENCODE pfEncode; size_t blocksize; TEX_FILTER_FLAGS cflags; if (!DetermineEncoderSettings(result.format, pfEncode, blocksize, cflags)) return HRESULT_E_NOT_SUPPORTED; XM_ALIGNED_DATA(16) XMVECTOR temp[16]; const uint8_t *pSrc = image.pixels; const uint8_t *pEnd = image.pixels + image.slicePitch; const size_t rowPitch = image.rowPitch; for (size_t h = 0; h < image.height; h += 4) { if (statusCallback) { if (!statusCallback(h, image.height)) { return E_ABORT; } } const uint8_t *sptr = pSrc; uint8_t* dptr = pDest; const size_t ph = std::min(4, image.height - h); size_t w = 0; for (size_t count = 0; (count < result.rowPitch) && (w < image.width); count += blocksize, w += 4) { const size_t pw = std::min(4, image.width - w); assert(pw > 0 && ph > 0); const ptrdiff_t bytesLeft = pEnd - sptr; assert(bytesLeft > 0); size_t bytesToRead = std::min(rowPitch, static_cast(bytesLeft)); if (!LoadScanline(&temp[0], pw, sptr, bytesToRead, format)) return E_FAIL; if (ph > 1) { bytesToRead = std::min(rowPitch, static_cast(bytesLeft) - rowPitch); if (!LoadScanline(&temp[4], pw, sptr + rowPitch, bytesToRead, format)) return E_FAIL; if (ph > 2) { bytesToRead = std::min(rowPitch, static_cast(bytesLeft) - rowPitch * 2); if (!LoadScanline(&temp[8], pw, sptr + rowPitch * 2, bytesToRead, format)) return E_FAIL; if (ph > 3) { bytesToRead = std::min(rowPitch, static_cast(bytesLeft) - rowPitch * 3); if (!LoadScanline(&temp[12], pw, sptr + rowPitch * 3, bytesToRead, format)) return E_FAIL; } } } if (pw != 4 || ph != 4) { // Replicate pixels for partial block static const size_t uSrc[] = { 0, 0, 0, 1 }; if (pw < 4) { for (size_t t = 0; t < ph && t < 4; ++t) { for (size_t s = pw; s < 4; ++s) { #pragma prefast(suppress: 26000, "PREFAST false positive") temp[(t << 2) | s] = temp[(t << 2) | uSrc[s]]; } } } if (ph < 4) { for (size_t t = ph; t < 4; ++t) { for (size_t s = 0; s < 4; ++s) { #pragma prefast(suppress: 26000, "PREFAST false positive") temp[(t << 2) | s] = temp[(uSrc[t] << 2) | s]; } } } } ConvertScanline(temp, 16, result.format, format, cflags | srgb); if (pfEncode) pfEncode(dptr, temp, bcflags); else D3DXEncodeBC1(dptr, temp, threshold, bcflags); sptr += sbpp * 4; dptr += blocksize; } pSrc += rowPitch * 4; pDest += result.rowPitch; } return S_OK; } //------------------------------------------------------------------------------------- #ifdef _OPENMP HRESULT CompressBC_Parallel( const Image& image, const Image& result, uint32_t bcflags, TEX_FILTER_FLAGS srgb, float threshold, const std::function& statusCallback) noexcept { 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_E_NOT_SUPPORTED; } // Round to bytes sbpp = (sbpp + 7) / 8; const uint8_t *pEnd = image.pixels + image.slicePitch; // Determine BC format encoder BC_ENCODE pfEncode; size_t blocksize; TEX_FILTER_FLAGS cflags; if (!DetermineEncoderSettings(result.format, pfEncode, blocksize, cflags)) return HRESULT_E_NOT_SUPPORTED; // Refactored version of loop to support parallel independance const size_t nBlocks = std::max(1, (image.width + 3) / 4) * std::max(1, (image.height + 3) / 4); bool fail = false; size_t progress = 0; bool abort = false; const size_t progressTotal = std::max(1, (image.height + 3) / 4); #pragma omp parallel for shared(progress) for (int nb = 0; nb < static_cast(nBlocks); ++nb) { #pragma omp flush (abort) if (abort) { // Short circuit the loop body if an abort is requested. // OpenMP 2.0 does not support cancellation of a 'parallel for' loop. continue; } const int nbWidth = std::max(1, int((image.width + 3) / 4)); int y = nb / nbWidth; const int x = (nb - (y*nbWidth)) * 4; y *= 4; assert((x >= 0) && (x < int(image.width))); assert((y >= 0) && (y < int(image.height))); const size_t rowPitch = image.rowPitch; const uint8_t *pSrc = image.pixels + (size_t(y)*rowPitch) + (size_t(x)*sbpp); uint8_t *pDest = result.pixels + (size_t(nb)*blocksize); const size_t ph = std::min(4, image.height - size_t(y)); const size_t pw = std::min(4, image.width - size_t(x)); assert(pw > 0 && ph > 0); const ptrdiff_t bytesLeft = pEnd - pSrc; assert(bytesLeft > 0); size_t bytesToRead = std::min(rowPitch, size_t(bytesLeft)); XM_ALIGNED_DATA(16) XMVECTOR temp[16]; if (!LoadScanline(&temp[0], pw, pSrc, bytesToRead, format)) fail = true; if (ph > 1) { bytesToRead = std::min(rowPitch, size_t(bytesLeft) - rowPitch); if (!LoadScanline(&temp[4], pw, pSrc + rowPitch, bytesToRead, format)) fail = true; if (ph > 2) { bytesToRead = std::min(rowPitch, size_t(bytesLeft) - rowPitch * 2); if (!LoadScanline(&temp[8], pw, pSrc + rowPitch * 2, bytesToRead, format)) fail = true; if (ph > 3) { bytesToRead = std::min(rowPitch, size_t(bytesLeft) - rowPitch * 3); if (!LoadScanline(&temp[12], pw, pSrc + rowPitch * 3, bytesToRead, format)) fail = true; } } } if (pw != 4 || ph != 4) { // Replicate pixels for partial block static const size_t uSrc[] = { 0, 0, 0, 1 }; if (pw < 4) { for (size_t t = 0; t < ph && t < 4; ++t) { for (size_t s = pw; s < 4; ++s) { temp[(t << 2) | s] = temp[(t << 2) | uSrc[s]]; } } } if (ph < 4) { for (size_t t = ph; t < 4; ++t) { for (size_t s = 0; s < 4; ++s) { temp[(t << 2) | s] = temp[(uSrc[t] << 2) | s]; } } } } ConvertScanline(temp, 16, result.format, format, cflags | srgb); if (pfEncode) pfEncode(pDest, temp, bcflags); else D3DXEncodeBC1(pDest, temp, threshold, bcflags); // Report progress when a new row is reached. if (x == 0 && statusCallback) { #pragma omp atomic progress += 4; if (!statusCallback(progress, progressTotal)) { abort = true; #pragma omp flush (abort) } } } if (abort) { return E_ABORT; } else { return (fail) ? E_FAIL : S_OK; } } #endif // _OPENMP //------------------------------------------------------------------------------------- DXGI_FORMAT DefaultDecompress(_In_ DXGI_FORMAT format) noexcept { 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; } } //------------------------------------------------------------------------------------- HRESULT DecompressBC(_In_ const Image& cImage, _In_ const Image& result) noexcept { if (!cImage.pixels || !result.pixels) return E_POINTER; assert(cImage.width == result.width); assert(cImage.height == result.height); 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_E_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_E_NOT_SUPPORTED; } XM_ALIGNED_DATA(16) 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; const size_t ph = std::min(4, cImage.height - h); size_t w = 0; for (size_t count = 0; (count < cImage.rowPitch) && (w < cImage.width); count += sbpp, w += 4) { pfDecode(temp, sptr); ConvertScanline(temp, 16, format, cformat, TEX_FILTER_DEFAULT); const size_t pw = std::min(4, cImage.width - w); assert(pw > 0 && ph > 0); if (!StoreScanline(dptr, rowPitch, format, &temp[0], pw)) return E_FAIL; if (ph > 1) { if (!StoreScanline(dptr + rowPitch, rowPitch, format, &temp[4], pw)) return E_FAIL; if (ph > 2) { if (!StoreScanline(dptr + rowPitch * 2, rowPitch, format, &temp[8], pw)) return E_FAIL; if (ph > 3) { if (!StoreScanline(dptr + rowPitch * 3, rowPitch, format, &temp[12], pw)) return E_FAIL; } } } sptr += sbpp; dptr += dbpp * 4; } pSrc += cImage.rowPitch; pDest += rowPitch * 4; } return S_OK; } } //------------------------------------------------------------------------------------- bool DirectX::Internal::IsAlphaAllOpaqueBC(_In_ const Image& cImage) noexcept { if (!cImage.pixels) return false; // 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_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_BC7_UNORM: case DXGI_FORMAT_BC7_UNORM_SRGB: pfDecode = D3DXDecodeBC7; sbpp = 16; break; default: // BC4, BC5, and BC6 don't have alpha channels return false; } // Scan blocks for non-opaque alpha static const XMVECTORF32 threshold = { { { 0.99f, 0.99f, 0.99f, 0.99f } } }; XM_ALIGNED_DATA(16) XMVECTOR temp[16]; const uint8_t* pPixels = cImage.pixels; for (size_t h = 0; h < cImage.height; h += 4) { const uint8_t* ptr = pPixels; const size_t ph = std::min(4, cImage.height - h); size_t w = 0; for (size_t count = 0; (count < cImage.rowPitch) && (w < cImage.width); count += sbpp, w += 4) { pfDecode(temp, ptr); const size_t pw = std::min(4, cImage.width - w); assert(pw > 0 && ph > 0); if (pw == 4 && ph == 4) { // Full blocks for (size_t j = 0; j < 16; ++j) { const XMVECTOR alpha = XMVectorSplatW(temp[j]); if (XMVector4Less(alpha, threshold)) return false; } } else { // Handle partial blocks for (size_t y = 0; y < ph; ++y) { for (size_t x = 0; x < pw; ++x) { const XMVECTOR alpha = XMVectorSplatW(temp[y * 4 + x]); if (XMVector4Less(alpha, threshold)) return false; } } } ptr += sbpp; } pPixels += cImage.rowPitch; } return true; } //===================================================================================== // Entry-points //===================================================================================== //------------------------------------------------------------------------------------- // Compression //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::Compress( const Image& srcImage, DXGI_FORMAT format, TEX_COMPRESS_FLAGS compress, float threshold, ScratchImage& image) noexcept { CompressOptions options = {}; options.flags = compress; options.threshold = threshold; return CompressEx(srcImage, format, options, image, nullptr); } _Use_decl_annotations_ HRESULT DirectX::Compress( const Image* srcImages, size_t nimages, const TexMetadata& metadata, DXGI_FORMAT format, TEX_COMPRESS_FLAGS compress, float threshold, ScratchImage& cImages) noexcept { CompressOptions options = {}; options.flags = compress; options.threshold = threshold; return CompressEx(srcImages, nimages, metadata, format, options, cImages, nullptr); } _Use_decl_annotations_ HRESULT DirectX::CompressEx( const Image& srcImage, DXGI_FORMAT format, const CompressOptions& options, ScratchImage& image, std::function statusCallback) { if (IsCompressed(srcImage.format) || !IsCompressed(format)) return E_INVALIDARG; if (IsTypeless(format) || IsTypeless(srcImage.format) || IsPlanar(srcImage.format) || IsPalettized(srcImage.format)) return HRESULT_E_NOT_SUPPORTED; // Create compressed image HRESULT hr = image.Initialize2D(format, srcImage.width, srcImage.height, 1, 1); if (FAILED(hr)) return hr; const Image *img = image.GetImage(0, 0, 0); if (!img) { image.Release(); return E_POINTER; } if (statusCallback) { if (!statusCallback(0, img->height)) { image.Release(); return E_ABORT; } } // Compress single image if (options.flags & TEX_COMPRESS_PARALLEL) { #ifndef _OPENMP hr = E_NOTIMPL; #else hr = CompressBC_Parallel(srcImage, *img, GetBCFlags(options.flags), GetSRGBFlags(options.flags), options.threshold, statusCallback); #endif // _OPENMP } else { hr = CompressBC(srcImage, *img, GetBCFlags(options.flags), GetSRGBFlags(options.flags), options.threshold, statusCallback); } if (FAILED(hr)) { image.Release(); return hr; } if (statusCallback) { if (!statusCallback(img->height, img->height)) { image.Release(); return E_ABORT; } } return S_OK; } _Use_decl_annotations_ HRESULT DirectX::CompressEx( const Image* srcImages, size_t nimages, const TexMetadata& metadata, DXGI_FORMAT format, const CompressOptions& options, ScratchImage& cImages, std::function statusCallback) { if (!srcImages || !nimages) return E_INVALIDARG; if (IsCompressed(metadata.format) || !IsCompressed(format)) return E_INVALIDARG; if (IsTypeless(format) || IsTypeless(metadata.format) || IsPlanar(metadata.format) || IsPalettized(metadata.format)) return HRESULT_E_NOT_SUPPORTED; cImages.Release(); if (statusCallback && nimages == 1 && !metadata.IsVolumemap() && metadata.mipLevels == 1 && metadata.arraySize == 1) { // If progress reporting is requested when compressing a single 1D or 2D image, call // the CompressEx overload that takes a single image. // This provides a better user experience as progress will be reported as the image // is being processed, instead of after processing has been completed. return CompressEx(srcImages[0], format, options, cImages, statusCallback); } 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; } if (statusCallback) { if (!statusCallback(0, nimages)) { cImages.Release(); return E_ABORT; } } for (size_t index = 0; index < nimages; ++index) { assert(dest[index].format == format); const Image& src = srcImages[index]; if (src.width != dest[index].width || src.height != dest[index].height) { cImages.Release(); return E_FAIL; } if (options.flags & TEX_COMPRESS_PARALLEL) { #ifndef _OPENMP hr = E_NOTIMPL; #else hr = CompressBC_Parallel(src, dest[index], GetBCFlags(options.flags), GetSRGBFlags(options.flags), options.threshold, nullptr); #endif // _OPENMP } else { hr = CompressBC(src, dest[index], GetBCFlags(options.flags), GetSRGBFlags(options.flags), options.threshold, nullptr); } if (FAILED(hr)) { cImages.Release(); return hr; } if (statusCallback) { if (!statusCallback(index, nimages)) { cImages.Release(); return E_ABORT; } } } if (statusCallback) { if (!statusCallback(nimages, nimages)) { cImages.Release(); return E_ABORT; } } return S_OK; } //------------------------------------------------------------------------------------- // Decompression //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::Decompress( const Image& cImage, DXGI_FORMAT format, ScratchImage& image) noexcept { if (!IsCompressed(cImage.format) || IsCompressed(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 (!IsValid(format)) return E_INVALIDARG; if (IsTypeless(format) || IsPlanar(format) || IsPalettized(format)) return HRESULT_E_NOT_SUPPORTED; } // 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; } _Use_decl_annotations_ HRESULT DirectX::Decompress( const Image* cImages, size_t nimages, const TexMetadata& metadata, DXGI_FORMAT format, ScratchImage& images) noexcept { if (!cImages || !nimages) return E_INVALIDARG; if (!IsCompressed(metadata.format) || IsCompressed(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; if (IsTypeless(format) || IsPlanar(format) || IsPalettized(format)) return HRESULT_E_NOT_SUPPORTED; } 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; }