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 <qingtao@amazon.com>monroegm-disable-blank-issue-2
parent
9184d52a9b
commit
fc8697edd5
@ -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
|
||||
@ -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
|
||||
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:120aaf43057b07fb3c784264eb48b899cc612f30917d316fe843bb839220dc22
|
||||
size 203062
|
||||
@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:646a9a9035cc3f4dfd57babc0055710d2f5bb8aee0a792f2b65d69b4fd6a94b3
|
||||
size 786450
|
||||
Loading…
Reference in New Issue