diff --git a/libs/imageio/include/imageio/BlockCompression.h b/libs/imageio/include/imageio/BlockCompression.h index 362c59c701..42022c589e 100644 --- a/libs/imageio/include/imageio/BlockCompression.h +++ b/libs/imageio/include/imageio/BlockCompression.h @@ -24,6 +24,8 @@ #include #include +#include + #include namespace image { @@ -44,18 +46,51 @@ enum class AstcSemantic { NORMALS, }; -// Gets passed to the encoder function to control the quality and speed of compression. -// The specified bitrate can be anywhere from 0.8 to 8.0. +// The encoder configuration controls the quality and speed of compression, as well as the resulting +// format. The specified block size must be one of the 14 block sizes that can be consumed by ES 3.2 +// as per https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glCompressedTexImage2D.xhtml struct AstcConfig { AstcPreset quality; AstcSemantic semantic; - float bitrate; + math::ushort2 blocksize; + bool srgb; +}; + +enum class AstcFormat { + RGBA_ASTC_4x4 = 0x93B0, + RGBA_ASTC_5x4 = 0x93B1, + RGBA_ASTC_5x5 = 0x93B2, + RGBA_ASTC_6x5 = 0x93B3, + RGBA_ASTC_6x6 = 0x93B4, + RGBA_ASTC_8x5 = 0x93B5, + RGBA_ASTC_8x6 = 0x93B6, + RGBA_ASTC_8x8 = 0x93B7, + RGBA_ASTC_10x5 = 0x93B8, + RGBA_ASTC_10x6 = 0x93B9, + RGBA_ASTC_10x8 = 0x93BA, + RGBA_ASTC_10x10 = 0x93BB, + RGBA_ASTC_12x10 = 0x93BC, + RGBA_ASTC_12x12 = 0x93BD, + SRGB8_ALPHA8_ASTC_4x4 = 0x93D0, + SRGB8_ALPHA8_ASTC_5x4 = 0x93D1, + SRGB8_ALPHA8_ASTC_5x5 = 0x93D2, + SRGB8_ALPHA8_ASTC_6x5 = 0x93D3, + SRGB8_ALPHA8_ASTC_6x6 = 0x93D4, + SRGB8_ALPHA8_ASTC_8x5 = 0x93D5, + SRGB8_ALPHA8_ASTC_8x6 = 0x93D6, + SRGB8_ALPHA8_ASTC_8x8 = 0x93D7, + SRGB8_ALPHA8_ASTC_10x5 = 0x93D8, + SRGB8_ALPHA8_ASTC_10x6 = 0x93D9, + SRGB8_ALPHA8_ASTC_10x8 = 0x93DA, + SRGB8_ALPHA8_ASTC_10x10 = 0x93DB, + SRGB8_ALPHA8_ASTC_12x10 = 0x93DC, + SRGB8_ALPHA8_ASTC_12x12 = 0x93DD, }; // Represents the result of compression, including which of the 28 internal formats that the // encoder finally settled on, based on the hints supplied in AstcConfig. struct AstcTexture { - const uint32_t gl_internal_format; + const AstcFormat format; const uint32_t size; std::unique_ptr data; }; @@ -66,8 +101,8 @@ AstcTexture astcCompress(const LinearImage& source, AstcConfig config); // Parses a simple underscore-delimited string to produce an ASTC compression configuration. This // makes it easy to incorporate the compression API into command-line tools. If the string is -// malformed, this returns a config with a 0 bitrate. Example strings: fast_ldr_4.2, -// thorough_normals_8.0, veryfast_hdr_1.0 +// malformed, this returns a config with a 0x0 blocksize. Example strings: fast_ldr_4x4, +// thorough_normals_6x6, veryfast_hdr_12x10 AstcConfig astcParseOptionString(const std::string& options); } // namespace image diff --git a/libs/imageio/src/BlockCompression.cpp b/libs/imageio/src/BlockCompression.cpp index 285f7e8525..00045e0960 100644 --- a/libs/imageio/src/BlockCompression.cpp +++ b/libs/imageio/src/BlockCompression.cpp @@ -40,6 +40,41 @@ AstcTexture astcCompress(const LinearImage& original, AstcConfig config) { first = false; } + // Check the validity of the given block size. + + AstcFormat format; + if (config.blocksize == math::ushort2 {4, 4}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_4x4 : AstcFormat::RGBA_ASTC_4x4; + } else if (config.blocksize == math::ushort2 {5, 4}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_5x4 : AstcFormat::RGBA_ASTC_5x4; + } else if (config.blocksize == math::ushort2 {5, 5}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_5x5 : AstcFormat::RGBA_ASTC_5x5; + } else if (config.blocksize == math::ushort2 {6, 5}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_6x5 : AstcFormat::RGBA_ASTC_6x5; + } else if (config.blocksize == math::ushort2 {6, 6}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_6x6 : AstcFormat::RGBA_ASTC_6x6; + } else if (config.blocksize == math::ushort2 {8, 5}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_8x5 : AstcFormat::RGBA_ASTC_8x5; + } else if (config.blocksize == math::ushort2 {8, 6}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_8x6 : AstcFormat::RGBA_ASTC_8x6; + } else if (config.blocksize == math::ushort2 {8, 8}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_8x8 : AstcFormat::RGBA_ASTC_8x8; + } else if (config.blocksize == math::ushort2 {10, 5}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_10x5 : AstcFormat::RGBA_ASTC_10x5; + } else if (config.blocksize == math::ushort2 {10, 6}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_10x6 : AstcFormat::RGBA_ASTC_10x6; + } else if (config.blocksize == math::ushort2 {10, 8}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_10x8 : AstcFormat::RGBA_ASTC_10x8; + } else if (config.blocksize == math::ushort2 {10, 10}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_10x10 : AstcFormat::RGBA_ASTC_10x10; + } else if (config.blocksize == math::ushort2 {12, 10}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_12x10 : AstcFormat::RGBA_ASTC_12x10; + } else if (config.blocksize == math::ushort2 {12, 12}) { + format = config.srgb ? AstcFormat::SRGB8_ALPHA8_ASTC_12x12 : AstcFormat::RGBA_ASTC_12x12; + } else { + return {}; + } + // Create an input image for the ARM encoder in a format that it can consume. // It expects four-channel data, so we extend or curtail the channel count in a reasonable way. // The encoder can take half-floats or bytes, but we always give it half-floats. @@ -93,13 +128,16 @@ AstcTexture astcCompress(const LinearImage& original, AstcConfig config) { } } - // Determine the block size based on the bit rate. + // Determine the bitrate based on the specified block size. - int xdim_2d, ydim_2d; - int xdim_3d, ydim_3d, zdim_3d; - find_closest_blockdim_2d(config.bitrate, &xdim_2d, &ydim_2d, 0); - find_closest_blockdim_3d(config.bitrate, &xdim_3d, &ydim_3d, &zdim_3d, 0); + int xdim_2d = config.blocksize.x, ydim_2d = config.blocksize.y; const float log10_texels_2d = std::log((float)(xdim_2d * ydim_2d)) / std::log(10.0f); + const float bitrate = 128.0 / (xdim_2d * ydim_2d); + + // We do not fully support 3D textures yet, but we include some of the 3D config params anyway. + + int xdim_3d, ydim_3d, zdim_3d; + find_closest_blockdim_3d(bitrate, &xdim_3d, &ydim_3d, &zdim_3d, 0); const float log10_texels_3d = std::log((float)(xdim_3d * ydim_3d * zdim_3d)) / log(10.0f); // Set up presets. @@ -173,11 +211,6 @@ AstcTexture astcCompress(const LinearImage& original, AstcConfig config) { int xdim = xdim_2d, ydim = ydim_2d, zdim = 1; expand_block_artifact_suppression(xdim, ydim, zdim, &ewp); - // To help with debugging, dump the encoding settings in a format similar to the astcenc tool - printf("2D Block size: %dx%d (%.2f bpp)\n", xdim_2d, ydim_2d, 128.0 / (xdim_2d * ydim_2d)); - printf("3D Block size: %dx%dx%d (%.2f bpp)\n", - xdim_3d, ydim_3d, zdim_3d, 128.0 / (xdim_3d * ydim_3d * zdim_3d)); - // Perform compression. constexpr int threadcount = 1; // TODO: set this thread count @@ -200,8 +233,8 @@ AstcTexture astcCompress(const LinearImage& original, AstcConfig config) { destroy_image(input_image); - return AstcTexture { - .gl_internal_format = 0, // TODO: figure out the correct GL enum here + return { + .format = format, .size = size, .data = decltype(AstcTexture::data)(buffer) }; @@ -215,7 +248,7 @@ AstcConfig astcParseOptionString(const string& configString) { } string quality = configString.substr(0, _1); string semantic = configString.substr(_1 + 1, _2 - _1 - 1); - string bitrate = configString.substr(_2 + 1); + string blocksize = configString.substr(_2 + 1); AstcConfig config; if (quality == "veryfast") { config.quality = AstcPreset::VERYFAST; @@ -239,7 +272,13 @@ AstcConfig astcParseOptionString(const string& configString) { } else { return {}; } - config.bitrate = std::stof(bitrate); + + const size_t _x = blocksize.find('x'); + if (_x == string::npos) { + return {}; + } + config.blocksize[0] = std::stoi(blocksize.substr(0, _x)); + config.blocksize[1] = std::stoi(blocksize.substr(_x + 1)); return config; } diff --git a/tools/mipgen/CMakeLists.txt b/tools/mipgen/CMakeLists.txt index a15b17227c..5633f49fd1 100644 --- a/tools/mipgen/CMakeLists.txt +++ b/tools/mipgen/CMakeLists.txt @@ -12,7 +12,7 @@ set(SRCS src/main.cpp) # Target definitions # ================================================================================================== add_executable(${TARGET} ${SRCS}) -target_link_libraries(${TARGET} PRIVATE math utils z image imageio getopt) +target_link_libraries(${TARGET} PRIVATE math utils z image imageio getopt stb) # ================================================================================================= # Licenses diff --git a/tools/mipgen/src/main.cpp b/tools/mipgen/src/main.cpp index 66f06f3db2..f7604a4300 100644 --- a/tools/mipgen/src/main.cpp +++ b/tools/mipgen/src/main.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -72,18 +73,21 @@ Options: specify output file format, inferred from output pattern if omitted --kernel=[box|nearest|hermite|gaussian|normals|mitchell|lanczos|min], -k [filter] specify filter kernel type (defaults to lanczos) + the "normals" filter may automatically change the compression scheme --strip-alpha ignore the alpha component of the input image --compression=COMPRESSION, -c COMPRESSION format specific compression: + KTX: astc_[fast/thorough]_[ldr/hdr]_WxH, where WxH is a valid block size PNG: Ignored Radiance: Ignored Photoshop: 16 (default), 32 OpenEXR: RAW, RLE, ZIPS, ZIP, PIZ (default) DDS: 8, 16 (default), 32 -Example: +Examples: MIPGEN -g --kernel=hermite grassland.png mip_%03d.png + MIPGEN -f ktx --compression=astc_fast_ldr_4x4 grassland.png mips.ktx )TXT"; static const char* HTML_PREFIX = R"HTML( @@ -271,9 +275,35 @@ int main(int argc, char* argv[]) { info.glInternalFormat = info.glBaseInternalFormat = KtxBundle::LUMINANCE; } + AstcConfig astcConfig {}; + if (!g_compression.empty()) { + if (g_compression.substr(0, 5) == "astc_") { + string suffix = g_compression.substr(5); + astcConfig = astcParseOptionString(suffix); + auto block = astcConfig.blocksize; + printf("Compressing with blocksize = %dx%d\n", block[0], block[1]); + } + if (astcConfig.blocksize[0] == 0) { + cerr << "Unrecognized compression: " << g_compression << endl; + return 1; + } + } uint32_t mip = 0; - auto addLevel = [&container, &mip](const LinearImage& image) { + auto addLevel = [&container, &mip, astcConfig, inputPath](const LinearImage& image) { std::unique_ptr data; + if (astcConfig.blocksize[0] > 0) { + // The ASTC encoder calls exit(1) if it fails, so it's very useful to print some + // source image information here for when this is invoked from a build script. + // Note that the encoder has limitations in terms of image size. + printf("Starting ASTC compression for %s (%dx%d)\n", inputPath.getName().c_str(), + image.getWidth(), image.getHeight()); + AstcTexture tex = astcCompress(image, astcConfig); + // Add newline here because the ASTC encoder has a progress indicator that issues a + // carriage return without a line feed. + putc('\n', stdout); + container.setBlob({mip++}, tex.data.get(), tex.size); + return; + } if (g_grayscale && g_linearized) { data = fromLinearToGrayscale(image); } else if (g_grayscale) {