Bläddra i källkod

ATOM-16029 Add direct support for Astc compression (#4157)

* ATOM-16029 Add direct support for Astc compression
Changes include:
- Add ASTCCompressor class
- Enabled 3rdparty::astc-encoder static library.
- Remove ASTC support in PVRTC compressor and ISPC compressor.
- Update unit test to include texts for ASTC compress/decompress.

Signed-off-by: Qing Tao <[email protected]>
Qing Tao 4 år sedan
förälder
incheckning
fc8697edd5

+ 1 - 2
Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt

@@ -62,6 +62,7 @@ ly_add_target(
             3rdParty::Qt::Core
             3rdParty::Qt::Widgets
             3rdParty::Qt::Gui
+            3rdParty::astc-encoder
             3rdParty::etc2comp
             3rdParty::PVRTexTool
             3rdParty::squish-ccr
@@ -77,8 +78,6 @@ ly_add_target(
             Gem::Atom_RPI.Public
             Gem::Atom_RHI.Reflect
             Gem::Atom_Utils.Static
-    RUNTIME_DEPENDENCIES
-        3rdParty::ASTCEncoder
 
 )
 ly_add_source_properties(

+ 322 - 0
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.cpp

@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <astcenc.h>
+
+#include <AzCore/Jobs/JobCompletion.h>
+#include <AzCore/Jobs/JobFunction.h>
+#include <AzCore/PlatformIncl.h>
+
+#include <Atom/ImageProcessing/ImageObject.h>
+#include <Compressors/ASTCCompressor.h>
+#include <Processing/ImageFlags.h>
+#include <Processing/ImageToProcess.h>
+#include <Processing/PixelFormatInfo.h>
+
+
+namespace ImageProcessingAtom
+{
+    bool ASTCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt)
+    {
+        return IsASTCFormat(fmt);
+    }
+
+    bool ASTCCompressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt)
+    {
+        // astc encoder requires the compress input image or decompress output image to have four channels
+        switch (fmt)
+        {
+        // uint 8
+        case ePixelFormat_R8G8B8A8:
+        case ePixelFormat_R8G8B8X8:
+        // fp16
+        case ePixelFormat_R16G16B16A16F:
+        // fp32
+        case ePixelFormat_R32G32B32A32F:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    EPixelFormat ASTCCompressor::GetSuggestedUncompressedFormat([[maybe_unused]] EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const
+    {
+        if (IsUncompressedPixelFormatSupported(uncompressedfmt))
+        {
+            return uncompressedfmt;
+        }
+        
+        auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(uncompressedfmt);
+        switch (formatInfo->eSampleType)
+        {
+        case ESampleType::eSampleType_Half:
+            return ePixelFormat_R16G16B16A16F;
+        case ESampleType::eSampleType_Float:
+            return ePixelFormat_R32G32B32A32F;
+        }
+
+        return ePixelFormat_R8G8B8A8;
+    }
+
+    ColorSpace ASTCCompressor::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const
+    {
+        return ColorSpace::autoSelect;
+    }
+
+    const char* ASTCCompressor::GetName() const
+    {
+        return "ASTCCompressor";
+    }
+
+    bool ASTCCompressor::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst)
+    {
+        return true;
+    }
+
+    astcenc_profile GetAstcProfile(bool isSrgb, EPixelFormat pixelFormat)
+    {
+        // select profile depends on LDR or HDR, SRGB or Linear
+        //      ASTCENC_PRF_LDR
+        //      ASTCENC_PRF_LDR_SRGB
+        //      ASTCENC_PRF_HDR_RGB_LDR_A
+        //      ASTCENC_PRF_HDR
+        
+        auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
+        bool isHDR = formatInfo->eSampleType == ESampleType::eSampleType_Half || formatInfo->eSampleType == ESampleType::eSampleType_Float;
+        astcenc_profile profile;
+        if (isHDR)
+        {
+            // HDR is not support in core vulkan 1.1 for android.
+            // https://arm-software.github.io/vulkan-sdk/_a_s_t_c.html
+            profile = isSrgb?ASTCENC_PRF_HDR_RGB_LDR_A:ASTCENC_PRF_HDR;
+        }
+        else
+        {
+            
+            profile = isSrgb?ASTCENC_PRF_LDR_SRGB:ASTCENC_PRF_LDR;
+        }
+        return profile;
+    }
+
+    astcenc_type GetAstcDataType(EPixelFormat pixelFormat)
+    {        
+        auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
+        astcenc_type dataType = ASTCENC_TYPE_U8;
+
+        switch (formatInfo->eSampleType)
+        {
+        case ESampleType::eSampleType_Uint8:
+            dataType = ASTCENC_TYPE_U8;
+            break;
+        case ESampleType::eSampleType_Half:
+            dataType = ASTCENC_TYPE_F16;
+            break;
+        case ESampleType::eSampleType_Float:
+            dataType = ASTCENC_TYPE_F32;
+            break;
+        default:
+            dataType = ASTCENC_TYPE_U8;
+            AZ_Assert(false, "Unsupport uncompressed format %s", formatInfo->szName);
+            break;
+        }
+
+        return dataType;
+    }
+
+    float GetAstcCompressQuality(ICompressor::EQuality quality)
+    {
+        switch (quality)
+        {
+        case ICompressor::EQuality::eQuality_Fast:
+            return ASTCENC_PRE_FAST;
+        case ICompressor::EQuality::eQuality_Slow:
+            return ASTCENC_PRE_THOROUGH;
+        case ICompressor::EQuality::eQuality_Preview:
+        case ICompressor::EQuality::eQuality_Normal:
+        default:
+            return ASTCENC_PRE_MEDIUM;
+        }
+    }
+
+    IImageObjectPtr ASTCCompressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const
+    {
+        //validate input
+        EPixelFormat fmtSrc = srcImage->GetPixelFormat();
+
+        //src format need to be uncompressed and dst format need to compressed.
+        if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst))
+        {
+            return nullptr;
+        }
+
+        astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, compressOption->discardAlpha? ASTCENC_SWZ_1:ASTCENC_SWZ_A};
+        AZ::u32 flags = 0;
+        if (srcImage->HasImageFlags(EIF_RenormalizedTexture))
+        {
+            ImageToProcess imageToProcess(srcImage);
+            imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
+            srcImage = imageToProcess.Get();
+            fmtSrc = srcImage->GetPixelFormat();
+
+            flags = ASTCENC_FLG_MAP_NORMAL;
+            swizzle = astcenc_swizzle{ ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_G };
+        }
+
+        auto dstFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtDst);
+
+        const float quality = GetAstcCompressQuality(compressOption->compressQuality);
+        const astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtSrc);
+
+        astcenc_config config;
+        astcenc_error status;
+        status = astcenc_config_init(profile, dstFormatInfo->blockWidth, dstFormatInfo->blockHeight, 1, quality, flags, &config);
+
+        //ASTCENC_FLG_MAP_NORMAL
+        AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec config init failed: %s\n", astcenc_get_error_string(status));
+
+        // Create a context based on the configuration
+        astcenc_context* context;
+        AZ::u32 blockCount = ((srcImage->GetWidth(0)+ dstFormatInfo->blockWidth-1)/dstFormatInfo->blockWidth) * ((srcImage->GetHeight(0) + dstFormatInfo->blockHeight-1)/dstFormatInfo->blockHeight);
+        AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency(), blockCount);
+        status = astcenc_context_alloc(&config, threadCount, &context);
+        AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec context alloc failed: %s\n", astcenc_get_error_string(status));
+
+        const astcenc_type dataType =GetAstcDataType(fmtSrc);
+
+        // Compress the image for each mips
+        IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
+        const AZ::u32 dstMips = dstImage->GetMipCount();
+        for (AZ::u32 mip = 0; mip < dstMips; ++mip)
+        {
+            astcenc_image image;
+            image.dim_x = srcImage->GetWidth(mip);
+            image.dim_y = srcImage->GetHeight(mip);
+            image.dim_z = 1;
+            image.data_type = dataType;
+                        
+            AZ::u8* srcMem;
+            AZ::u32 srcPitch;
+            srcImage->GetImagePointer(mip, srcMem, srcPitch);
+            image.data = reinterpret_cast<void**>(&srcMem);
+                        
+            AZ::u8* dstMem;
+            AZ::u32 dstPitch;
+            dstImage->GetImagePointer(mip, dstMem, dstPitch);
+            AZ::u32 dataSize = dstImage->GetMipBufSize(mip);
+
+            // Create jobs for each compression thread
+            auto completionJob = aznew AZ::JobCompletion();
+            for (AZ::u32 threadIdx = 0; threadIdx < threadCount; threadIdx++)
+            {
+                const auto jobLambda = [&status, context, &image, &swizzle, dstMem, dataSize, threadIdx]()
+                {
+                    astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
+                    if (error != ASTCENC_SUCCESS)
+                    {
+                        status = error;
+                    }
+                };
+
+                AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr);  //auto-deletes
+                simulationJob->SetDependent(completionJob);
+                simulationJob->Start();
+            }
+
+            if (completionJob)
+            {
+                completionJob->StartAndWaitForCompletion();
+                delete completionJob;
+                completionJob = nullptr;
+            }
+
+            if (status != ASTCENC_SUCCESS)
+            {
+                AZ_Error("Image Processing", false, "ASTCCompressor::CompressImage failed: %s\n", astcenc_get_error_string(status));
+                astcenc_context_free(context);
+                return nullptr;
+            }
+
+            // Need to reset to compress next mip
+            astcenc_compress_reset(context);
+        }
+        astcenc_context_free(context);
+
+        return dstImage;
+    }
+
+    IImageObjectPtr ASTCCompressor::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const
+    {
+        //validate input
+        EPixelFormat fmtSrc = srcImage->GetPixelFormat(); //compressed
+        auto srcFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtSrc);
+
+        if (!IsCompressedPixelFormatSupported(fmtSrc) || !IsUncompressedPixelFormatSupported(fmtDst))
+        {
+            return nullptr;
+        }
+
+        const float quality = ASTCENC_PRE_MEDIUM;
+        astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
+        if (srcImage->HasImageFlags(EIF_RenormalizedTexture))
+        {
+            swizzle = astcenc_swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_A, ASTCENC_SWZ_Z, ASTCENC_SWZ_1};
+        }
+
+        astcenc_config config;
+        astcenc_error status;
+        astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtDst);
+        AZ::u32 flags = ASTCENC_FLG_DECOMPRESS_ONLY;
+        status = astcenc_config_init(profile, srcFormatInfo->blockWidth, srcFormatInfo->blockHeight, 1, quality, flags, &config);
+
+        //ASTCENC_FLG_MAP_NORMAL
+        AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_config_init failed: %s\n", astcenc_get_error_string(status));
+
+        // Create a context based on the configuration
+        const AZ::u32 threadCount = 1; // Decompress function doesn't support multiple threads
+        astcenc_context* context;
+        status = astcenc_context_alloc(&config, threadCount, &context);
+        AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_context_alloc failed: %s\n", astcenc_get_error_string(status));
+
+        astcenc_type dataType =GetAstcDataType(fmtDst);
+
+        // Decompress the image for each mips
+        IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
+        const AZ::u32 dstMips = dstImage->GetMipCount();
+        for (AZ::u32 mip = 0; mip < dstMips; ++mip)
+        {
+            astcenc_image image;
+            image.dim_x = srcImage->GetWidth(mip);
+            image.dim_y = srcImage->GetHeight(mip);
+            image.dim_z = 1;
+            image.data_type = dataType;
+                        
+            AZ::u8* srcMem;
+            AZ::u32 srcPitch;
+            srcImage->GetImagePointer(mip, srcMem, srcPitch);
+            AZ::u32 srcDataSize = srcImage->GetMipBufSize(mip);
+                        
+            AZ::u8* dstMem;
+            AZ::u32 dstPitch;
+            dstImage->GetImagePointer(mip, dstMem, dstPitch);
+            image.data = reinterpret_cast<void**>(&dstMem);
+
+            status = astcenc_decompress_image(context, srcMem, srcDataSize, &image, &swizzle, 0);
+
+            if (status != ASTCENC_SUCCESS)
+            {
+                AZ_Error("Image Processing", false, "ASTCCompressor::DecompressImage failed: %s\n", astcenc_get_error_string(status));
+                astcenc_context_free(context);
+                return nullptr;
+            }
+        }
+
+        astcenc_context_free(context);
+
+        return dstImage;
+    }
+} //namespace ImageProcessingAtom

+ 30 - 0
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.h

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <Compressors/Compressor.h>
+
+namespace ImageProcessingAtom
+{
+    class ASTCCompressor
+        : public ICompressor
+    {
+    public:
+        static bool IsCompressedPixelFormatSupported(EPixelFormat fmt);
+        static bool IsUncompressedPixelFormatSupported(EPixelFormat fmt);
+        static bool DoesSupportDecompress(EPixelFormat fmtDst);
+
+        IImageObjectPtr CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const override;
+        IImageObjectPtr DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const override;
+
+        EPixelFormat GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const override;
+        ColorSpace GetSupportedColorSpace(EPixelFormat compressFormat) const final;
+        const char* GetName() const final;
+    };
+} // namespace ImageProcessingAtom

+ 10 - 4
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp

@@ -8,6 +8,7 @@
 
 
 #include <AzCore/PlatformIncl.h>
+#include <Compressors/ASTCCompressor.h>
 #include <Compressors/CTSquisher.h>
 #include <Compressors/PVRTC.h>
 #include <Compressors/ETC2.h>
@@ -15,11 +16,8 @@
 
 namespace ImageProcessingAtom
 {
-    ICompressorPtr ICompressor::FindCompressor(EPixelFormat fmt, [[maybe_unused]] ColorSpace colorSpace, bool isCompressing)
+    ICompressorPtr ICompressor::FindCompressor(EPixelFormat fmt, ColorSpace colorSpace, bool isCompressing)
     {
-        // The ISPC texture compressor is able to compress BC1, BC3, BC6H and BC7 formats, and all of the ASTC formats.
-        // Note: The ISPC texture compressor is only able to compress images that are a multiple of the compressed format's blocksize.
-        // Another limitation is that the compressor requires LDR source images to be in sRGB colorspace.
         if (ISPCCompressor::IsCompressedPixelFormatSupported(fmt))
         {
             if ((isCompressing && ISPCCompressor::IsSourceColorSpaceSupported(colorSpace, fmt)) || (!isCompressing && ISPCCompressor::DoesSupportDecompress(fmt)))
@@ -35,6 +33,14 @@ namespace ImageProcessingAtom
                 return ICompressorPtr(new CTSquisher());
             }
         }
+        
+        if (ASTCCompressor::IsCompressedPixelFormatSupported(fmt))
+        {
+            if (isCompressing || (!isCompressing && ASTCCompressor::DoesSupportDecompress(fmt)))
+            {
+                return ICompressorPtr(new ASTCCompressor());
+            }
+        }
 
         // Both ETC2Compressor and PVRTCCompressor can process ETC formats
         // According to Mobile team, Etc2Com is faster than PVRTexLib, so we check with ETC2Compressor before PVRTCCompressor

+ 1 - 2
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h

@@ -38,8 +38,7 @@ namespace ImageProcessingAtom
             EQuality compressQuality = eQuality_Normal;
             //required for CTSquisher
             AZ::Vector3 rgbWeight = AZ::Vector3(0.3333f, 0.3334f, 0.3333f);
-            //required for ISPC texture compressor
-            bool ispcDiscardAlpha = false;
+            bool discardAlpha = false;
         };
 
     public:

+ 13 - 19
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ISPCTextureCompressor.cpp

@@ -57,6 +57,12 @@ namespace ImageProcessingAtom
 
     bool ISPCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt)
     {
+        // Even though the ISPC compressor support ASTC formats. But it has restrictions
+        //      1. Only supports LDR color profile
+        //      2. Only supports a subset of 2D block sizes
+        // Also it has overall lower quality compare to astc-encoder
+        // So we won't add ASTC as part of supported formats here
+        // Ref: https://solidpixel.github.io/2020/03/02/astc-compared.html
         switch (fmt)
         {
         case ePixelFormat_BC3:
@@ -152,7 +158,7 @@ namespace ImageProcessingAtom
         if (compressOption)
         {
             quality = compressOption->compressQuality;
-            discardAlpha = compressOption->ispcDiscardAlpha;
+            discardAlpha = compressOption->discardAlpha;
         }
 
         // Get the compression profile
@@ -230,24 +236,12 @@ namespace ImageProcessingAtom
             }
             break;
             default:
-                if (IsASTCFormat(destinationFormat))
-                {
-                    const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo(destinationFormat);
-                    astc_enc_settings settings = {};
-
-                    const auto setProfile = compressionProfile->GetASTC(discardAlpha);
-                    setProfile(&settings, info->blockWidth, info->blockHeight);
-
-                    // Compress with ASTC
-                    CompressBlocksASTC(&sourceSurface, destinationImageData, &settings);
-                }
-                else
-                {
-                    // No valid pixel format
-                    AZ_Assert(false, "Unhandled pixel format %d", destinationFormat);
-                    return nullptr;
-                }
-                break;
+            {
+                // No valid pixel format
+                AZ_Assert(false, "Unhandled pixel format %d", destinationFormat);
+                return nullptr;
+            }
+            break;
             }
         }
 

+ 3 - 50
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp

@@ -19,7 +19,7 @@
 
 namespace ImageProcessingAtom
 {
-    // Note: PVRTexLib supports ASTC formats, ETC formats, PVRTC formats and BC formats
+    // Note: PVRTexLib supports ETC formats, PVRTC formats and BC formats
     // We haven't tested the performace to compress BC formats compare to CTSquisher
     // For PVRTC formats, we only added PVRTC 1 support for now
     // The compression for ePVRTPF_EAC_R11 and ePVRTPF_EAC_RG11 are very slow. It takes 7 and 14 minutes for a 2048x2048 texture.
@@ -27,34 +27,6 @@ namespace ImageProcessingAtom
     {
         switch (fmt)
         {
-        case ePixelFormat_ASTC_4x4:
-            return ePVRTPF_ASTC_4x4;
-        case ePixelFormat_ASTC_5x4:
-            return ePVRTPF_ASTC_5x4;
-        case ePixelFormat_ASTC_5x5:
-            return ePVRTPF_ASTC_5x5;
-        case ePixelFormat_ASTC_6x5:
-            return ePVRTPF_ASTC_6x5;
-        case ePixelFormat_ASTC_6x6:
-            return ePVRTPF_ASTC_6x6;
-        case ePixelFormat_ASTC_8x5:
-            return ePVRTPF_ASTC_8x5;
-        case ePixelFormat_ASTC_8x6:
-            return ePVRTPF_ASTC_8x6;
-        case ePixelFormat_ASTC_8x8:
-            return ePVRTPF_ASTC_8x8;
-        case ePixelFormat_ASTC_10x5:
-            return ePVRTPF_ASTC_10x5;
-        case ePixelFormat_ASTC_10x6:
-            return ePVRTPF_ASTC_10x6;
-        case ePixelFormat_ASTC_10x8:
-            return ePVRTPF_ASTC_10x8;
-        case ePixelFormat_ASTC_10x10:
-            return ePVRTPF_ASTC_10x10;
-        case ePixelFormat_ASTC_12x10:
-            return ePVRTPF_ASTC_12x10;
-        case ePixelFormat_ASTC_12x12:
-            return ePVRTPF_ASTC_12x12;
         case ePixelFormat_PVRTC2:
             return ePVRTPF_PVRTCI_2bpp_RGBA;
         case ePixelFormat_PVRTC4:
@@ -156,26 +128,7 @@ namespace ImageProcessingAtom
                 internalQuality = pvrtexture::eETCSlow;
             }
         }
-        else if (IsASTCFormat(fmtDst))
-        {
-            if (quality == eQuality_Preview)
-            {
-                internalQuality = pvrtexture::eASTCVeryFast;
-            }
-            else if (quality == eQuality_Fast)
-            {
-                internalQuality = pvrtexture::eASTCFast;
-            }
-            else if (quality == eQuality_Normal)
-            {
-                internalQuality = pvrtexture::eASTCMedium;
-            }
-            else
-            {
-                internalQuality = pvrtexture::eASTCThorough;
-            }
-        }
-        else
+        else 
         {
             if (quality == eQuality_Preview)
             {
@@ -252,7 +205,7 @@ namespace ImageProcessingAtom
 
             if (!isSuccess)
             {
-                AZ_Error("Image Processing", false, "Failed to compress image with PVRTexLib. You may not have astcenc.exe for compressing ASTC formates");
+                AZ_Error("Image Processing", false, "Failed to compress image with PVRTexLib.");
                 return nullptr;
             }
 

+ 13 - 7
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp

@@ -117,6 +117,11 @@ namespace ImageProcessingAtom
         }
     }
 
+    const ImageConvertProcessDescriptor* ImageConvertProcess::GetInputDesc() const
+    {
+        return m_input.get();
+    }
+
     ImageConvertProcess::ImageConvertProcess(AZStd::unique_ptr<ImageConvertProcessDescriptor>&& descriptor)
         : m_image(nullptr)
         , m_progressStep(0)
@@ -554,12 +559,6 @@ namespace ImageProcessingAtom
     // pixel format conversion
     bool ImageConvertProcess::ConvertPixelformat()
     {
-        //For ASTC compression we need to clear out the alpha to get accurate rgb compression.
-        if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
-        {
-            m_image->Get()->Swizzle("rgb1");
-        }
-
         //set up compress option
         ICompressor::EQuality quality;
         if (m_input->m_isPreview)
@@ -574,7 +573,14 @@ namespace ImageProcessingAtom
         // set the compression options
         m_image->GetCompressOption().compressQuality = quality;
         m_image->GetCompressOption().rgbWeight = m_input->m_presetSetting.GetColorWeight();
-        m_image->GetCompressOption().ispcDiscardAlpha = m_input->m_presetSetting.m_discardAlpha;
+        m_image->GetCompressOption().discardAlpha = m_input->m_presetSetting.m_discardAlpha;
+
+        //For ASTC compression we need to clear out the alpha to get accurate rgb compression.
+        if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat))
+        {
+            m_image->GetCompressOption().discardAlpha = true;
+        }
+
         m_image->ConvertFormat(m_input->m_presetSetting.m_pixelFormat);
 
         return true;

+ 2 - 0
Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h

@@ -122,6 +122,8 @@ namespace ImageProcessingAtom
         // Get output JobProducts and append them to the outProducts vector.
         void GetAppendOutputProducts(AZStd::vector<AssetBuilderSDK::JobProduct>& outProducts);
 
+        const ImageConvertProcessDescriptor* GetInputDesc() const;
+
     private:
         //input image and settings
         AZStd::shared_ptr<ImageConvertProcessDescriptor> m_input;

+ 135 - 15
Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp

@@ -12,8 +12,12 @@
 
 #include <AzQtComponents/Utilities/QtPluginPaths.h>
 
+#include <AzCore/AzCore_Traits_Platform.h>
+
 #include <AzCore/Asset/AssetManager.h>
 #include <AzCore/Asset/AssetManagerComponent.h>
+#include <AzCore/Jobs/JobContext.h>
+#include <AzCore/Jobs/JobManager.h>
 #include <AzCore/Memory/Memory.h>
 #include <AzCore/Memory/PoolAllocator.h>
 #include <AzCore/RTTI/ReflectionManager.h>
@@ -123,6 +127,9 @@ namespace UnitTest
         AZStd::string m_outputRootFolder;
         AZStd::string m_outputFolder;
 
+        AZStd::unique_ptr<AZ::JobManager> m_jobManager;
+        AZStd::unique_ptr<AZ::JobContext> m_jobContext;
+
         void SetUp() override
         {
             AllocatorsBase::SetupAllocator();
@@ -159,6 +166,27 @@ namespace UnitTest
             m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
             BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get());
 
+            // Setup job context for job system
+            JobManagerDesc jobManagerDesc;
+            JobManagerThreadDesc threadDesc;
+#if AZ_TRAIT_SET_JOB_PROCESSOR_ID
+            threadDesc.m_cpuId = 0; // Don't set processors IDs on windows
+#endif 
+
+            uint32_t numWorkerThreads = AZStd::thread::hardware_concurrency();
+
+            for (unsigned int i = 0; i < numWorkerThreads; ++i)
+            {
+                jobManagerDesc.m_workerThreads.push_back(threadDesc);
+#if AZ_TRAIT_SET_JOB_PROCESSOR_ID
+                threadDesc.m_cpuId++;
+#endif 
+            }
+
+            m_jobManager = AZStd::make_unique<JobManager>(jobManagerDesc);
+            m_jobContext = AZStd::make_unique<JobContext>(*m_jobManager);
+            JobContext::SetGlobalContext(m_jobContext.get());
+
             // Startup default local FileIO (hits OSAllocator) if not already setup.
             if (AZ::IO::FileIOBase::GetInstance() == nullptr)
             {
@@ -192,6 +220,10 @@ namespace UnitTest
             delete AZ::IO::FileIOBase::GetInstance();
             AZ::IO::FileIOBase::SetInstance(nullptr);
 
+            JobContext::SetGlobalContext(nullptr);
+            m_jobContext = nullptr;
+            m_jobManager = nullptr;
+
             m_jsonRegistrationContext->EnableRemoveReflection();
             m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get());
             BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get());
@@ -223,7 +255,7 @@ namespace UnitTest
             Image_512X288_RGB8_Tga,
             Image_1024X1024_RGB8_Tif,
             Image_UpperCase_Tga,
-            Image_512x512_Normal_Tga,       // QImage doesn't support loading this file.
+            Image_1024x1024_normal_tiff,
             Image_128x128_Transparent_Tga,
             Image_237x177_RGB_Jpg,
             Image_GreyScale_Png,
@@ -251,7 +283,7 @@ namespace UnitTest
             m_imagFileNameMap[Image_512X288_RGB8_Tga] = m_testFileFolder + "512x288_24bit.tga";
             m_imagFileNameMap[Image_1024X1024_RGB8_Tif] = m_testFileFolder + "1024x1024_24bit.tif";
             m_imagFileNameMap[Image_UpperCase_Tga] = m_testFileFolder + "uppercase.TGA";
-            m_imagFileNameMap[Image_512x512_Normal_Tga] = m_testFileFolder + "512x512_RGB_N.tga";
+            m_imagFileNameMap[Image_1024x1024_normal_tiff] = m_testFileFolder + "1024x1024_normal.tiff";
             m_imagFileNameMap[Image_128x128_Transparent_Tga] = m_testFileFolder + "128x128_RGBA8.tga";
             m_imagFileNameMap[Image_237x177_RGB_Jpg] = m_testFileFolder + "237x177_RGB.jpg";
             m_imagFileNameMap[Image_GreyScale_Png] = m_testFileFolder + "greyscale.png";
@@ -801,8 +833,7 @@ namespace UnitTest
             auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
             if (formatInfo->bCompressed)
             {
-                // exclude astc formats until we add astc compressor to all platforms
-                // exclude pvrtc formats (deprecating) 
+                // skip ASTC formats which are tested in TestConvertASTCCompressor
                 if (!IsASTCFormat(pixelFormat)
                     && pixelFormat != ePixelFormat_PVRTC2 && pixelFormat != ePixelFormat_PVRTC4
                     && !IsETCFormat(pixelFormat)) // skip ETC since it's very slow
@@ -830,32 +861,121 @@ namespace UnitTest
                     continue;
                 }
 
-                [[maybe_unused]] auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
-                imageToProcess.Set(srcImage);
-                imageToProcess.ConvertFormat(pixelFormat);
+                auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
+                ColorSpace sourceColorSpace = srcImage->HasImageFlags(EIF_SRGBRead) ? ColorSpace::sRGB : ColorSpace::linear;
+                ICompressorPtr compressor = ICompressor::FindCompressor(pixelFormat, sourceColorSpace, true);
 
-                if (!imageToProcess.Get())
+                if (!compressor)
                 {
                     AZ_Warning("test", false, "unsupported format: %s",  formatInfo->szName);
                     continue;
                 }
+
+                imageToProcess.Set(srcImage);
+                imageToProcess.ConvertFormat(pixelFormat);
+
                 ASSERT_TRUE(imageToProcess.Get());
                 ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat);
 
-                // Get compressor name
-                ColorSpace sourceColorSpace = srcImage->HasImageFlags(EIF_SRGBRead) ? ColorSpace::sRGB : ColorSpace::linear;
-                ICompressorPtr compressor = ICompressor::FindCompressor(pixelFormat, sourceColorSpace, true);
+                //convert back to an uncompressed format and expect it will be successful
+                imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
+                ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
 
-                //save the image to a file so we can check the visual result
+                // Save the image to a file so we can check the visual result
                 AZStd::string outputName = AZStd::string::format("%s_%s", imageName.c_str(), compressor->GetName());
                 SaveImageToFile(imageToProcess.Get(), outputName, 1);
+            }
+        }
+    }
+        
+    TEST_F(ImageProcessingTest, Test_ConvertAllAstc_Success)
+    {
+        // Compress/Decompress to all astc formats (LDR)
+        auto imageIdx = Image_237x177_RGB_Jpg;
+        IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx]));
+        QFileInfo fi(m_imagFileNameMap[imageIdx].c_str());
+        AZStd::string imageName = fi.baseName().toUtf8().constData();
+        for (uint32 i = 0; i < ePixelFormat_Count; i++)
+        {
+            EPixelFormat pixelFormat = (EPixelFormat)i;
+            if (IsASTCFormat(pixelFormat))
+            {
+                ImageToProcess imageToProcess(srcImage);
+                imageToProcess.ConvertFormat(pixelFormat);
 
-                //convert back to an uncompressed format and expect it will be successful
-                imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
-                ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == ePixelFormat_R8G8B8A8);
+                ASSERT_TRUE(imageToProcess.Get());
+                ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat);
+                ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0));
+                ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0));
+
+                // convert back to an uncompressed format and expect it will be successful
+                imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
+                ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
+
+                // save the image to a file so we can check the visual result
+                AZStd::string outputName = AZStd::string::format("ASTC_%s", imageName.c_str());
+                SaveImageToFile(imageToProcess.Get(), outputName, 1);
             }
         }
     }
+        
+    TEST_F(ImageProcessingTest, Test_ConvertHdrToAstc_Success)
+    {
+        // Compress/Decompress HDR
+        auto imageIdx = Image_defaultprobe_cm_1536x256_64bits_tif;
+        IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx]));
+
+        EPixelFormat dstFormat = ePixelFormat_ASTC_4x4;
+        ImageToProcess imageToProcess(srcImage);
+        imageToProcess.ConvertFormat(ePixelFormat_ASTC_4x4);
+                
+        ASSERT_TRUE(imageToProcess.Get());
+        ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == dstFormat);
+        ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0));
+        ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0));
+                                
+        //convert back to an uncompressed format and expect it will be successful
+        imageToProcess.ConvertFormat(srcImage->GetPixelFormat());
+        ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat());
+                                
+        //save the image to a file so we can check the visual result
+        SaveImageToFile(imageToProcess.Get(), "ASTC_HDR", 1);
+    }
+    
+    TEST_F(ImageProcessingTest, Test_AstcNormalPreset_Success)
+    {
+        // Normal.preset which uses ASTC as output format
+        // This test compress a normal texture and its mipmaps
+
+        auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder);
+        ASSERT_TRUE(outcome.IsSuccess());
+
+        AZStd::string inputFile;
+        AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
+
+        inputFile = m_imagFileNameMap[Image_1024x1024_normal_tiff];
+        IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(inputFile));
+
+        ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder, "ios", outProducts, m_context.get());
+
+        const PresetSettings* preset = &process->GetInputDesc()->m_presetSetting;
+
+        if (process != nullptr)
+        {
+            process->ProcessAll();
+
+            //get process result
+            ASSERT_TRUE(process->IsSucceed());
+            auto outputImage = process->GetOutputImage();
+            ASSERT_TRUE(outputImage->GetPixelFormat() == preset->m_pixelFormat);
+            ASSERT_TRUE(outputImage->GetWidth(0) == srcImage->GetWidth(0));
+            ASSERT_TRUE(outputImage->GetHeight(0) == srcImage->GetHeight(0));
+            
+            SaveImageToFile(outputImage, "ASTC_Normal", 10);
+
+            delete process;
+        }
+    }
 
     TEST_F(ImageProcessingTest, DISABLED_TestImageFilter)
     {

+ 3 - 0
Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/1024x1024_normal.tiff

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:120aaf43057b07fb3c784264eb48b899cc612f30917d316fe843bb839220dc22
+size 203062

+ 0 - 3
Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/512x512_RGB_N.tga

@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:646a9a9035cc3f4dfd57babc0055710d2f5bb8aee0a792f2b65d69b4fd6a94b3
-size 786450

+ 2 - 0
Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake

@@ -114,6 +114,8 @@ set(FILES
     ../External/CubeMapGen/CImageSurface.cpp
     ../External/CubeMapGen/CImageSurface.h
     ../External/CubeMapGen/VectorMacros.h
+    Source/Compressors/ASTCCompressor.cpp
+    Source/Compressors/ASTCCompressor.h
     Source/Compressors/Compressor.h
     Source/Compressors/Compressor.cpp
     Source/Compressors/CTSquisher.h

+ 1 - 1
cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake

@@ -9,7 +9,6 @@
 # shared by other platforms:
 ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform                  TARGETS ilmbase                     PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348)
 ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform                  TARGETS assimplib                   PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec)
-ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform         TARGETS ASTCEncoder                 PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326)
 ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform                             TARGETS md5                         PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf)
 ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform                TARGETS RapidJSON                   PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023)
 ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform                  TARGETS RapidXML                    PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246)
@@ -44,5 +43,6 @@ ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux
 ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev2-linux                           TARGETS azslc                       PACKAGE_HASH 1ba84d8321a566d35a1e9aa7400211ba8e6d1c11c08e4be3c93e6e74b8f7aef1)
 ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-linux                            TARGETS zlib                        PACKAGE_HASH 16f3b9e11cda525efb62144f354c1cfc30a5def9eff020dbe49cb00ee7d8234f)
 ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux                     TARGETS squish-ccr                  PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530)
+ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-linux                       TARGETS astc-encoder                PACKAGE_HASH 2ba97a06474d609945f0ab4419af1f6bbffdd294ca6b869f5fcebec75c573c0f)
 ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-linux                    TARGETS ISPCTexComp                 PACKAGE_HASH 065fd12abe4247dde247330313763cf816c3375c221da030bdec35024947f259)
 ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-linux                        TARGETS lz4                         PACKAGE_HASH 5de3dbd3e2a3537c6555d759b3c5bb98e5456cf85c74ff6d046f809b7087290d)

+ 1 - 1
cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake

@@ -9,7 +9,6 @@
 # shared by other platforms:
 ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform                  TARGETS ilmbase                     PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348)
 ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform                  TARGETS assimplib                   PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec)
-ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform         TARGETS ASTCEncoder                 PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326)
 ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform                             TARGETS md5                         PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf)
 ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform                TARGETS RapidJSON                   PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023)
 ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform                  TARGETS RapidXML                    PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246)
@@ -42,6 +41,7 @@ ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-mac
 ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-mac                      TARGETS libsamplerate               PACKAGE_HASH b912af40c0ac197af9c43d85004395ba92a6a859a24b7eacd920fed5854a97fe)
 ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-mac                              TARGETS zlib                        PACKAGE_HASH 21714e8a6de4f2523ee92a7f52d51fbee29c5f37ced334e00dc3c029115b472e)
 ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac                       TARGETS squish-ccr                  PACKAGE_HASH 155bfbfa17c19a9cd2ef025de14c5db598f4290045d5b0d83ab58cb345089a77)
+ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-mac                         TARGETS astc-encoder                PACKAGE_HASH 96f6ea8c3e45ec7fe525230c7c53ca665c8300d8e28456cc19bb3159ce6f8dcc)
 ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac                      TARGETS ISPCTexComp                 PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169)
 ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac                          TARGETS lz4                         PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b)
 

+ 1 - 1
cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake

@@ -9,7 +9,6 @@
 # shared by other platforms:
 ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform                      TARGETS ilmbase                     PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348)
 ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform                      TARGETS assimplib                   PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec)
-ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform             TARGETS ASTCEncoder                 PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326)
 ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform                                 TARGETS md5                         PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf)
 ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform                    TARGETS RapidJSON                   PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023)
 ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform                      TARGETS RapidXML                    PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246)
@@ -49,5 +48,6 @@ ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-windows
 ly_associate_package(PACKAGE_NAME Crashpad-0.8.0-rev1-windows                           TARGETS Crashpad                    PACKAGE_HASH d162aa3070147bc0130a44caab02c5fe58606910252caf7f90472bd48d4e31e2)
 ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-windows                              TARGETS zlib                        PACKAGE_HASH 9afab1d67641ed8bef2fb38fc53942da47f2ab339d9e77d3d20704a48af2da0b)
 ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-windows                       TARGETS squish-ccr                  PACKAGE_HASH 5c3d9fa491e488ccaf802304ad23b932268a2b2846e383f088779962af2bfa84)
+ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-windows                         TARGETS astc-encoder                PACKAGE_HASH 3addc6fc1a7eb0d6b7f3d530e962af967e6d92b3825ef485da243346357cf78e)
 ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows                      TARGETS ISPCTexComp                 PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0)
 ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows                          TARGETS lz4                         PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16)