3
0

ASTCCompressor.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <astcenc.h>
  9. #include <AzCore/Jobs/JobCompletion.h>
  10. #include <AzCore/Jobs/JobFunction.h>
  11. #include <AzCore/PlatformIncl.h>
  12. #include <Atom/ImageProcessing/ImageObject.h>
  13. #include <Compressors/ASTCCompressor.h>
  14. #include <Processing/ImageFlags.h>
  15. #include <Processing/ImageToProcess.h>
  16. #include <Processing/PixelFormatInfo.h>
  17. namespace ImageProcessingAtom
  18. {
  19. bool ASTCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt)
  20. {
  21. return IsASTCFormat(fmt);
  22. }
  23. bool ASTCCompressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt)
  24. {
  25. // astc encoder requires the compress input image or decompress output image to have four channels
  26. switch (fmt)
  27. {
  28. // uint 8
  29. case ePixelFormat_R8G8B8A8:
  30. case ePixelFormat_R8G8B8X8:
  31. // fp16
  32. case ePixelFormat_R16G16B16A16F:
  33. // fp32
  34. case ePixelFormat_R32G32B32A32F:
  35. return true;
  36. default:
  37. return false;
  38. }
  39. }
  40. EPixelFormat ASTCCompressor::GetSuggestedUncompressedFormat([[maybe_unused]] EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const
  41. {
  42. if (IsUncompressedPixelFormatSupported(uncompressedfmt))
  43. {
  44. return uncompressedfmt;
  45. }
  46. auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(uncompressedfmt);
  47. switch (formatInfo->eSampleType)
  48. {
  49. case ESampleType::eSampleType_Half:
  50. return ePixelFormat_R16G16B16A16F;
  51. case ESampleType::eSampleType_Float:
  52. return ePixelFormat_R32G32B32A32F;
  53. }
  54. return ePixelFormat_R8G8B8A8;
  55. }
  56. ColorSpace ASTCCompressor::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const
  57. {
  58. return ColorSpace::autoSelect;
  59. }
  60. const char* ASTCCompressor::GetName() const
  61. {
  62. return "ASTCCompressor";
  63. }
  64. bool ASTCCompressor::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst)
  65. {
  66. return true;
  67. }
  68. astcenc_profile GetAstcProfile(bool isSrgb, bool isHDR)
  69. {
  70. // select profile depends on LDR or HDR, SRGB or Linear
  71. // ASTCENC_PRF_LDR
  72. // ASTCENC_PRF_LDR_SRGB
  73. // ASTCENC_PRF_HDR_RGB_LDR_A
  74. // ASTCENC_PRF_HDR
  75. astcenc_profile profile;
  76. if (isHDR)
  77. {
  78. // HDR is not support in core vulkan 1.1 for android.
  79. // https://arm-software.github.io/vulkan-sdk/_a_s_t_c.html
  80. profile = isSrgb?ASTCENC_PRF_HDR_RGB_LDR_A:ASTCENC_PRF_HDR;
  81. }
  82. else
  83. {
  84. profile = isSrgb?ASTCENC_PRF_LDR_SRGB:ASTCENC_PRF_LDR;
  85. }
  86. return profile;
  87. }
  88. astcenc_type GetAstcDataType(EPixelFormat pixelFormat)
  89. {
  90. auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat);
  91. astcenc_type dataType = ASTCENC_TYPE_U8;
  92. switch (formatInfo->eSampleType)
  93. {
  94. case ESampleType::eSampleType_Uint8:
  95. dataType = ASTCENC_TYPE_U8;
  96. break;
  97. case ESampleType::eSampleType_Half:
  98. dataType = ASTCENC_TYPE_F16;
  99. break;
  100. case ESampleType::eSampleType_Float:
  101. dataType = ASTCENC_TYPE_F32;
  102. break;
  103. default:
  104. dataType = ASTCENC_TYPE_U8;
  105. AZ_Assert(false, "Unsupport uncompressed format %s", formatInfo->szName);
  106. break;
  107. }
  108. return dataType;
  109. }
  110. float GetAstcCompressQuality(ICompressor::EQuality quality)
  111. {
  112. switch (quality)
  113. {
  114. case ICompressor::EQuality::eQuality_Fast:
  115. return ASTCENC_PRE_FAST;
  116. case ICompressor::EQuality::eQuality_Slow:
  117. return ASTCENC_PRE_THOROUGH;
  118. case ICompressor::EQuality::eQuality_Preview:
  119. case ICompressor::EQuality::eQuality_Normal:
  120. default:
  121. return ASTCENC_PRE_MEDIUM;
  122. }
  123. }
  124. IImageObjectPtr ASTCCompressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const
  125. {
  126. //validate input
  127. EPixelFormat fmtSrc = srcImage->GetPixelFormat();
  128. //src format need to be uncompressed and dst format need to compressed.
  129. if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst))
  130. {
  131. return nullptr;
  132. }
  133. astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, compressOption->discardAlpha? ASTCENC_SWZ_1:ASTCENC_SWZ_A};
  134. AZ::u32 flags = 0;
  135. if (srcImage->HasImageFlags(EIF_RenormalizedTexture))
  136. {
  137. ImageToProcess imageToProcess(srcImage);
  138. imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8);
  139. srcImage = imageToProcess.Get();
  140. fmtSrc = srcImage->GetPixelFormat();
  141. flags = ASTCENC_FLG_MAP_NORMAL;
  142. swizzle = astcenc_swizzle{ ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_G };
  143. }
  144. auto dstFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtDst);
  145. const float quality = GetAstcCompressQuality(compressOption->compressQuality);
  146. const astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), srcImage->HasImageFlags(EIF_HDR));
  147. astcenc_config config;
  148. astcenc_error status;
  149. status = astcenc_config_init(profile, dstFormatInfo->blockWidth, dstFormatInfo->blockHeight, 1, quality, flags, &config);
  150. //ASTCENC_FLG_MAP_NORMAL
  151. AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec config init failed: %s\n", astcenc_get_error_string(status));
  152. // Create a context based on the configuration
  153. astcenc_context* context;
  154. AZ::u32 blockCount = AZ::DivideAndRoundUp(srcImage->GetWidth(0), dstFormatInfo->blockWidth) * AZ::DivideAndRoundUp(srcImage->GetHeight(0), dstFormatInfo->blockHeight);
  155. AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency()/2, blockCount);
  156. status = astcenc_context_alloc(&config, threadCount, &context);
  157. AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec context alloc failed: %s\n", astcenc_get_error_string(status));
  158. AZ::Job* currentJob = AZ::JobContext::GetGlobalContext()->GetJobManager().GetCurrentJob();
  159. const astcenc_type dataType =GetAstcDataType(fmtSrc);
  160. // Compress the image for each mips
  161. IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
  162. const AZ::u32 dstMips = dstImage->GetMipCount();
  163. for (AZ::u32 mip = 0; mip < dstMips; ++mip)
  164. {
  165. astcenc_image image;
  166. image.dim_x = srcImage->GetWidth(mip);
  167. image.dim_y = srcImage->GetHeight(mip);
  168. image.dim_z = 1;
  169. image.data_type = dataType;
  170. AZ::u8* srcMem;
  171. AZ::u32 srcPitch;
  172. srcImage->GetImagePointer(mip, srcMem, srcPitch);
  173. image.data = reinterpret_cast<void**>(&srcMem);
  174. AZ::u8* dstMem;
  175. AZ::u32 dstPitch;
  176. dstImage->GetImagePointer(mip, dstMem, dstPitch);
  177. AZ::u32 dataSize = dstImage->GetMipBufSize(mip);
  178. if (threadCount == 1)
  179. {
  180. astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, 0);
  181. if (error != ASTCENC_SUCCESS)
  182. {
  183. status = error;
  184. }
  185. }
  186. else
  187. {
  188. AZ::JobCompletion* completionJob = nullptr;
  189. if (!currentJob)
  190. {
  191. completionJob = aznew AZ::JobCompletion();
  192. }
  193. // Create jobs for each compression thread
  194. for (AZ::u32 threadIdx = 0; threadIdx < threadCount; threadIdx++)
  195. {
  196. const auto jobLambda = [&status, context, &image, &swizzle, dstMem, dataSize, threadIdx]()
  197. {
  198. astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
  199. if (error != ASTCENC_SUCCESS)
  200. {
  201. status = error;
  202. }
  203. };
  204. AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes
  205. // adds this job as child to current job if there is a current job
  206. // otherwise adds it as a dependent for the complete job
  207. if (currentJob)
  208. {
  209. currentJob->StartAsChild(simulationJob);
  210. }
  211. else
  212. {
  213. simulationJob->SetDependent(completionJob);
  214. simulationJob->Start();
  215. }
  216. astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx);
  217. if (error != ASTCENC_SUCCESS)
  218. {
  219. status = error;
  220. }
  221. }
  222. if (currentJob)
  223. {
  224. currentJob->WaitForChildren();
  225. }
  226. if (completionJob)
  227. {
  228. completionJob->StartAndWaitForCompletion();
  229. delete completionJob;
  230. completionJob = nullptr;
  231. }
  232. }
  233. if (status != ASTCENC_SUCCESS)
  234. {
  235. AZ_Error("Image Processing", false, "ASTCCompressor::CompressImage failed: %s\n", astcenc_get_error_string(status));
  236. astcenc_context_free(context);
  237. return nullptr;
  238. }
  239. // Need to reset to compress next mip
  240. astcenc_compress_reset(context);
  241. }
  242. astcenc_context_free(context);
  243. return dstImage;
  244. }
  245. IImageObjectPtr ASTCCompressor::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const
  246. {
  247. //validate input
  248. EPixelFormat fmtSrc = srcImage->GetPixelFormat(); //compressed
  249. auto srcFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtSrc);
  250. if (!IsCompressedPixelFormatSupported(fmtSrc) || !IsUncompressedPixelFormatSupported(fmtDst))
  251. {
  252. return nullptr;
  253. }
  254. const float quality = ASTCENC_PRE_MEDIUM;
  255. astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A};
  256. if (srcImage->HasImageFlags(EIF_RenormalizedTexture))
  257. {
  258. swizzle = astcenc_swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_A, ASTCENC_SWZ_Z, ASTCENC_SWZ_1};
  259. }
  260. astcenc_config config;
  261. astcenc_error status;
  262. astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtDst);
  263. AZ::u32 flags = ASTCENC_FLG_DECOMPRESS_ONLY;
  264. status = astcenc_config_init(profile, srcFormatInfo->blockWidth, srcFormatInfo->blockHeight, 1, quality, flags, &config);
  265. //ASTCENC_FLG_MAP_NORMAL
  266. AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_config_init failed: %s\n", astcenc_get_error_string(status));
  267. // Create a context based on the configuration
  268. const AZ::u32 threadCount = 1; // Decompress function doesn't support multiple threads
  269. astcenc_context* context;
  270. status = astcenc_context_alloc(&config, threadCount, &context);
  271. AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_context_alloc failed: %s\n", astcenc_get_error_string(status));
  272. astcenc_type dataType =GetAstcDataType(fmtDst);
  273. // Decompress the image for each mips
  274. IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst));
  275. const AZ::u32 dstMips = dstImage->GetMipCount();
  276. for (AZ::u32 mip = 0; mip < dstMips; ++mip)
  277. {
  278. astcenc_image image;
  279. image.dim_x = srcImage->GetWidth(mip);
  280. image.dim_y = srcImage->GetHeight(mip);
  281. image.dim_z = 1;
  282. image.data_type = dataType;
  283. AZ::u8* srcMem;
  284. AZ::u32 srcPitch;
  285. srcImage->GetImagePointer(mip, srcMem, srcPitch);
  286. AZ::u32 srcDataSize = srcImage->GetMipBufSize(mip);
  287. AZ::u8* dstMem;
  288. AZ::u32 dstPitch;
  289. dstImage->GetImagePointer(mip, dstMem, dstPitch);
  290. image.data = reinterpret_cast<void**>(&dstMem);
  291. status = astcenc_decompress_image(context, srcMem, srcDataSize, &image, &swizzle, 0);
  292. if (status != ASTCENC_SUCCESS)
  293. {
  294. AZ_Error("Image Processing", false, "ASTCCompressor::DecompressImage failed: %s\n", astcenc_get_error_string(status));
  295. astcenc_context_free(context);
  296. return nullptr;
  297. }
  298. }
  299. astcenc_context_free(context);
  300. return dstImage;
  301. }
  302. } //namespace ImageProcessingAtom