Files
unity/crnlib/crn_pixel_format.h
T
Alexander Suvorov bec4114bea Add compression support for ETC2A textures
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
2017-08-04 16:56:10 +02:00

334 lines
7.3 KiB
C++

// File: crn_pixel_format.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once
#include "crn_dxt.h"
#include "../inc/crnlib.h"
#include "../inc/dds_defs.h"
namespace crnlib {
namespace pixel_format_helpers {
uint get_num_formats();
pixel_format get_pixel_format_by_index(uint index);
const char* get_pixel_format_string(pixel_format fmt);
const char* get_crn_format_string(crn_format fmt);
inline bool is_grayscale(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_L8:
case PIXEL_FMT_A8L8:
return true;
default:
break;
}
return false;
}
inline bool is_dxt1(pixel_format fmt) {
return (fmt == PIXEL_FMT_DXT1) || (fmt == PIXEL_FMT_DXT1A);
}
// has_alpha() should probably be called "has_opacity()" - it indicates if the format encodes opacity
// because some swizzled DXT5 formats do not encode opacity.
inline bool has_alpha(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1A:
case PIXEL_FMT_DXT2:
case PIXEL_FMT_DXT3:
case PIXEL_FMT_DXT4:
case PIXEL_FMT_DXT5:
case PIXEL_FMT_DXT5A:
case PIXEL_FMT_A8R8G8B8:
case PIXEL_FMT_A8:
case PIXEL_FMT_A8L8:
case PIXEL_FMT_DXT5_AGBR:
case PIXEL_FMT_ETC2A:
return true;
default:
break;
}
return false;
}
inline bool is_alpha_only(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_A8:
case PIXEL_FMT_DXT5A:
return true;
default:
break;
}
return false;
}
inline bool is_normal_map(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_3DC:
case PIXEL_FMT_DXN:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_AGBR:
return true;
default:
break;
}
return false;
}
inline int is_dxt(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1:
case PIXEL_FMT_DXT1A:
case PIXEL_FMT_DXT2:
case PIXEL_FMT_DXT3:
case PIXEL_FMT_DXT4:
case PIXEL_FMT_DXT5:
case PIXEL_FMT_3DC:
case PIXEL_FMT_DXT5A:
case PIXEL_FMT_DXN:
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
case PIXEL_FMT_ETC1:
case PIXEL_FMT_ETC2:
case PIXEL_FMT_ETC2A:
return true;
default:
break;
}
return false;
}
inline int get_fundamental_format(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1A:
return PIXEL_FMT_DXT1;
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
return PIXEL_FMT_DXT5;
default:
break;
}
return fmt;
}
inline dxt_format get_dxt_format(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1:
return cDXT1;
case PIXEL_FMT_DXT1A:
return cDXT1A;
case PIXEL_FMT_DXT2:
return cDXT3;
case PIXEL_FMT_DXT3:
return cDXT3;
case PIXEL_FMT_DXT4:
return cDXT5;
case PIXEL_FMT_DXT5:
return cDXT5;
case PIXEL_FMT_3DC:
return cDXN_YX;
case PIXEL_FMT_DXT5A:
return cDXT5A;
case PIXEL_FMT_DXN:
return cDXN_XY;
case PIXEL_FMT_DXT5_CCxY:
return cDXT5;
case PIXEL_FMT_DXT5_xGxR:
return cDXT5;
case PIXEL_FMT_DXT5_xGBR:
return cDXT5;
case PIXEL_FMT_DXT5_AGBR:
return cDXT5;
case PIXEL_FMT_ETC1:
return cETC1;
case PIXEL_FMT_ETC2:
return cETC2;
case PIXEL_FMT_ETC2A:
return cETC2A;
default:
break;
}
return cDXTInvalid;
}
inline pixel_format from_dxt_format(dxt_format dxt_fmt) {
switch (dxt_fmt) {
case cDXT1:
return PIXEL_FMT_DXT1;
case cDXT1A:
return PIXEL_FMT_DXT1A;
case cDXT3:
return PIXEL_FMT_DXT3;
case cDXT5:
return PIXEL_FMT_DXT5;
case cDXN_XY:
return PIXEL_FMT_DXN;
case cDXN_YX:
return PIXEL_FMT_3DC;
case cDXT5A:
return PIXEL_FMT_DXT5A;
case cETC1:
return PIXEL_FMT_ETC1;
case cETC2:
return PIXEL_FMT_ETC2;
case cETC2A:
return PIXEL_FMT_ETC2A;
default:
break;
}
CRNLIB_ASSERT(false);
return PIXEL_FMT_INVALID;
}
inline bool is_pixel_format_non_srgb(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_3DC:
case PIXEL_FMT_DXN:
case PIXEL_FMT_DXT5A:
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
return true;
default:
break;
}
return false;
}
inline bool is_crn_format_non_srgb(crn_format fmt) {
switch (fmt) {
case cCRNFmtDXN_XY:
case cCRNFmtDXN_YX:
case cCRNFmtDXT5A:
case cCRNFmtDXT5_CCxY:
case cCRNFmtDXT5_xGxR:
case cCRNFmtDXT5_xGBR:
case cCRNFmtDXT5_AGBR:
return true;
default:
break;
}
return false;
}
inline uint get_bpp(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1:
return 4;
case PIXEL_FMT_DXT1A:
return 4;
case PIXEL_FMT_ETC1:
return 4;
case PIXEL_FMT_ETC2:
return 4;
case PIXEL_FMT_ETC2A:
return 8;
case PIXEL_FMT_DXT2:
return 8;
case PIXEL_FMT_DXT3:
return 8;
case PIXEL_FMT_DXT4:
return 8;
case PIXEL_FMT_DXT5:
return 8;
case PIXEL_FMT_3DC:
return 8;
case PIXEL_FMT_DXT5A:
return 4;
case PIXEL_FMT_R8G8B8:
return 24;
case PIXEL_FMT_A8R8G8B8:
return 32;
case PIXEL_FMT_A8:
return 8;
case PIXEL_FMT_L8:
return 8;
case PIXEL_FMT_A8L8:
return 16;
case PIXEL_FMT_DXN:
return 8;
case PIXEL_FMT_DXT5_CCxY:
return 8;
case PIXEL_FMT_DXT5_xGxR:
return 8;
case PIXEL_FMT_DXT5_xGBR:
return 8;
case PIXEL_FMT_DXT5_AGBR:
return 8;
default:
break;
}
CRNLIB_ASSERT(false);
return 0;
};
inline uint get_dxt_bytes_per_block(pixel_format fmt) {
switch (fmt) {
case PIXEL_FMT_DXT1:
return 8;
case PIXEL_FMT_DXT1A:
return 8;
case PIXEL_FMT_DXT5A:
return 8;
case PIXEL_FMT_ETC1:
return 8;
case PIXEL_FMT_ETC2:
return 8;
case PIXEL_FMT_ETC2A:
return 16;
case PIXEL_FMT_DXT2:
return 16;
case PIXEL_FMT_DXT3:
return 16;
case PIXEL_FMT_DXT4:
return 16;
case PIXEL_FMT_DXT5:
return 16;
case PIXEL_FMT_3DC:
return 16;
case PIXEL_FMT_DXN:
return 16;
case PIXEL_FMT_DXT5_CCxY:
return 16;
case PIXEL_FMT_DXT5_xGxR:
return 16;
case PIXEL_FMT_DXT5_xGBR:
return 16;
case PIXEL_FMT_DXT5_AGBR:
return 16;
default:
break;
}
CRNLIB_ASSERT(false);
return 0;
}
enum component_flags {
cCompFlagRValid = 1,
cCompFlagGValid = 2,
cCompFlagBValid = 4,
cCompFlagAValid = 8,
cCompFlagGrayscale = 16,
cCompFlagNormalMap = 32,
cCompFlagLumaChroma = 64,
cDefaultCompFlags = cCompFlagRValid | cCompFlagGValid | cCompFlagBValid | cCompFlagAValid
};
component_flags get_component_flags(pixel_format fmt);
crn_format convert_pixel_format_to_best_crn_format(pixel_format crn_fmt);
pixel_format convert_crn_format_to_pixel_format(crn_format fmt);
} // namespace pixel_format_helpers
} // namespace crnlib