bec4114bea
This change makes it possible to use Crunch algorithms for ETC textures with Alpha channel.
Explanation:
For simplicity, Crunch algorithms currently do not use ETC2 specific modes (T, H or P). For this reason, the currently used ETC2A compression format is technically equivalent to ETC1 + Alpha. Note that ETC2 encoding is a superset of ETC1, so any texture, which consists of ETC1 color blocks and ETC2 Alpha blocks, can be correctly decoded by an ETC2A (ETC2_RGBA8) decoder.
Compression scheme for ETC2 Alpha blocks is equivalent to the compression scheme for DXT5 Alpha blocks. ETC2 Alpha endpoint clusterization is performed based on the very same output of the Alpha palettizer which is used for DXT5 Alpha. The only part which is actually different is the Alpha endpoint optimization step.
In order to perform ETC2 Alpha encoding, we can first run the already existing algorithm for DXT5 Alpha endpoint optimization, in order to obtain the initial approximate solution. Then the approximate solution is refined based on the ETC2 Alpha modifier table. When performing raw ETC2A encoding, all the 16 ETC2 Alpha modifiers are used during optimization. However, when performing ETC2A quantization, for performance reasons, only 2 Alpha modifiers are currently used (modifier 13, which allows to perform precise approximation on short Alpha intervals, and modifier 11, which has more or less regularly distributed values, and is used for large Alpha intervals).
For compatibility reasons, ETC2 color compression wrappers have also been added to the code, though, as has been mentioned before, at the current moment ETC2 specific modes are not used, so ETC2 color compression is currently equivalent to ETC1 compression.
The ETC decoder functionality has been significantly extended, Crunch is now capable to decode ETC2 and ETC2A textures (input ETC2 textures can have T, H or P blocks).
In order to use ETC2A compression, use the -ETC2A command line option (i.e. "crunch_x64.exe -ETC2A input.png"). By default, compressed ETC2A textures will be decompressed into KTX file format.
DXT Testing:
The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). All the decompressed test images are identical to the images being compressed and decompressed using original version of Crunch (revision ea9b8d8).
[Compressing Kodak set without mipmaps using DXT1 encoding]
Original: 1582222 bytes / 28.880 sec
Modified: 1468204 bytes / 13.288 sec
Improvement: 7.21% (compression ratio) / 53.99% (compression time)
[Compressing Kodak set with mipmaps using DXT1 encoding]
Original: 2065243 bytes / 36.936 sec
Modified: 1914805 bytes / 18.044 sec
Improvement: 7.28% (compression ratio) / 51.15% (compression time)
ETC Testing:
The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). The ETC1 quantization parameters have been selected in such a way, so that ETC1 compression gives approximately the same average Luma PSNR as the corresponding DXT1 compression (which is equal to 34.044 dB for the Kodak test set compressed without mipmaps using DXT1 encoding and default quality settings).
[Compressing Kodak set without mipmaps using ETC1 encoding]
Total size: 1607858 bytes
Total time: 17.361 sec
Average bitrate: 1.363 bpp
Average Luma PSNR: 34.050 dB
292 lines
10 KiB
C++
292 lines
10 KiB
C++
// File: crn_ktx_texture.h
|
|
#ifndef _KTX_TEXTURE_H_
|
|
#define _KTX_TEXTURE_H_
|
|
#ifdef _MSC_VER
|
|
#pragma once
|
|
#endif
|
|
|
|
#include "crn_data_stream_serializer.h"
|
|
|
|
#define KTX_ENDIAN 0x04030201
|
|
#define KTX_OPPOSITE_ENDIAN 0x01020304
|
|
|
|
namespace crnlib {
|
|
extern const uint8 s_ktx_file_id[12];
|
|
|
|
struct ktx_header {
|
|
uint8 m_identifier[12];
|
|
uint32 m_endianness;
|
|
uint32 m_glType;
|
|
uint32 m_glTypeSize;
|
|
uint32 m_glFormat;
|
|
uint32 m_glInternalFormat;
|
|
uint32 m_glBaseInternalFormat;
|
|
uint32 m_pixelWidth;
|
|
uint32 m_pixelHeight;
|
|
uint32 m_pixelDepth;
|
|
uint32 m_numberOfArrayElements;
|
|
uint32 m_numberOfFaces;
|
|
uint32 m_numberOfMipmapLevels;
|
|
uint32 m_bytesOfKeyValueData;
|
|
|
|
void clear() {
|
|
memset(this, 0, sizeof(*this));
|
|
}
|
|
|
|
void endian_swap() {
|
|
utils::endian_swap_mem32(&m_endianness, (sizeof(*this) - sizeof(m_identifier)) / sizeof(uint32));
|
|
}
|
|
};
|
|
|
|
typedef crnlib::vector<uint8_vec> ktx_key_value_vec;
|
|
typedef crnlib::vector<uint8_vec> ktx_image_data_vec;
|
|
|
|
// Compressed pixel data formats: ETC1, DXT1, DXT3, DXT5
|
|
enum {
|
|
KTX_ETC1_RGB8_OES = 0x8D64,
|
|
KTX_COMPRESSED_RGB8_ETC2 = 0x9274,
|
|
KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
|
|
KTX_RGB_S3TC = 0x83A0,
|
|
KTX_RGB4_S3TC = 0x83A1,
|
|
KTX_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0,
|
|
KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1,
|
|
KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C,
|
|
KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D,
|
|
KTX_RGBA_S3TC = 0x83A2,
|
|
KTX_RGBA4_S3TC = 0x83A3,
|
|
KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2,
|
|
KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E,
|
|
KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3,
|
|
KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F,
|
|
KTX_RGBA_DXT5_S3TC = 0x83A4,
|
|
KTX_RGBA4_DXT5_S3TC = 0x83A5,
|
|
KTX_COMPRESSED_RED_RGTC1_EXT = 0x8DBB,
|
|
KTX_COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC,
|
|
KTX_COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD,
|
|
KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE,
|
|
KTX_COMPRESSED_LUMINANCE_LATC1_EXT = 0x8C70,
|
|
KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT = 0x8C71,
|
|
KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C72,
|
|
KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C73
|
|
};
|
|
|
|
// Pixel formats (various internal, base, and base internal formats)
|
|
enum {
|
|
KTX_R8 = 0x8229,
|
|
KTX_R8UI = 0x8232,
|
|
KTX_RGB8 = 0x8051,
|
|
KTX_SRGB8 = 0x8C41,
|
|
KTX_SRGB = 0x8C40,
|
|
KTX_SRGB_ALPHA = 0x8C42,
|
|
KTX_SRGB8_ALPHA8 = 0x8C43,
|
|
KTX_RGBA8 = 0x8058,
|
|
KTX_STENCIL_INDEX = 0x1901,
|
|
KTX_DEPTH_COMPONENT = 0x1902,
|
|
KTX_DEPTH_STENCIL = 0x84F9,
|
|
KTX_RED = 0x1903,
|
|
KTX_GREEN = 0x1904,
|
|
KTX_BLUE = 0x1905,
|
|
KTX_ALPHA = 0x1906,
|
|
KTX_RG = 0x8227,
|
|
KTX_RGB = 0x1907,
|
|
KTX_RGBA = 0x1908,
|
|
KTX_BGR = 0x80E0,
|
|
KTX_BGRA = 0x80E1,
|
|
KTX_RED_INTEGER = 0x8D94,
|
|
KTX_GREEN_INTEGER = 0x8D95,
|
|
KTX_BLUE_INTEGER = 0x8D96,
|
|
KTX_ALPHA_INTEGER = 0x8D97,
|
|
KTX_RGB_INTEGER = 0x8D98,
|
|
KTX_RGBA_INTEGER = 0x8D99,
|
|
KTX_BGR_INTEGER = 0x8D9A,
|
|
KTX_BGRA_INTEGER = 0x8D9B,
|
|
KTX_LUMINANCE = 0x1909,
|
|
KTX_LUMINANCE_ALPHA = 0x190A,
|
|
KTX_RG_INTEGER = 0x8228,
|
|
KTX_RG8 = 0x822B,
|
|
KTX_ALPHA8 = 0x803C,
|
|
KTX_LUMINANCE8 = 0x8040,
|
|
KTX_LUMINANCE8_ALPHA8 = 0x8045
|
|
};
|
|
|
|
// Pixel data types
|
|
enum {
|
|
KTX_UNSIGNED_BYTE = 0x1401,
|
|
KTX_BYTE = 0x1400,
|
|
KTX_UNSIGNED_SHORT = 0x1403,
|
|
KTX_SHORT = 0x1402,
|
|
KTX_UNSIGNED_INT = 0x1405,
|
|
KTX_INT = 0x1404,
|
|
KTX_HALF_FLOAT = 0x140B,
|
|
KTX_FLOAT = 0x1406,
|
|
KTX_UNSIGNED_BYTE_3_3_2 = 0x8032,
|
|
KTX_UNSIGNED_BYTE_2_3_3_REV = 0x8362,
|
|
KTX_UNSIGNED_SHORT_5_6_5 = 0x8363,
|
|
KTX_UNSIGNED_SHORT_5_6_5_REV = 0x8364,
|
|
KTX_UNSIGNED_SHORT_4_4_4_4 = 0x8033,
|
|
KTX_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365,
|
|
KTX_UNSIGNED_SHORT_5_5_5_1 = 0x8034,
|
|
KTX_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366,
|
|
KTX_UNSIGNED_INT_8_8_8_8 = 0x8035,
|
|
KTX_UNSIGNED_INT_8_8_8_8_REV = 0x8367,
|
|
KTX_UNSIGNED_INT_10_10_10_2 = 0x8036,
|
|
KTX_UNSIGNED_INT_2_10_10_10_REV = 0x8368,
|
|
KTX_UNSIGNED_INT_24_8 = 0x84FA,
|
|
KTX_UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B,
|
|
KTX_UNSIGNED_INT_5_9_9_9_REV = 0x8C3E,
|
|
KTX_FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD
|
|
};
|
|
|
|
bool is_packed_pixel_ogl_type(uint32 ogl_type);
|
|
uint get_ogl_type_size(uint32 ogl_type);
|
|
bool get_ogl_fmt_desc(uint32 ogl_fmt, uint32 ogl_type, uint& block_dim, uint& bytes_per_block);
|
|
uint get_ogl_type_size(uint32 ogl_type);
|
|
uint32 get_ogl_base_internal_fmt(uint32 ogl_fmt);
|
|
|
|
class ktx_texture {
|
|
public:
|
|
ktx_texture() {
|
|
clear();
|
|
}
|
|
|
|
ktx_texture(const ktx_texture& other) {
|
|
*this = other;
|
|
}
|
|
|
|
ktx_texture& operator=(const ktx_texture& rhs) {
|
|
if (this == &rhs)
|
|
return *this;
|
|
|
|
clear();
|
|
|
|
m_header = rhs.m_header;
|
|
m_key_values = rhs.m_key_values;
|
|
m_image_data = rhs.m_image_data;
|
|
m_block_dim = rhs.m_block_dim;
|
|
m_bytes_per_block = rhs.m_bytes_per_block;
|
|
m_opposite_endianness = rhs.m_opposite_endianness;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void clear() {
|
|
m_header.clear();
|
|
m_key_values.clear();
|
|
m_image_data.clear();
|
|
|
|
m_block_dim = 0;
|
|
m_bytes_per_block = 0;
|
|
|
|
m_opposite_endianness = false;
|
|
}
|
|
|
|
// High level methods
|
|
bool read_from_stream(data_stream_serializer& serializer);
|
|
bool write_to_stream(data_stream_serializer& serializer, bool no_keyvalue_data = false);
|
|
|
|
bool init_2D(uint width, uint height, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
|
|
bool init_2D_array(uint width, uint height, uint num_mips, uint array_size, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
|
|
bool init_3D(uint width, uint height, uint depth, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
|
|
bool init_cubemap(uint dim, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
|
|
|
|
bool check_header() const;
|
|
bool consistency_check() const;
|
|
|
|
// General info
|
|
|
|
bool is_valid() const { return (m_header.m_pixelWidth > 0) && (m_image_data.size() > 0); }
|
|
|
|
uint get_width() const { return m_header.m_pixelWidth; }
|
|
uint get_height() const { return CRNLIB_MAX(m_header.m_pixelHeight, 1); }
|
|
uint get_depth() const { return CRNLIB_MAX(m_header.m_pixelDepth, 1); }
|
|
uint get_num_mips() const { return CRNLIB_MAX(m_header.m_numberOfMipmapLevels, 1); }
|
|
uint get_array_size() const { return CRNLIB_MAX(m_header.m_numberOfArrayElements, 1); }
|
|
uint get_num_faces() const { return m_header.m_numberOfFaces; }
|
|
|
|
uint32 get_ogl_type() const { return m_header.m_glType; }
|
|
uint32 get_ogl_fmt() const { return m_header.m_glFormat; }
|
|
uint32 get_ogl_base_fmt() const { return m_header.m_glBaseInternalFormat; }
|
|
uint32 get_ogl_internal_fmt() const { return m_header.m_glInternalFormat; }
|
|
|
|
uint get_total_images() const { return get_num_mips() * (get_depth() * get_num_faces() * get_array_size()); }
|
|
|
|
bool is_compressed() const { return m_block_dim > 1; }
|
|
bool is_uncompressed() const { return !is_compressed(); }
|
|
|
|
bool get_opposite_endianness() const { return m_opposite_endianness; }
|
|
void set_opposite_endianness(bool flag) { m_opposite_endianness = flag; }
|
|
|
|
uint32 get_block_dim() const { return m_block_dim; }
|
|
uint32 get_bytes_per_block() const { return m_bytes_per_block; }
|
|
|
|
const ktx_header& get_header() const { return m_header; }
|
|
|
|
// Key values
|
|
const ktx_key_value_vec& get_key_value_vec() const { return m_key_values; }
|
|
ktx_key_value_vec& get_key_value_vec() { return m_key_values; }
|
|
|
|
const uint8_vec* find_key(const char* pKey) const;
|
|
bool get_key_value_as_string(const char* pKey, dynamic_string& str) const;
|
|
|
|
uint add_key_value(const char* pKey, const void* pVal, uint val_size);
|
|
uint add_key_value(const char* pKey, const char* pVal) { return add_key_value(pKey, pVal, static_cast<uint>(strlen(pVal)) + 1); }
|
|
|
|
// Image data
|
|
uint get_num_images() const { return m_image_data.size(); }
|
|
|
|
const uint8_vec& get_image_data(uint image_index) const { return m_image_data[image_index]; }
|
|
uint8_vec& get_image_data(uint image_index) { return m_image_data[image_index]; }
|
|
|
|
const uint8_vec& get_image_data(uint mip_index, uint array_index, uint face_index, uint zslice_index) const { return get_image_data(get_image_index(mip_index, array_index, face_index, zslice_index)); }
|
|
uint8_vec& get_image_data(uint mip_index, uint array_index, uint face_index, uint zslice_index) { return get_image_data(get_image_index(mip_index, array_index, face_index, zslice_index)); }
|
|
|
|
const ktx_image_data_vec& get_image_data_vec() const { return m_image_data; }
|
|
ktx_image_data_vec& get_image_data_vec() { return m_image_data; }
|
|
|
|
void add_image(uint face_index, uint mip_index, const void* pImage, uint image_size) {
|
|
const uint image_index = get_image_index(mip_index, 0, face_index, 0);
|
|
if (image_index >= m_image_data.size())
|
|
m_image_data.resize(image_index + 1);
|
|
if (image_size) {
|
|
uint8_vec& v = m_image_data[image_index];
|
|
v.resize(image_size);
|
|
memcpy(&v[0], pImage, image_size);
|
|
}
|
|
}
|
|
|
|
uint get_image_index(uint mip_index, uint array_index, uint face_index, uint zslice_index) const {
|
|
CRNLIB_ASSERT((mip_index < get_num_mips()) && (array_index < get_array_size()) && (face_index < get_num_faces()) && (zslice_index < get_depth()));
|
|
return zslice_index + (face_index * get_depth()) + (array_index * (get_depth() * get_num_faces())) + (mip_index * (get_depth() * get_num_faces() * get_array_size()));
|
|
}
|
|
|
|
void get_mip_dim(uint mip_index, uint& mip_width, uint& mip_height) const {
|
|
CRNLIB_ASSERT(mip_index < get_num_mips());
|
|
mip_width = CRNLIB_MAX(get_width() >> mip_index, 1);
|
|
mip_height = CRNLIB_MAX(get_height() >> mip_index, 1);
|
|
}
|
|
|
|
void get_mip_dim(uint mip_index, uint& mip_width, uint& mip_height, uint& mip_depth) const {
|
|
CRNLIB_ASSERT(mip_index < get_num_mips());
|
|
mip_width = CRNLIB_MAX(get_width() >> mip_index, 1);
|
|
mip_height = CRNLIB_MAX(get_height() >> mip_index, 1);
|
|
mip_depth = CRNLIB_MAX(get_depth() >> mip_index, 1);
|
|
}
|
|
|
|
private:
|
|
ktx_header m_header;
|
|
|
|
ktx_key_value_vec m_key_values;
|
|
ktx_image_data_vec m_image_data;
|
|
|
|
uint32 m_block_dim;
|
|
uint32 m_bytes_per_block;
|
|
|
|
bool m_opposite_endianness;
|
|
|
|
bool compute_pixel_info();
|
|
};
|
|
|
|
} // namespace crnlib
|
|
|
|
#endif // #ifndef _KTX_TEXTURE_H_
|