From cc61e4649bde4adaa005f9f10ebbc7ca7bdca52f Mon Sep 17 00:00:00 2001 From: Philip Rideout Date: Tue, 2 Oct 2018 11:32:09 -0700 Subject: [PATCH] Add ETC support to mipgen / imageio / suzanne. This shrinks file size as follows, although it seems to darken the rendered result. ``` 1398209 Oct 2 16:22 ao.ktx 699172 Oct 2 16:20 ao_etc.ktx 1398209 Oct 2 16:22 metallic.ktx 699172 Oct 2 16:19 metallic_etc.ktx 1398209 Oct 2 16:22 roughness.ktx 699172 Oct 2 16:20 roughness_etc.ktx ``` --- libs/image/include/image/KtxBundle.h | 11 +++ .../include/imageio/BlockCompression.h | 96 +++++++++++++----- libs/imageio/src/BlockCompression.cpp | 98 +++++++++++++++++++ samples/web/CMakeLists.txt | 5 + samples/web/filaweb.cpp | 10 ++ samples/web/filaweb.js | 8 +- samples/web/suzanne.cpp | 16 +-- samples/web/suzanne.html | 11 ++- tools/mipgen/src/main.cpp | 26 ++++- 9 files changed, 236 insertions(+), 45 deletions(-) diff --git a/libs/image/include/image/KtxBundle.h b/libs/image/include/image/KtxBundle.h index b81cc7c216..2b8b27dcb8 100644 --- a/libs/image/include/image/KtxBundle.h +++ b/libs/image/include/image/KtxBundle.h @@ -189,6 +189,17 @@ public: static constexpr uint32_t SRGB8_ALPHA8_ASTC_12x10 = 0x93DC; static constexpr uint32_t SRGB8_ALPHA8_ASTC_12x12 = 0x93DD; + static constexpr uint32_t R11_EAC = 0x9270; + static constexpr uint32_t SIGNED_R11_EAC = 0x9271; + static constexpr uint32_t RG11_EAC = 0x9272; + static constexpr uint32_t SIGNED_RG11_EAC = 0x9273; + static constexpr uint32_t RGB8_ETC2 = 0x9274; + static constexpr uint32_t SRGB8_ETC2 = 0x9275; + static constexpr uint32_t RGB8_ALPHA1_ETC2 = 0x9276; + static constexpr uint32_t SRGB8_ALPHA1_ETC = 0x9277; + static constexpr uint32_t RGBA8_ETC2_EAC = 0x9278; + static constexpr uint32_t SRGB8_ALPHA8_ETC2_EAC = 0x9279; + private: image::KtxInfo mInfo = {}; uint32_t mNumMipLevels; diff --git a/libs/imageio/include/imageio/BlockCompression.h b/libs/imageio/include/imageio/BlockCompression.h index 731214cce0..5e569e6e95 100644 --- a/libs/imageio/include/imageio/BlockCompression.h +++ b/libs/imageio/include/imageio/BlockCompression.h @@ -30,35 +30,20 @@ namespace image { -// Controls how fast compression occurs at the cost of quality in the resulting image. -enum class AstcPreset { - VERYFAST, - FAST, - MEDIUM, - THOROUGH, - EXHAUSTIVE, -}; - -// Informs the encoder what texels represent; this is especially crucial for normal maps. -enum class AstcSemantic { - COLORS_LDR, - COLORS_HDR, - NORMALS, -}; - -// 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; - math::ushort2 blocksize; - bool srgb; -}; - enum class CompressedFormat { INVALID = 0, + R11_EAC = 0x9270, + SIGNED_R11_EAC = 0x9271, + RG11_EAC = 0x9272, + SIGNED_RG11_EAC = 0x9273, + RGB8_ETC2 = 0x9274, + SRGB8_ETC2 = 0x9275, + RGB8_ALPHA1_ETC2 = 0x9276, + SRGB8_ALPHA1_ETC = 0x9277, + RGBA8_ETC2_EAC = 0x9278, + SRGB8_ALPHA8_ETC2_EAC = 0x9279, + RGB_S3TC_DXT1 = 0x83F0, RGBA_S3TC_DXT1 = 0x83F1, RGBA_S3TC_DXT3 = 0x83F2, @@ -105,6 +90,34 @@ struct CompressedTexture { std::unique_ptr data; }; +// ASTC //////////////////////////////////////////////////////////////////////////////////////////// + +// Controls how fast compression occurs at the cost of quality in the resulting image. +enum class AstcPreset { + VERYFAST, + FAST, + MEDIUM, + THOROUGH, + EXHAUSTIVE, +}; + +// Informs the encoder what texels represent; this is especially crucial for normal maps. +enum class AstcSemantic { + COLORS_LDR, + COLORS_HDR, + NORMALS, +}; + +// 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; + math::ushort2 blocksize; + bool srgb; +}; + // Uses the CPU to compress a linear image (1 to 4 channels) into an ASTC texture. The 16-byte // header block that ARM uses in their file format is not included. CompressedTexture astcCompress(const LinearImage& source, AstcConfig config); @@ -115,6 +128,37 @@ CompressedTexture astcCompress(const LinearImage& source, AstcConfig config); // thorough_normals_6x6, veryfast_hdr_12x10 AstcConfig astcParseOptionString(const std::string& options); +// ETC ///////////////////////////////////////////////////////////////////////////////////////////// + +enum class EtcErrorMetric { + RGBA, + RGBX, + REC709, + NUMERIC, + NORMALXYZ, +}; + +// Informs the ETC encoder of the desired output. Effort sets the quality / speed tradeoff with +// a number between 0 and 100. +struct EtcConfig { + CompressedFormat format; + EtcErrorMetric metric; + int effort; +}; + +// Uses the CPU to compress a linear image (1 to 4 channels) into an ETC texture. +CompressedTexture etcCompress(const LinearImage& source, EtcConfig config); + +// Converts a string into an ETC compression configuration where the string has the form +// FORMAT_METRIC_EFFORT where: +// - FORMAT is one of: r11, signed_r11, rg11, signed_rg11, rgb8, srgb8, rgb8_alpha, +// srgb8_alpha, rgba8, and srgb8_alpha8 +// - METRIC is one of: rgba, rgbx, rec709, numeric, and normalxyz +// - EFFORT is an integer between 0 and 100 +EtcConfig etcParseOptionString(const std::string& options); + +// S3TC //////////////////////////////////////////////////////////////////////////////////////////// + // Informs the S3TC encoder of the desired output. struct S3tcConfig { CompressedFormat format; diff --git a/libs/imageio/src/BlockCompression.cpp b/libs/imageio/src/BlockCompression.cpp index efee7410e7..a350e61a6b 100644 --- a/libs/imageio/src/BlockCompression.cpp +++ b/libs/imageio/src/BlockCompression.cpp @@ -21,6 +21,7 @@ #include #include +#include #define STB_DXT_IMPLEMENTATION #include @@ -314,6 +315,103 @@ S3tcConfig s3tcParseOptionString(const std::string& options) { return {}; } +CompressedTexture etcCompress(const LinearImage& original, EtcConfig config) { + LinearImage source = extendToFourChannels(original); + constexpr int threadcount = 1; // TODO: set this thread count + Etc::Image::Format etcformat; + switch (config.format) { + case CompressedFormat::R11_EAC: etcformat = Etc::Image::Format::R11; break; + case CompressedFormat::SIGNED_R11_EAC: etcformat = Etc::Image::Format::SIGNED_R11; break; + case CompressedFormat::RG11_EAC: etcformat = Etc::Image::Format::RG11; break; + case CompressedFormat::SIGNED_RG11_EAC: etcformat = Etc::Image::Format::SIGNED_RG11; break; + case CompressedFormat::RGB8_ETC2: etcformat = Etc::Image::Format::RGB8; break; + case CompressedFormat::SRGB8_ETC2: etcformat = Etc::Image::Format::SRGB8; break; + case CompressedFormat::RGB8_ALPHA1_ETC2: etcformat = Etc::Image::Format::RGB8A1; break; + case CompressedFormat::SRGB8_ALPHA1_ETC: etcformat = Etc::Image::Format::SRGB8A1; break; + case CompressedFormat::RGBA8_ETC2_EAC: etcformat = Etc::Image::Format::RGBA8; break; + case CompressedFormat::SRGB8_ALPHA8_ETC2_EAC: etcformat = Etc::Image::Format::SRGBA8; break; + default: return {}; + } + Etc::ErrorMetric etcmetric; + switch (config.metric) { + case EtcErrorMetric::RGBA: etcmetric = Etc::RGBA; break; + case EtcErrorMetric::RGBX: etcmetric = Etc::RGBX; break; + case EtcErrorMetric::REC709: etcmetric = Etc::REC709; break; + case EtcErrorMetric::NUMERIC: etcmetric = Etc::NUMERIC; break; + case EtcErrorMetric::NORMALXYZ: etcmetric = Etc::NORMALXYZ; break; + default: return {}; + } + unsigned char *paucEncodingBits; + unsigned int uiEncodingBitsBytes; + unsigned int uiExtendedWidth; + unsigned int uiExtendedHeight; + int iEncodingTime_ms; + + // The etc2comp API doesn't tell you that you need to free paucEncodingBits, but they have a + // commented-out "delete[] m_paucEncodingBits" in their Image destructor, which is essentially + // what our unique_ptr wrapper does (CompressedTexture::data). + + Etc::Encode(source.getPixelRef(0, 0), + source.getWidth(), source.getHeight(), + etcformat, + etcmetric, + config.effort, + threadcount, + 1024, + &paucEncodingBits, &uiEncodingBitsBytes, + &uiExtendedWidth, &uiExtendedHeight, + &iEncodingTime_ms); + + return { + .format = config.format, + .size = uiEncodingBitsBytes, + .data = decltype(CompressedTexture::data)(paucEncodingBits) + }; +} + +EtcConfig etcParseOptionString(const std::string& options) { + EtcConfig result {}; + const size_t _2 = options.rfind('_'); + const size_t _1 = options.rfind('_', _2 - 1); + std::string sformat = options.substr(0, _1); + std::string smetric = options.substr(_1 + 1, _2 - _1 - 1); + std::string seffort = options.substr(_2 + 1); + if (sformat == "r11") { + result.format = CompressedFormat::R11_EAC; + } else if (sformat == "signed_r11") { + result.format = CompressedFormat::SIGNED_R11_EAC; + } else if (sformat == "rg11") { + result.format = CompressedFormat::RG11_EAC; + } else if (sformat == "signed_rg11") { + result.format = CompressedFormat::SIGNED_RG11_EAC; + } else if (sformat == "rgb8") { + result.format = CompressedFormat::RGB8_ETC2; + } else if (sformat == "srgb8") { + result.format = CompressedFormat::SRGB8_ETC2; + } else if (sformat == "rgb8_alpha") { + result.format = CompressedFormat::RGB8_ALPHA1_ETC2; + } else if (sformat == "srgb8_alpha") { + result.format = CompressedFormat::SRGB8_ALPHA1_ETC; + } else if (sformat == "rgba8_etc2") { + result.format = CompressedFormat::RGBA8_ETC2_EAC; + } else if (sformat == "srgb8_alpha8_etc2") { + result.format = CompressedFormat::SRGB8_ALPHA8_ETC2_EAC; + } + if (smetric == "rgba") { + result.metric = EtcErrorMetric::RGBA; + } else if (smetric == "rgbx") { + result.metric = EtcErrorMetric::RGBX; + } else if (smetric == "rec709") { + result.metric = EtcErrorMetric::REC709; + } else if (smetric == "numeric") { + result.metric = EtcErrorMetric::NUMERIC; + } else if (smetric == "normalxyz") { + result.metric = EtcErrorMetric::NORMALXYZ; + } + result.effort = std::stoi(seffort); + return result; +} + static LinearImage extendToFourChannels(LinearImage original) { LinearImage source = original; const uint32_t width = source.getWidth(); diff --git a/samples/web/CMakeLists.txt b/samples/web/CMakeLists.txt index 71f1f55e1e..4d6a3b92b9 100644 --- a/samples/web/CMakeLists.txt +++ b/samples/web/CMakeLists.txt @@ -83,13 +83,18 @@ function(add_ktxfiles SOURCE TARGET EXTRA_ARGS) DEPENDS mipgen) endfunction() +set(ETC_R11_ARGS "--grayscale;--compression=etc_r11_numeric_40") + add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo.ktx" "") add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo_astc.ktx" "--compression=astc_fast_ldr_4x4") add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo_s3tc.ktx" "--compression=s3tc_rgb_dxt1") add_ktxfiles("assets/models/monkey/normal.png" "monkey/normal.ktx" "--kernel=NORMALS;--linear") add_ktxfiles("assets/models/monkey/roughness.png" "monkey/roughness.ktx" "--grayscale") +add_ktxfiles("assets/models/monkey/roughness.png" "monkey/roughness_etc.ktx" "${ETC_R11_ARGS}") add_ktxfiles("assets/models/monkey/metallic.png" "monkey/metallic.ktx" "--grayscale") +add_ktxfiles("assets/models/monkey/metallic.png" "monkey/metallic_etc.ktx" "${ETC_R11_ARGS}") add_ktxfiles("assets/models/monkey/ao.png" "monkey/ao.ktx" "--grayscale") +add_ktxfiles("assets/models/monkey/ao.png" "monkey/ao_etc.ktx" "${ETC_R11_ARGS}") # Convert OBJ files into filamesh files. function(add_mesh SOURCE TARGET) diff --git a/samples/web/filaweb.cpp b/samples/web/filaweb.cpp index ea6b466145..e00142464c 100644 --- a/samples/web/filaweb.cpp +++ b/samples/web/filaweb.cpp @@ -348,6 +348,16 @@ T toFilamentEnum(uint32_t format) { case KtxBundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10; case KtxBundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10; case KtxBundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12; + case KtxBundle::R11_EAC: return T::EAC_R11; + case KtxBundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED; + case KtxBundle::RG11_EAC: return T::EAC_RG11; + case KtxBundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED; + case KtxBundle::RGB8_ETC2: return T::ETC2_RGB8; + case KtxBundle::SRGB8_ETC2: return T::ETC2_SRGB8; + case KtxBundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1; + case KtxBundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1; + case KtxBundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8; + case KtxBundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8; } return (T) 0xffff; } diff --git a/samples/web/filaweb.js b/samples/web/filaweb.js index f92fc11ee4..cec356d99e 100644 --- a/samples/web/filaweb.js +++ b/samples/web/filaweb.js @@ -6,7 +6,7 @@ let queued_mouse_events = []; let use_astc = false; let use_s3tc = false; -let use_etc1 = false; +let use_etc = false; let canvas = document.getElementById('filament-canvas'); let ctx = GL.createContext(canvas, { @@ -19,12 +19,14 @@ let ctx = GL.createContext(canvas, { GL.makeContextCurrent(ctx); for (let ext of Module.ctx.getSupportedExtensions()) { if (ext == "WEBGL_compressed_texture_s3tc") { + Module.ctx.getExtension('WEBGL_compressed_texture_s3tc'); use_s3tc = true; } else if (ext == "WEBGL_compressed_texture_astc") { Module.ctx.getExtension('WEBGL_compressed_texture_astc'); use_astc = true; - } else if (ext == "WEBGL_compressed_texture_etc1") { - use_etc1 = true; + } else if (ext == "WEBGL_compressed_texture_etc") { + Module.ctx.getExtension('WEBGL_compressed_texture_etc'); + use_etc = true; } } diff --git a/samples/web/suzanne.cpp b/samples/web/suzanne.cpp index 12dbd72ba4..aeba90ae7a 100644 --- a/samples/web/suzanne.cpp +++ b/samples/web/suzanne.cpp @@ -66,14 +66,6 @@ static Texture* setTextureParameter(Engine& engine, filaweb::Asset& asset, strin asset->texture.reset(); }; - Texture::Format format; - switch (info.glTypeSize) { - case 1: format = Texture::Format::R; break; - case 2: format = Texture::Format::RG; break; - case 3: format = Texture::Format::RGB; break; - case 4: format = Texture::Format::RGBA; break; - } - uint8_t* data; uint32_t nbytes; asset.texture->getBlob({}, &data, &nbytes); @@ -97,6 +89,14 @@ static Texture* setTextureParameter(Engine& engine, filaweb::Asset& asset, strin return texture; } + Texture::Format format; + switch (info.glTypeSize) { + case 1: format = Texture::Format::R; break; + case 2: format = Texture::Format::RG; break; + case 3: format = Texture::Format::RGB; break; + case 4: format = Texture::Format::RGBA; break; + } + Texture::PixelBufferDescriptor pb(data, nbytes, format, Texture::Type::UBYTE, destructor, &asset); diff --git a/samples/web/suzanne.html b/samples/web/suzanne.html index 42976a0e82..da43b2bd9f 100644 --- a/samples/web/suzanne.html +++ b/samples/web/suzanne.html @@ -22,14 +22,19 @@