ImageImporter.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. // Copyright (C) 2009-2021, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <AnKi/Importer/ImageImporter.h>
  6. #include <AnKi/Gr/Common.h>
  7. #include <AnKi/Resource/Stb.h>
  8. #include <AnKi/Util/Process.h>
  9. #include <AnKi/Util/File.h>
  10. namespace anki
  11. {
  12. namespace
  13. {
  14. class SurfaceOrVolumeData
  15. {
  16. public:
  17. DynamicArrayAuto<U8, PtrSize> m_pixels;
  18. DynamicArrayAuto<U8, PtrSize> m_s3tcPixels;
  19. DynamicArrayAuto<U8, PtrSize> m_astcPixels;
  20. SurfaceOrVolumeData(GenericMemoryPoolAllocator<U8> alloc)
  21. : m_pixels(alloc)
  22. , m_s3tcPixels(alloc)
  23. , m_astcPixels(alloc)
  24. {
  25. }
  26. };
  27. class Mipmap
  28. {
  29. public:
  30. DynamicArrayAuto<SurfaceOrVolumeData> m_surfacesOrVolume;
  31. Mipmap(GenericMemoryPoolAllocator<U8> alloc)
  32. : m_surfacesOrVolume(alloc)
  33. {
  34. }
  35. };
  36. /// Image importer context.
  37. class ImageImporterContext
  38. {
  39. public:
  40. DynamicArrayAuto<Mipmap> m_mipmaps;
  41. U32 m_width = 0;
  42. U32 m_height = 0;
  43. U32 m_depth = 0;
  44. U32 m_faceCount = 0;
  45. U32 m_layerCount = 0;
  46. U32 m_channelCount = 0;
  47. U32 m_pixelSize = 0;
  48. ImageImporterContext(GenericMemoryPoolAllocator<U8> alloc)
  49. : m_mipmaps(alloc)
  50. {
  51. }
  52. GenericMemoryPoolAllocator<U8> getAllocator() const
  53. {
  54. return m_mipmaps.getAllocator();
  55. }
  56. };
  57. class DdsPixelFormat
  58. {
  59. public:
  60. U32 m_dwSize;
  61. U32 m_dwFlags;
  62. Array<char, 4> m_dwFourCC;
  63. U32 m_dwRGBBitCount;
  64. U32 m_dwRBitMask;
  65. U32 m_dwGBitMask;
  66. U32 m_dwBBitMask;
  67. U32 m_dwABitMask;
  68. };
  69. class DdsHeader
  70. {
  71. public:
  72. Array<U8, 4> m_magic;
  73. U32 m_dwSize;
  74. U32 m_dwFlags;
  75. U32 m_dwHeight;
  76. U32 m_dwWidth;
  77. U32 m_dwPitchOrLinearSize;
  78. U32 m_dwDepth;
  79. U32 m_dwMipMapCount;
  80. Array<U32, 11> m_dwReserved1;
  81. DdsPixelFormat m_ddspf;
  82. U32 m_dwCaps;
  83. U32 m_dwCaps2;
  84. U32 m_dwCaps3;
  85. U32 m_dwCaps4;
  86. U32 m_dwReserved2;
  87. };
  88. class AstcHeader
  89. {
  90. public:
  91. Array<U8, 4> m_magic;
  92. U8 m_blockX;
  93. U8 m_blockY;
  94. U8 m_blockZ;
  95. Array<U8, 3> m_dimX;
  96. Array<U8, 3> m_dimY;
  97. Array<U8, 3> m_dimZ;
  98. };
  99. /// Simple class to delete a file when it goes out of scope.
  100. class CleanupFile
  101. {
  102. public:
  103. StringAuto m_fileToDelete;
  104. CleanupFile(GenericMemoryPoolAllocator<U8> alloc, CString filename)
  105. : m_fileToDelete(alloc, filename)
  106. {
  107. }
  108. ~CleanupFile()
  109. {
  110. if(!m_fileToDelete.isEmpty())
  111. {
  112. const int err = std::remove(m_fileToDelete.cstr());
  113. if(err)
  114. {
  115. ANKI_IMPORTER_LOGE("Couldn't delete file: %s", m_fileToDelete.cstr());
  116. }
  117. ANKI_IMPORTER_LOGV("Deleted %s", m_fileToDelete.cstr());
  118. }
  119. }
  120. };
  121. } // namespace
  122. static ANKI_USE_RESULT Error checkConfig(const ImageImporterConfig& config)
  123. {
  124. #define ANKI_CFG_ASSERT(x, message) \
  125. do \
  126. { \
  127. if(!(x)) \
  128. { \
  129. ANKI_IMPORTER_LOGE(message); \
  130. return Error::USER_DATA; \
  131. } \
  132. } while(false)
  133. // Filenames
  134. ANKI_CFG_ASSERT(config.m_outFilename.getLength() > 0, "Empty output filename");
  135. for(CString in : config.m_inputFilenames)
  136. {
  137. ANKI_CFG_ASSERT(in.getLength() > 0, "Empty input filename");
  138. }
  139. // Type
  140. ANKI_CFG_ASSERT(config.m_type != ImageBinaryType::NONE, "Wrong image type");
  141. ANKI_CFG_ASSERT(config.m_inputFilenames.getSize() == 1 || config.m_type != ImageBinaryType::_2D,
  142. "2D images require only one input image");
  143. ANKI_CFG_ASSERT(config.m_inputFilenames.getSize() != 1 || config.m_type != ImageBinaryType::_2D_ARRAY,
  144. "2D array images require more than one input image");
  145. ANKI_CFG_ASSERT(config.m_inputFilenames.getSize() != 1 || config.m_type != ImageBinaryType::_3D,
  146. "3D images require more than one input image");
  147. ANKI_CFG_ASSERT(config.m_inputFilenames.getSize() != 6 || config.m_type != ImageBinaryType::CUBE,
  148. "Cube images require 6 input images");
  149. // Compressions
  150. ANKI_CFG_ASSERT(config.m_compressions != ImageBinaryDataCompression::NONE, "Missing output compressions");
  151. ANKI_CFG_ASSERT(config.m_compressions == ImageBinaryDataCompression::RAW || config.m_type != ImageBinaryType::_3D,
  152. "Can't compress 3D textures");
  153. // ASTC
  154. if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
  155. {
  156. ANKI_CFG_ASSERT(config.m_astcBlockSize == UVec2(4u) || config.m_astcBlockSize == UVec2(8u),
  157. "Incorrect ASTC block sizes");
  158. }
  159. // Mip size
  160. ANKI_CFG_ASSERT(config.m_minMipmapDimension >= 4, "Mimpap min dimension can be less than 4");
  161. #undef ANKI_CFG_ASSERT
  162. return Error::NONE;
  163. }
  164. static ANKI_USE_RESULT Error checkInputImages(const ImageImporterConfig& config, U32& width, U32& height,
  165. U32& channelCount)
  166. {
  167. width = 0;
  168. height = 0;
  169. channelCount = 0;
  170. for(U32 i = 0; i < config.m_inputFilenames.getSize(); ++i)
  171. {
  172. I32 iwidth, iheight, ichannelCount;
  173. const I ok = stbi_info(config.m_inputFilenames[i].cstr(), &iwidth, &iheight, &ichannelCount);
  174. if(!ok)
  175. {
  176. ANKI_IMPORTER_LOGE("STB failed to load file: %s", config.m_inputFilenames[i].cstr());
  177. return Error::FUNCTION_FAILED;
  178. }
  179. if(width == 0)
  180. {
  181. width = U32(iwidth);
  182. height = U32(iheight);
  183. channelCount = U32(ichannelCount);
  184. }
  185. else if(width != U32(iwidth) || height != U32(iheight) || channelCount != U32(ichannelCount))
  186. {
  187. ANKI_IMPORTER_LOGE("Input image doesn't match previous input images: %s",
  188. config.m_inputFilenames[i].cstr());
  189. return Error::USER_DATA;
  190. }
  191. }
  192. ANKI_ASSERT(width > 0 && height > 0 && channelCount > 0);
  193. if(!isPowerOfTwo(width) || !isPowerOfTwo(height))
  194. {
  195. ANKI_IMPORTER_LOGE("Only power of two images are accepted");
  196. return Error::USER_DATA;
  197. }
  198. return Error::NONE;
  199. }
  200. static ANKI_USE_RESULT Error loadFirstMipmap(const ImageImporterConfig& config, ImageImporterContext& ctx)
  201. {
  202. GenericMemoryPoolAllocator<U8> alloc = ctx.getAllocator();
  203. ctx.m_mipmaps.emplaceBack(alloc);
  204. Mipmap& mip0 = ctx.m_mipmaps[0];
  205. if(ctx.m_depth > 1)
  206. {
  207. mip0.m_surfacesOrVolume.create(1, alloc);
  208. mip0.m_surfacesOrVolume[0].m_pixels.create(ctx.m_pixelSize * ctx.m_width * ctx.m_height * ctx.m_depth);
  209. }
  210. else
  211. {
  212. mip0.m_surfacesOrVolume.create(ctx.m_faceCount * ctx.m_layerCount, alloc);
  213. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  214. {
  215. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  216. {
  217. mip0.m_surfacesOrVolume[l * ctx.m_faceCount + f].m_pixels.create(ctx.m_pixelSize * ctx.m_width
  218. * ctx.m_height);
  219. }
  220. }
  221. }
  222. for(U32 i = 0; i < config.m_inputFilenames.getSize(); ++i)
  223. {
  224. I32 width, height, c;
  225. stbi_set_flip_vertically_on_load_thread(true);
  226. void* data = stbi_load(config.m_inputFilenames[i].cstr(), &width, &height, &c, ctx.m_channelCount);
  227. ANKI_ASSERT(U32(c) == ctx.m_channelCount);
  228. if(!data)
  229. {
  230. ANKI_IMPORTER_LOGE("STB load failed: %s", config.m_inputFilenames[i].cstr());
  231. return Error::FUNCTION_FAILED;
  232. }
  233. const PtrSize dataSize = PtrSize(ctx.m_width) * ctx.m_height * ctx.m_pixelSize;
  234. if(ctx.m_depth > 1)
  235. {
  236. memcpy(mip0.m_surfacesOrVolume.getBegin() + i * dataSize, data, dataSize);
  237. }
  238. else
  239. {
  240. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  241. {
  242. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  243. {
  244. memcpy(mip0.m_surfacesOrVolume[l * ctx.m_faceCount + f].m_pixels.getBegin(), data, dataSize);
  245. }
  246. }
  247. }
  248. stbi_image_free(data);
  249. }
  250. return Error::NONE;
  251. }
  252. template<U32 TCHANNEL_COUNT>
  253. static void generateSurfaceMipmap(ConstWeakArray<U8, PtrSize> inBuffer, U32 inWidth, U32 inHeight,
  254. WeakArray<U8, PtrSize> outBuffer)
  255. {
  256. using UVecType = typename std::conditional_t<TCHANNEL_COUNT == 3, U8Vec3, U8Vec4>;
  257. using FVecType = typename std::conditional_t<TCHANNEL_COUNT == 3, Vec3, Vec4>;
  258. const ConstWeakArray<UVecType, PtrSize> inPixels(reinterpret_cast<const UVecType*>(&inBuffer[0]),
  259. inBuffer.getSizeInBytes() / sizeof(UVecType));
  260. WeakArray<UVecType, PtrSize> outPixels(reinterpret_cast<UVecType*>(&outBuffer[0]),
  261. outBuffer.getSizeInBytes() / sizeof(UVecType));
  262. const U32 outWidth = inWidth >> 1;
  263. const U32 outHeight = inHeight >> 1;
  264. for(U32 h = 0; h < outHeight; ++h)
  265. {
  266. for(U32 w = 0; w < outWidth; ++w)
  267. {
  268. // Gather input
  269. FVecType average(0.0f);
  270. for(U32 y = 0; y < 2; ++y)
  271. {
  272. for(U32 x = 0; x < 2; ++x)
  273. {
  274. const U32 idx = (h * 2 + y) * inWidth + (w * 2 + x);
  275. const UVecType inPixel = inPixels[idx];
  276. for(U32 c = 0; c < TCHANNEL_COUNT; ++c)
  277. {
  278. average[c] += F32(inPixel[c]) / 255.0f * 0.25f;
  279. }
  280. }
  281. }
  282. UVecType uaverage;
  283. for(U32 c = 0; c < TCHANNEL_COUNT; ++c)
  284. {
  285. uaverage[c] = U8(average[c] * 255.0f);
  286. }
  287. // Store
  288. const U32 idx = h * outWidth + w;
  289. outPixels[idx] = uaverage;
  290. }
  291. }
  292. }
  293. static ANKI_USE_RESULT Error compressS3tc(GenericMemoryPoolAllocator<U8> alloc, CString tempDirectory,
  294. CString compressonatorPath, ConstWeakArray<U8, PtrSize> inPixels, U32 inWidth,
  295. U32 inHeight, U32 channelCount, WeakArray<U8, PtrSize> outPixels)
  296. {
  297. ANKI_ASSERT(inPixels.getSizeInBytes() == PtrSize(inWidth) * inHeight * channelCount);
  298. ANKI_ASSERT(inWidth > 0 && isPowerOfTwo(inWidth) && inHeight > 0 && isPowerOfTwo(inHeight));
  299. ANKI_ASSERT(outPixels.getSizeInBytes() == PtrSize((channelCount == 3) ? 8 : 16) * (inWidth / 4) * (inHeight / 4));
  300. // Create a PNG image to feed to the compressor
  301. StringAuto pngFilename(alloc);
  302. pngFilename.sprintf("%s/AnKiImageImporter_%u.png", tempDirectory.cstr(), U32(std::rand()));
  303. ANKI_IMPORTER_LOGV("Will store: %s", pngFilename.cstr());
  304. if(!stbi_write_png(pngFilename.cstr(), inWidth, inHeight, channelCount, inPixels.getBegin(), 0))
  305. {
  306. ANKI_IMPORTER_LOGE("STB failed to create: %s", pngFilename.cstr());
  307. return Error::FUNCTION_FAILED;
  308. }
  309. CleanupFile pngCleanup(alloc, pngFilename);
  310. // Invoke the compressor process
  311. StringAuto ddsFilename(alloc);
  312. ddsFilename.sprintf("%s/AnKiImageImporter_%u.dds", tempDirectory.cstr(), U32(std::rand()));
  313. Process proc;
  314. Array<CString, 5> args;
  315. U32 argCount = 0;
  316. args[argCount++] = "-nomipmap";
  317. args[argCount++] = "-fd";
  318. args[argCount++] = (channelCount == 3) ? "BC1" : "BC3";
  319. args[argCount++] = pngFilename;
  320. args[argCount++] = ddsFilename;
  321. ANKI_IMPORTER_LOGV("Will invoke process: CompressonatorCLI %s %s %s %s %s", args[0].cstr(), args[1].cstr(),
  322. args[2].cstr(), args[3].cstr(), args[4].cstr());
  323. ANKI_CHECK(proc.start("CompressonatorCLI", args,
  324. (compressonatorPath.isEmpty()) ? ConstWeakArray<CString>()
  325. : Array<CString, 2>{{"PATH", compressonatorPath}}));
  326. CleanupFile ddsCleanup(alloc, ddsFilename);
  327. ProcessStatus status;
  328. I32 exitCode;
  329. ANKI_CHECK(proc.wait(60.0, &status, &exitCode));
  330. if(status != ProcessStatus::NORMAL_EXIT || exitCode != 0)
  331. {
  332. StringAuto errStr(alloc);
  333. if(exitCode != 0)
  334. {
  335. ANKI_CHECK(proc.readFromStdout(errStr));
  336. }
  337. if(errStr.isEmpty())
  338. {
  339. errStr = "Unknown error";
  340. }
  341. ANKI_IMPORTER_LOGE("Invoking compressor process failed: %s", errStr.cstr());
  342. return Error::FUNCTION_FAILED;
  343. }
  344. // Read the DDS file
  345. File ddsFile;
  346. ANKI_CHECK(ddsFile.open(ddsFilename, FileOpenFlag::READ | FileOpenFlag::BINARY));
  347. DdsHeader ddsHeader;
  348. ANKI_CHECK(ddsFile.read(&ddsHeader, sizeof(DdsHeader)));
  349. if(channelCount == 3 && memcmp(&ddsHeader.m_ddspf.m_dwFourCC[0], "DXT1", 4) != 0)
  350. {
  351. ANKI_IMPORTER_LOGE("Incorrect format. Expecting DXT1");
  352. return Error::FUNCTION_FAILED;
  353. }
  354. if(channelCount == 4 && memcmp(&ddsHeader.m_ddspf.m_dwFourCC[0], "DXT5", 4) != 0)
  355. {
  356. ANKI_IMPORTER_LOGE("Incorrect format. Expecting DXT5");
  357. return Error::FUNCTION_FAILED;
  358. }
  359. if(ddsHeader.m_dwWidth != inWidth || ddsHeader.m_dwHeight != inHeight)
  360. {
  361. ANKI_IMPORTER_LOGE("Incorrect DDS image size");
  362. return Error::FUNCTION_FAILED;
  363. }
  364. ANKI_CHECK(ddsFile.read(outPixels.getBegin(), outPixels.getSizeInBytes()));
  365. return Error::NONE;
  366. }
  367. static ANKI_USE_RESULT Error compressAstc(GenericMemoryPoolAllocator<U8> alloc, CString tempDirectory,
  368. CString astcencPath, ConstWeakArray<U8, PtrSize> inPixels, U32 inWidth,
  369. U32 inHeight, U32 inChannelCount, UVec2 blockSize,
  370. WeakArray<U8, PtrSize> outPixels)
  371. {
  372. const PtrSize blockBytes = 16;
  373. (void)blockBytes;
  374. ANKI_ASSERT(inPixels.getSizeInBytes() == PtrSize(inWidth) * inHeight * inChannelCount);
  375. ANKI_ASSERT(inWidth > 0 && isPowerOfTwo(inWidth) && inHeight > 0 && isPowerOfTwo(inHeight));
  376. ANKI_ASSERT(outPixels.getSizeInBytes() == blockBytes * (inWidth / blockSize.x()) * (inHeight / blockSize.y()));
  377. // Create a BMP image to feed to the astcebc
  378. StringAuto pngFilename(alloc);
  379. pngFilename.sprintf("%s/AnKiImageImporter_%u.png", tempDirectory.cstr(), U32(std::rand()));
  380. ANKI_IMPORTER_LOGV("Will store: %s", pngFilename.cstr());
  381. if(!stbi_write_png(pngFilename.cstr(), inWidth, inHeight, inChannelCount, inPixels.getBegin(), 0))
  382. {
  383. ANKI_IMPORTER_LOGE("STB failed to create: %s", pngFilename.cstr());
  384. return Error::FUNCTION_FAILED;
  385. }
  386. CleanupFile pngCleanup(alloc, pngFilename);
  387. // Invoke the compressor process
  388. StringAuto astcFilename(alloc);
  389. astcFilename.sprintf("%s/AnKiImageImporter_%u.astc", tempDirectory.cstr(), U32(std::rand()));
  390. StringAuto blockStr(alloc);
  391. blockStr.sprintf("%ux%u", blockSize.x(), blockSize.y());
  392. Process proc;
  393. Array<CString, 5> args;
  394. U32 argCount = 0;
  395. args[argCount++] = "-cl";
  396. args[argCount++] = pngFilename;
  397. args[argCount++] = astcFilename;
  398. args[argCount++] = blockStr;
  399. args[argCount++] = "-fast";
  400. ANKI_IMPORTER_LOGV("Will invoke process: astcenc-avx2 %s %s %s %s %s", args[0].cstr(), args[1].cstr(),
  401. args[2].cstr(), args[3].cstr(), args[4].cstr());
  402. ANKI_CHECK(
  403. proc.start("astcenc-avx2", args,
  404. (astcencPath.isEmpty()) ? ConstWeakArray<CString>() : Array<CString, 2>{{"PATH", astcencPath}}));
  405. CleanupFile astcCleanup(alloc, astcFilename);
  406. ProcessStatus status;
  407. I32 exitCode;
  408. ANKI_CHECK(proc.wait(60.0, &status, &exitCode));
  409. if(status != ProcessStatus::NORMAL_EXIT || exitCode != 0)
  410. {
  411. StringAuto errStr(alloc);
  412. if(exitCode != 0)
  413. {
  414. ANKI_CHECK(proc.readFromStdout(errStr));
  415. }
  416. if(errStr.isEmpty())
  417. {
  418. errStr = "Unknown error";
  419. }
  420. ANKI_IMPORTER_LOGE("Invoking astcenc-avx2 process failed: %s", errStr.cstr());
  421. return Error::FUNCTION_FAILED;
  422. }
  423. // Read the astc file
  424. File astcFile;
  425. ANKI_CHECK(astcFile.open(astcFilename, FileOpenFlag::READ | FileOpenFlag::BINARY));
  426. AstcHeader header;
  427. ANKI_CHECK(astcFile.read(&header, sizeof(header)));
  428. auto unpackBytes = [](U8 a, U8 b, U8 c, U8 d) -> U32 {
  429. return (U32(a)) + (U32(b) << 8) + (U32(c) << 16) + (U32(d) << 24);
  430. };
  431. const U32 magicval = unpackBytes(header.m_magic[0], header.m_magic[1], header.m_magic[2], header.m_magic[3]);
  432. if(magicval != 0x5CA1AB13)
  433. {
  434. ANKI_IMPORTER_LOGE("astcenc-avx2 produced a file with wrong magic");
  435. return Error::FUNCTION_FAILED;
  436. }
  437. const U32 blockx = max<U32>(header.m_blockX, 1u);
  438. const U32 blocky = max<U32>(header.m_blockY, 1u);
  439. const U32 blockz = max<U32>(header.m_blockZ, 1u);
  440. if(blockx != blockSize.x() || blocky != blockSize.y() || blockz != 1)
  441. {
  442. ANKI_IMPORTER_LOGE("astcenc-avx2 with wrong block size");
  443. return Error::FUNCTION_FAILED;
  444. }
  445. const U32 dimx = unpackBytes(header.m_dimX[0], header.m_dimX[1], header.m_dimX[2], 0);
  446. const U32 dimy = unpackBytes(header.m_dimY[0], header.m_dimY[1], header.m_dimY[2], 0);
  447. const U32 dimz = unpackBytes(header.m_dimZ[0], header.m_dimZ[1], header.m_dimZ[2], 0);
  448. if(dimx != inWidth || dimy != inHeight || dimz != 1)
  449. {
  450. ANKI_IMPORTER_LOGE("astcenc-avx2 with wrong image size");
  451. return Error::FUNCTION_FAILED;
  452. }
  453. ANKI_CHECK(astcFile.read(outPixels.getBegin(), outPixels.getSizeInBytes()));
  454. return Error::NONE;
  455. }
  456. static ANKI_USE_RESULT Error storeAnkiImage(const ImageImporterConfig& config, const ImageImporterContext& ctx)
  457. {
  458. ANKI_IMPORTER_LOGV("Storing to %s", config.m_outFilename.cstr());
  459. File outFile;
  460. ANKI_CHECK(outFile.open(config.m_outFilename, FileOpenFlag::BINARY | FileOpenFlag::WRITE));
  461. // Header
  462. ImageBinaryHeader header = {};
  463. memcpy(&header.m_magic[0], &IMAGE_MAGIC[0], sizeof(header.m_magic));
  464. header.m_width = ctx.m_width;
  465. header.m_height = ctx.m_height;
  466. header.m_depthOrLayerCount = max(ctx.m_layerCount, ctx.m_depth);
  467. header.m_type = config.m_type;
  468. header.m_colorFormat = (ctx.m_channelCount == 3) ? ImageBinaryColorFormat::RGB8 : ImageBinaryColorFormat::RGBA8;
  469. header.m_compressionMask = config.m_compressions;
  470. header.m_isNormal = false;
  471. header.m_mipmapCount = ctx.m_mipmaps.getSize();
  472. header.m_astcBlockSizeX = config.m_astcBlockSize.x();
  473. header.m_astcBlockSizeY = config.m_astcBlockSize.y();
  474. ANKI_CHECK(outFile.write(&header, sizeof(header)));
  475. // Write RAW
  476. if(!!(config.m_compressions & ImageBinaryDataCompression::RAW))
  477. {
  478. ANKI_IMPORTER_LOGV("Storing RAW");
  479. // for(I32 mip = I32(ctx.m_mipmaps.getSize()) - 1; mip >= 0; --mip)
  480. for(U32 mip = 0; mip < ctx.m_mipmaps.getSize(); ++mip)
  481. {
  482. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  483. {
  484. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  485. {
  486. const U32 idx = l * ctx.m_faceCount + f;
  487. const ConstWeakArray<U8, PtrSize> pixels = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx].m_pixels;
  488. ANKI_CHECK(outFile.write(&pixels[0], pixels.getSizeInBytes()));
  489. }
  490. }
  491. }
  492. }
  493. // Write S3TC
  494. if(!!(config.m_compressions & ImageBinaryDataCompression::S3TC))
  495. {
  496. ANKI_IMPORTER_LOGV("Storing S3TC");
  497. // for(I32 mip = I32(ctx.m_mipmaps.getSize()) - 1; mip >= 0; --mip)
  498. for(U32 mip = 0; mip < ctx.m_mipmaps.getSize(); ++mip)
  499. {
  500. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  501. {
  502. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  503. {
  504. const U32 idx = l * ctx.m_faceCount + f;
  505. const ConstWeakArray<U8, PtrSize> pixels = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx].m_s3tcPixels;
  506. ANKI_CHECK(outFile.write(&pixels[0], pixels.getSizeInBytes()));
  507. }
  508. }
  509. }
  510. }
  511. // Write ASTC
  512. if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
  513. {
  514. ANKI_IMPORTER_LOGV("Storing ASTC");
  515. // for(I32 mip = I32(ctx.m_mipmaps.getSize()) - 1; mip >= 0; --mip)
  516. for(U32 mip = 0; mip < ctx.m_mipmaps.getSize(); ++mip)
  517. {
  518. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  519. {
  520. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  521. {
  522. const U32 idx = l * ctx.m_faceCount + f;
  523. const ConstWeakArray<U8, PtrSize> pixels = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx].m_astcPixels;
  524. ANKI_CHECK(outFile.write(&pixels[0], pixels.getSizeInBytes()));
  525. }
  526. }
  527. }
  528. }
  529. return Error::NONE;
  530. }
  531. static ANKI_USE_RESULT Error importImageInternal(const ImageImporterConfig& config)
  532. {
  533. // Checks
  534. ANKI_CHECK(checkConfig(config));
  535. U32 width, height, channelCount;
  536. ANKI_CHECK(checkInputImages(config, width, height, channelCount));
  537. // Init image
  538. GenericMemoryPoolAllocator<U8> alloc = config.m_allocator;
  539. ImageImporterContext ctx(alloc);
  540. ctx.m_width = width;
  541. ctx.m_height = height;
  542. ctx.m_depth = (config.m_type == ImageBinaryType::_3D) ? config.m_inputFilenames.getSize() : 1;
  543. ctx.m_faceCount = (config.m_type == ImageBinaryType::CUBE) ? 6 : 1;
  544. ctx.m_layerCount = (config.m_type == ImageBinaryType::_2D_ARRAY) ? config.m_inputFilenames.getSize() : 1;
  545. U32 desiredChannelCount;
  546. if(config.m_noAlpha || channelCount == 1)
  547. {
  548. // no alpha or 1 component grey
  549. desiredChannelCount = 3;
  550. }
  551. else if(channelCount == 2)
  552. {
  553. // grey with alpha
  554. desiredChannelCount = 4;
  555. }
  556. else
  557. {
  558. desiredChannelCount = channelCount;
  559. }
  560. ctx.m_channelCount = desiredChannelCount;
  561. ctx.m_pixelSize = ctx.m_channelCount;
  562. // Load first mip from the files
  563. ANKI_CHECK(loadFirstMipmap(config, ctx));
  564. // Generate mipmaps
  565. U32 minMipDimension = max(config.m_minMipmapDimension, 4u);
  566. if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
  567. {
  568. minMipDimension = max(minMipDimension, config.m_astcBlockSize.x());
  569. minMipDimension = max(minMipDimension, config.m_astcBlockSize.y());
  570. }
  571. const U32 mipCount =
  572. min(config.m_mipmapCount, (config.m_type == ImageBinaryType::_3D)
  573. ? computeMaxMipmapCount3d(width, height, ctx.m_depth, minMipDimension)
  574. : computeMaxMipmapCount2d(width, height, minMipDimension));
  575. for(U32 mip = 1; mip < mipCount; ++mip)
  576. {
  577. ctx.m_mipmaps.emplaceBack(alloc);
  578. if(config.m_type != ImageBinaryType::_3D)
  579. {
  580. ctx.m_mipmaps[mip].m_surfacesOrVolume.create(ctx.m_faceCount * ctx.m_layerCount, alloc);
  581. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  582. {
  583. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  584. {
  585. const U32 idx = l * ctx.m_faceCount + f;
  586. const SurfaceOrVolumeData& inSurface = ctx.m_mipmaps[mip - 1].m_surfacesOrVolume[idx];
  587. SurfaceOrVolumeData& outSurface = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx];
  588. outSurface.m_pixels.create((ctx.m_width >> mip) * (ctx.m_height >> mip) * ctx.m_pixelSize);
  589. if(ctx.m_channelCount == 3)
  590. {
  591. generateSurfaceMipmap<3>(ConstWeakArray<U8, PtrSize>(inSurface.m_pixels),
  592. ctx.m_width >> (mip - 1), ctx.m_height >> (mip - 1),
  593. WeakArray<U8, PtrSize>(outSurface.m_pixels));
  594. }
  595. else
  596. {
  597. ANKI_ASSERT(ctx.m_channelCount == 4);
  598. generateSurfaceMipmap<4>(ConstWeakArray<U8, PtrSize>(inSurface.m_pixels),
  599. ctx.m_width >> (mip - 1), ctx.m_height >> (mip - 1),
  600. WeakArray<U8, PtrSize>(outSurface.m_pixels));
  601. }
  602. }
  603. }
  604. }
  605. else
  606. {
  607. ANKI_ASSERT(!"TODO");
  608. }
  609. }
  610. // Compress
  611. if(!!(config.m_compressions & ImageBinaryDataCompression::S3TC))
  612. {
  613. ANKI_IMPORTER_LOGV("Will compress in S3TC");
  614. for(U32 mip = 0; mip < mipCount; ++mip)
  615. {
  616. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  617. {
  618. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  619. {
  620. const U32 idx = l * ctx.m_faceCount + f;
  621. SurfaceOrVolumeData& surface = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx];
  622. const U32 width = ctx.m_width >> mip;
  623. const U32 height = ctx.m_height >> mip;
  624. const PtrSize blockSize = (ctx.m_channelCount == 3) ? 8 : 16;
  625. const PtrSize s3tcImageSize = blockSize * (width / 4) * (height / 4);
  626. surface.m_s3tcPixels.create(s3tcImageSize);
  627. ANKI_CHECK(compressS3tc(alloc, config.m_tempDirectory, config.m_compressonatorPath,
  628. ConstWeakArray<U8, PtrSize>(surface.m_pixels), width, height,
  629. ctx.m_channelCount, WeakArray<U8, PtrSize>(surface.m_s3tcPixels)));
  630. }
  631. }
  632. }
  633. }
  634. if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
  635. {
  636. ANKI_IMPORTER_LOGV("Will compress in ASTC");
  637. for(U32 mip = 0; mip < mipCount; ++mip)
  638. {
  639. for(U32 l = 0; l < ctx.m_layerCount; ++l)
  640. {
  641. for(U32 f = 0; f < ctx.m_faceCount; ++f)
  642. {
  643. const U32 idx = l * ctx.m_faceCount + f;
  644. SurfaceOrVolumeData& surface = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx];
  645. const U32 width = ctx.m_width >> mip;
  646. const U32 height = ctx.m_height >> mip;
  647. const PtrSize blockSize = 16;
  648. const PtrSize astcImageSize =
  649. blockSize * (width / config.m_astcBlockSize.x()) * (height / config.m_astcBlockSize.y());
  650. surface.m_astcPixels.create(astcImageSize);
  651. ANKI_CHECK(compressAstc(alloc, config.m_tempDirectory, config.m_astcencPath,
  652. ConstWeakArray<U8, PtrSize>(surface.m_pixels), width, height,
  653. ctx.m_channelCount, config.m_astcBlockSize,
  654. WeakArray<U8, PtrSize>(surface.m_astcPixels)));
  655. }
  656. }
  657. }
  658. }
  659. if(!!(config.m_compressions & ImageBinaryDataCompression::ETC))
  660. {
  661. ANKI_ASSERT(!"TODO");
  662. }
  663. // Store the image
  664. ANKI_CHECK(storeAnkiImage(config, ctx));
  665. return Error::NONE;
  666. }
  667. Error importImage(const ImageImporterConfig& config)
  668. {
  669. const Error err = importImageInternal(config);
  670. if(err)
  671. {
  672. ANKI_IMPORTER_LOGE("Image importing failed");
  673. return err;
  674. }
  675. return Error::NONE;
  676. }
  677. } // end namespace anki