Browse Source

Add ASTC support to the image importer (untested)

Panagiotis Christopoulos Charitos 4 years ago
parent
commit
b2ded605bf

+ 183 - 23
AnKi/Importer/ImageImporter.cpp

@@ -20,10 +20,12 @@ class SurfaceOrVolumeData
 public:
 public:
 	DynamicArrayAuto<U8, PtrSize> m_pixels;
 	DynamicArrayAuto<U8, PtrSize> m_pixels;
 	DynamicArrayAuto<U8, PtrSize> m_s3tcPixels;
 	DynamicArrayAuto<U8, PtrSize> m_s3tcPixels;
+	DynamicArrayAuto<U8, PtrSize> m_astcPixels;
 
 
 	SurfaceOrVolumeData(GenericMemoryPoolAllocator<U8> alloc)
 	SurfaceOrVolumeData(GenericMemoryPoolAllocator<U8> alloc)
 		: m_pixels(alloc)
 		: m_pixels(alloc)
 		, m_s3tcPixels(alloc)
 		, m_s3tcPixels(alloc)
+		, m_astcPixels(alloc)
 	{
 	{
 	}
 	}
 };
 };
@@ -96,6 +98,42 @@ public:
 	U32 m_dwReserved2;
 	U32 m_dwReserved2;
 };
 };
 
 
+class AstcHeader
+{
+public:
+	Array<U8, 4> m_magic;
+	U8 m_blockX;
+	U8 m_blockY;
+	U8 m_blockZ;
+	Array<U8, 3> m_dimX;
+	Array<U8, 3> m_dimY;
+	Array<U8, 3> m_dimZ;
+};
+
+/// Simple class to delete a file when it goes out of scope.
+class CleanupFile
+{
+public:
+	StringAuto m_fileToDelete;
+
+	CleanupFile(GenericMemoryPoolAllocator<U8> alloc, CString filename)
+		: m_fileToDelete(alloc, filename)
+	{
+	}
+
+	~CleanupFile()
+	{
+		if(!m_fileToDelete.isEmpty())
+		{
+			const int err = std::remove(m_fileToDelete.cstr());
+			if(err)
+			{
+				ANKI_IMPORTER_LOGE("Couldn't delete file: %s", m_fileToDelete.cstr());
+			}
+		}
+	}
+};
+
 } // namespace
 } // namespace
 
 
 static ANKI_USE_RESULT Error checkConfig(const ImageImporterConfig& config)
 static ANKI_USE_RESULT Error checkConfig(const ImageImporterConfig& config)
@@ -297,29 +335,6 @@ static ANKI_USE_RESULT Error compressS3tc(GenericMemoryPoolAllocator<U8> alloc,
 	ANKI_ASSERT(inWidth > 0 && isPowerOfTwo(inWidth) && inHeight > 0 && isPowerOfTwo(inHeight));
 	ANKI_ASSERT(inWidth > 0 && isPowerOfTwo(inWidth) && inHeight > 0 && isPowerOfTwo(inHeight));
 	ANKI_ASSERT(outPixels.getSizeInBytes() == PtrSize((channelCount == 3) ? 8 : 16) * (inWidth / 4) * (inHeight / 4));
 	ANKI_ASSERT(outPixels.getSizeInBytes() == PtrSize((channelCount == 3) ? 8 : 16) * (inWidth / 4) * (inHeight / 4));
 
 
-	class CleanupFile
-	{
-	public:
-		StringAuto m_fileToDelete;
-
-		CleanupFile(GenericMemoryPoolAllocator<U8> alloc, CString filename)
-			: m_fileToDelete(alloc, filename)
-		{
-		}
-
-		~CleanupFile()
-		{
-			if(!m_fileToDelete.isEmpty())
-			{
-				const int err = std::remove(m_fileToDelete.cstr());
-				if(err)
-				{
-					ANKI_IMPORTER_LOGE("Couldn't delete file: %s", m_fileToDelete.cstr());
-				}
-			}
-		}
-	};
-
 	// Create a BMP image to feed to the compressor
 	// Create a BMP image to feed to the compressor
 	StringAuto bmpFilename(alloc);
 	StringAuto bmpFilename(alloc);
 	bmpFilename.sprintf("%s/AnKiImageImporter_%u.bmp", tempDirectory.cstr(), U32(std::rand()));
 	bmpFilename.sprintf("%s/AnKiImageImporter_%u.bmp", tempDirectory.cstr(), U32(std::rand()));
@@ -396,6 +411,106 @@ static ANKI_USE_RESULT Error compressS3tc(GenericMemoryPoolAllocator<U8> alloc,
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
+static ANKI_USE_RESULT Error compressAstc(GenericMemoryPoolAllocator<U8> alloc, CString tempDirectory,
+										  CString astcencPath, ConstWeakArray<U8, PtrSize> inPixels, U32 inWidth,
+										  U32 inHeight, UVec2 blockSize, WeakArray<U8, PtrSize> outPixels)
+{
+	const PtrSize blockBytes = 16;
+	const PtrSize channelCount = 4;
+	ANKI_ASSERT(inPixels.getSizeInBytes() == PtrSize(inWidth) * inHeight * channelCount);
+	ANKI_ASSERT(inWidth > 0 && isPowerOfTwo(inWidth) && inHeight > 0 && isPowerOfTwo(inHeight));
+	ANKI_ASSERT(outPixels.getSizeInBytes() == blockBytes * (inWidth / blockSize.x()) * (inHeight / blockSize.y()));
+
+	// Create a BMP image to feed to the astcebc
+	StringAuto bmpFilename(alloc);
+	bmpFilename.sprintf("%s/AnKiImageImporter_%u.bmp", tempDirectory.cstr(), U32(std::rand()));
+	if(!stbi_write_bmp(bmpFilename.cstr(), inWidth, inHeight, channelCount, inPixels.getBegin()))
+	{
+		ANKI_IMPORTER_LOGE("STB failed to create: %s", bmpFilename.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+	CleanupFile bmpCleanup(alloc, bmpFilename);
+
+	// Invoke the compressor process
+	StringAuto astcFilename(alloc);
+	astcFilename.sprintf("%s/AnKiImageImporter_%u.astc", tempDirectory.cstr(), U32(std::rand()));
+	StringAuto blockStr(alloc);
+	blockStr.sprintf("%ux%u", blockSize.x(), blockSize.y());
+	Process proc;
+	Array<CString, 5> args;
+	U32 argCount = 0;
+	args[argCount++] = "-cl";
+	args[argCount++] = bmpFilename;
+	args[argCount++] = astcFilename;
+	args[argCount++] = blockStr;
+	args[argCount++] = "-fast";
+
+	ANKI_CHECK(
+		proc.start("astcenc-avx2", args,
+				   (astcencPath.isEmpty()) ? ConstWeakArray<CString>() : Array<CString, 2>{{"PATH", astcencPath}}));
+
+	CleanupFile astcCleanup(alloc, astcFilename);
+	ProcessStatus status;
+	I32 exitCode;
+	ANKI_CHECK(proc.wait(60.0, &status, &exitCode));
+
+	if(status != ProcessStatus::NORMAL_EXIT || exitCode != 0)
+	{
+		StringAuto errStr(alloc);
+		if(exitCode != 0)
+		{
+			ANKI_CHECK(proc.readFromStdout(errStr));
+		}
+
+		if(errStr.isEmpty())
+		{
+			errStr = "Unknown error";
+		}
+
+		ANKI_IMPORTER_LOGE("Invoking astcenc-avx2 process failed: %s", errStr.cstr());
+		return Error::FUNCTION_FAILED;
+	}
+
+	// Read the astc file
+	File astcFile;
+	ANKI_CHECK(astcFile.open(astcFilename, FileOpenFlag::READ | FileOpenFlag::BINARY));
+	AstcHeader header;
+	ANKI_CHECK(astcFile.read(&header, sizeof(header)));
+
+	auto unpackBytes = [](U8 a, U8 b, U8 c, U8 d) -> U32 {
+		return (U32(a)) + (U32(b) << 8) + (U32(c) << 16) + (U32(d) << 24);
+	};
+
+	const U32 magicval = unpackBytes(header.m_magic[0], header.m_magic[1], header.m_magic[2], header.m_magic[3]);
+	if(magicval != 0x5CA1AB13)
+	{
+		ANKI_IMPORTER_LOGE("astcenc-avx2 produced a file with wrong magic");
+		return Error::FUNCTION_FAILED;
+	}
+
+	const U32 blockx = max<U32>(header.m_blockX, 1u);
+	const U32 blocky = max<U32>(header.m_blockY, 1u);
+	const U32 blockz = max<U32>(header.m_blockZ, 1u);
+	if(blockx != blockSize.x() || blocky != blockSize.y() || blockz != 1)
+	{
+		ANKI_IMPORTER_LOGE("astcenc-avx2 with wrong block size");
+		return Error::FUNCTION_FAILED;
+	}
+
+	const U32 dimx = unpackBytes(header.m_dimX[0], header.m_dimX[1], header.m_dimX[2], 0);
+	const U32 dimy = unpackBytes(header.m_dimY[0], header.m_dimY[1], header.m_dimY[2], 0);
+	const U32 dimz = unpackBytes(header.m_dimZ[0], header.m_dimZ[1], header.m_dimZ[2], 0);
+	if(dimx != inWidth || dimy != inHeight || dimz != 1)
+	{
+		ANKI_IMPORTER_LOGE("astcenc-avx2 with wrong image size");
+		return Error::FUNCTION_FAILED;
+	}
+
+	ANKI_CHECK(astcFile.read(outPixels.getBegin(), outPixels.getSizeInBytes()));
+
+	return Error::NONE;
+}
+
 static ANKI_USE_RESULT Error storeAnkiImage(const ImageImporterConfig& config, const ImageImporterContext& ctx)
 static ANKI_USE_RESULT Error storeAnkiImage(const ImageImporterConfig& config, const ImageImporterContext& ctx)
 {
 {
 	File outFile;
 	File outFile;
@@ -450,6 +565,24 @@ static ANKI_USE_RESULT Error storeAnkiImage(const ImageImporterConfig& config, c
 		}
 		}
 	}
 	}
 
 
+	// Write ASTC
+	if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
+	{
+		// for(I32 mip = I32(ctx.m_mipmaps.getSize()) - 1; mip >= 0; --mip)
+		for(U32 mip = 0; mip < ctx.m_mipmaps.getSize(); ++mip)
+		{
+			for(U32 l = 0; l < ctx.m_layerCount; ++l)
+			{
+				for(U32 f = 0; f < ctx.m_faceCount; ++f)
+				{
+					const U32 idx = l * ctx.m_faceCount + f;
+					const ConstWeakArray<U8, PtrSize> pixels = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx].m_astcPixels;
+					ANKI_CHECK(outFile.write(&pixels[0], pixels.getSizeInBytes()));
+				}
+			}
+		}
+	}
+
 	return Error::NONE;
 	return Error::NONE;
 }
 }
 
 
@@ -562,6 +695,33 @@ static ANKI_USE_RESULT Error importImageInternal(const ImageImporterConfig& conf
 		}
 		}
 	}
 	}
 
 
+	if(!!(config.m_compressions & ImageBinaryDataCompression::ASTC))
+	{
+		for(U32 mip = 0; mip < mipCount; ++mip)
+		{
+			for(U32 l = 0; l < ctx.m_layerCount; ++l)
+			{
+				for(U32 f = 0; f < ctx.m_faceCount; ++f)
+				{
+					const U32 idx = l * ctx.m_faceCount + f;
+					SurfaceOrVolumeData& surface = ctx.m_mipmaps[mip].m_surfacesOrVolume[idx];
+
+					const U32 width = ctx.m_width >> mip;
+					const U32 height = ctx.m_height >> mip;
+					const PtrSize blockSize = 16;
+					const PtrSize astcImageSize =
+						blockSize * (width / config.m_astcBlockSize.x()) * (height / config.m_astcBlockSize.y());
+
+					surface.m_astcPixels.create(astcImageSize);
+
+					ANKI_CHECK(compressAstc(alloc, config.m_tempDirectory, config.m_astcencPath,
+											ConstWeakArray<U8, PtrSize>(surface.m_pixels), width, height,
+											config.m_astcBlockSize, WeakArray<U8, PtrSize>(surface.m_astcPixels)));
+				}
+			}
+		}
+	}
+
 	if(!!(config.m_compressions & ImageBinaryDataCompression::ETC))
 	if(!!(config.m_compressions & ImageBinaryDataCompression::ETC))
 	{
 	{
 		ANKI_ASSERT(!"TODO");
 		ANKI_ASSERT(!"TODO");

+ 2 - 0
AnKi/Importer/ImageImporter.h

@@ -29,6 +29,8 @@ public:
 	Bool m_noAlpha = true;
 	Bool m_noAlpha = true;
 	CString m_tempDirectory;
 	CString m_tempDirectory;
 	CString m_compressonatorPath; ///< Optional.
 	CString m_compressonatorPath; ///< Optional.
+	CString m_astcencPath; ///< Optional.
+	UVec2 m_astcBlockSize = UVec2(8u);
 };
 };
 
 
 /// Converts images to AnKi's specific format.
 /// Converts images to AnKi's specific format.

+ 2 - 1
AnKi/Resource/ImageBinary.h

@@ -45,7 +45,8 @@ enum class ImageBinaryDataCompression : U32
 	NONE,
 	NONE,
 	RAW = 1 << 0,
 	RAW = 1 << 0,
 	S3TC = 1 << 1,
 	S3TC = 1 << 1,
-	ETC = 1 << 2
+	ETC = 1 << 2,
+	ASTC = 1 << 3
 };
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(ImageBinaryDataCompression)
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(ImageBinaryDataCompression)
 
 

+ 2 - 1
AnKi/Resource/ImageBinary.xml

@@ -36,7 +36,8 @@ enum class ImageBinaryDataCompression : U32
 	NONE,
 	NONE,
 	RAW = 1 << 0,
 	RAW = 1 << 0,
 	S3TC = 1 << 1,
 	S3TC = 1 << 1,
-	ETC = 1 << 2
+	ETC = 1 << 2,
+	ASTC = 1 << 3
 };
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(ImageBinaryDataCompression)
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(ImageBinaryDataCompression)
 ]]></prefix_code>
 ]]></prefix_code>

BIN
ThirdParty/Bin/astcenc-avx2


+ 65 - 8
Tools/Image/ImageImporterMain.cpp

@@ -10,18 +10,21 @@ using namespace anki;
 
 
 static const char* USAGE = R"(Usage: %s in_files out_file [options]
 static const char* USAGE = R"(Usage: %s in_files out_file [options]
 Options:
 Options:
--t <type>           : Image type. One of: 2D, 3D, Cube, 2DArray
--no-alpha           : If the image has alpha don't store it. By default it stores it
--store-s3tc <0|1>   : Store S3TC images. Default is 1
--store-raw <0|1>    : Store RAW images. Default is 0
--mip-count <number> : Max number of mipmaps. By default store until 4x4
+-t <type>              : Image type. One of: 2D, 3D, Cube, 2DArray
+-no-alpha              : If the image has alpha don't store it. By default it stores it
+-store-s3tc <0|1>      : Store S3TC images. Default is 1
+-store-astc <0|1>      : Store ASTC images. Default is 1
+-store-raw <0|1>       : Store RAW images. Default is 0
+-mip-count <number>    : Max number of mipmaps. By default store until 4x4
+-astc-block-size <XxY> : The size of the ASTC block size. eg 4x4. Default is 8x8
 )";
 )";
 
 
 static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& config,
 static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& config,
 								  DynamicArrayAuto<StringAuto>& filenames, DynamicArrayAuto<CString>& cfilenames)
 								  DynamicArrayAuto<StringAuto>& filenames, DynamicArrayAuto<CString>& cfilenames)
 {
 {
-	config.m_compressions = ImageBinaryDataCompression::S3TC;
+	config.m_compressions = ImageBinaryDataCompression::S3TC | ImageBinaryDataCompression::ASTC;
 	config.m_noAlpha = false;
 	config.m_noAlpha = false;
+	config.m_astcBlockSize = UVec2(8u);
 
 
 	// Parse config
 	// Parse config
 	if(argc < 3)
 	if(argc < 3)
@@ -76,7 +79,32 @@ static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& co
 			{
 			{
 				config.m_compressions |= ImageBinaryDataCompression::S3TC;
 				config.m_compressions |= ImageBinaryDataCompression::S3TC;
 			}
 			}
-			else if(CString(argv[i]) != "0")
+			else if(CString(argv[i]) == "0")
+			{
+				config.m_compressions = config.m_compressions & ~ImageBinaryDataCompression::S3TC;
+			}
+			else
+			{
+				return Error::USER_DATA;
+			}
+		}
+		else if(CString(argv[i]) == "-store-astc")
+		{
+			++i;
+			if(i >= argc)
+			{
+				return Error::USER_DATA;
+			}
+
+			if(CString(argv[i]) == "1")
+			{
+				config.m_compressions |= ImageBinaryDataCompression::ASTC;
+			}
+			else if(CString(argv[i]) == "0")
+			{
+				config.m_compressions = config.m_compressions & ~ImageBinaryDataCompression::ASTC;
+			}
+			else
 			{
 			{
 				return Error::USER_DATA;
 				return Error::USER_DATA;
 			}
 			}
@@ -93,7 +121,32 @@ static Error parseCommandLineArgs(int argc, char** argv, ImageImporterConfig& co
 			{
 			{
 				config.m_compressions |= ImageBinaryDataCompression::RAW;
 				config.m_compressions |= ImageBinaryDataCompression::RAW;
 			}
 			}
-			else if(CString(argv[i]) != "0")
+			else if(CString(argv[i]) == "0")
+			{
+				config.m_compressions = config.m_compressions & ~ImageBinaryDataCompression::RAW;
+			}
+			else
+			{
+				return Error::USER_DATA;
+			}
+		}
+		else if(CString(argv[i]) == "-astc-block-size")
+		{
+			++i;
+			if(i >= argc)
+			{
+				return Error::USER_DATA;
+			}
+
+			if(CString(argv[i]) == "4x4")
+			{
+				config.m_astcBlockSize = UVec2(4u);
+			}
+			else if(CString(argv[i]) == "8x8")
+			{
+				config.m_astcBlockSize = UVec2(8u);
+			}
+			else
 			{
 			{
 				return Error::USER_DATA;
 				return Error::USER_DATA;
 			}
 			}
@@ -159,6 +212,10 @@ int main(int argc, char** argv)
 	compressonatorPath.sprintf("%s/../../ThirdParty/Bin/Compressonator:%s", p.cstr(), getenv("PATH"));
 	compressonatorPath.sprintf("%s/../../ThirdParty/Bin/Compressonator:%s", p.cstr(), getenv("PATH"));
 	config.m_compressonatorPath = compressonatorPath;
 	config.m_compressonatorPath = compressonatorPath;
 
 
+	StringAuto astcencPath(alloc);
+	astcencPath.sprintf("%s/../../ThirdParty/Bin:%s", p.cstr(), getenv("PATH"));
+	config.m_astcencPath = astcencPath;
+
 	if(importImage(config))
 	if(importImage(config))
 	{
 	{
 		ANKI_IMPORTER_LOGE("Importing failed");
 		ANKI_IMPORTER_LOGE("Importing failed");