| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- /*
- * 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, bool isHDR)
- {
- // 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
-
- 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), srcImage->HasImageFlags(EIF_HDR));
- 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 = AZ::DivideAndRoundUp(srcImage->GetWidth(0), dstFormatInfo->blockWidth) * AZ::DivideAndRoundUp(srcImage->GetHeight(0), dstFormatInfo->blockHeight);
- AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency()/2, 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));
- AZ::Job* currentJob = AZ::JobContext::GetGlobalContext()->GetJobManager().GetCurrentJob();
- 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);
- if (threadCount == 1)
- {
- astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, 0);
- if (error != ASTCENC_SUCCESS)
- {
- status = error;
- }
- }
- else
- {
- AZ::JobCompletion* completionJob = nullptr;
- if (!currentJob)
- {
- completionJob = aznew AZ::JobCompletion();
- }
- // Create jobs for each compression thread
- 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
- // adds this job as child to current job if there is a current job
- // otherwise adds it as a dependent for the complete job
- if (currentJob)
- {
- currentJob->StartAsChild(simulationJob);
- }
- else
- {
- simulationJob->SetDependent(completionJob);
- simulationJob->Start();
- }
- astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
- if (error != ASTCENC_SUCCESS)
- {
- status = error;
- }
- }
-
- if (currentJob)
- {
- currentJob->WaitForChildren();
- }
- 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
|