Transcoder update: ASTC 4x4 RGB/RGBA, ATC RGB/RGBA, BC7 mode 5 RGBA, PVRTC1 4bpp RGBA, and uncompressed raster formats.

This commit is contained in:
richgel999
2019-09-19 12:29:44 -07:00
parent 09e71afedf
commit 3629438d43
12 changed files with 4609 additions and 784 deletions

View File

@@ -60,6 +60,7 @@ set(BASISU_SRC_LIST ${COMMON_SRC_LIST}
basisu_resample_filters.cpp
basisu_ssim.cpp
basisu_tool.cpp
basisu_astc_decomp.cpp
lodepng.cpp
transcoder/basisu_transcoder.cpp
)

View File

@@ -1,7 +1,7 @@
# basis_universal
Basis Universal GPU Texture and Texture Video Compression Codec
Basis Universal is a ["supercompressed"](http://gamma.cs.unc.edu/GST/gst.pdf) compressed GPU texture and [compressed texture video](http://gamma.cs.unc.edu/MPTC/) compression system that outputs a highly compressed intermediate file format (.basis) that can be quickly transcoded to a wide variety of GPU texture compression formats: PVRTC1 4bpp RGB, BC7 mode 6 RGB, BC1-5, ETC1, and ETC2. We will be adding ASTC RGB/RGBA, BC7 mode 5 RGBA, PVRTC1 4bpp RGBA, ATC RGB/RGBA, and various uncompressed raster pixel formats next. Basis files support non-uniform texture arrays, so cubemaps, volume textures, texture arrays, mipmap levels, video sequences, or arbitrary texture "tiles" can be stored in a single file. The compressor is able to exploit color and pattern correlations across the entire file, so multiple images with mipmaps can be stored very efficiently in a single file.
Basis Universal is a ["supercompressed"](http://gamma.cs.unc.edu/GST/gst.pdf) compressed GPU texture and [compressed texture video](http://gamma.cs.unc.edu/MPTC/) compression system that outputs a highly compressed intermediate file format (.basis) that can be quickly transcoded to a wide variety of GPU texture compression formats: ASTC 4x4 RGB/RGBA, PVRTC1 4bpp RGB/RGBA, BC7 mode 6 RGB and mode 5 RGA/RGBA, BC1-5, ETC1/2, and ATC RGB/RGBA. Basis files support non-uniform texture arrays, so cubemaps, volume textures, texture arrays, mipmap levels, video sequences, or arbitrary texture "tiles" can be stored in a single file. The compressor is able to exploit color and pattern correlations across the entire file, so multiple images with mipmaps can be stored very efficiently in a single file.
The system's bitrate depends on the quality setting and image content, but common usable bitrates are .3-1.25 bits/texel. .basis files are typically 10-25% smaller than using RDO texture compression of the internal texture data stored in the .basis file followed by LZMA. The current system is what we've been calling the "baseline" system, which is designed to reach all the GPU formats. The next major step is to extend the system to allow for much higher quality for the ASTC and BC7 texture formats.
@@ -40,11 +40,10 @@ To add automatically generated mipmaps to the .basis file, at a higher than defa
There are several mipmap options that allow you to change the filter kernel, the filter colorspace for the RGB channels (linear vs. sRGB), the smallest mipmap dimension, etc. The tool also supports generating cubemap files, 2D/cubemap texture arrays, etc.
To create a higher quality .basis file (one with better codebooks) - note this will *really* slow down compression:
To create a slightly higher quality .basis file (one with better codebooks) - note this is much slower to encode:
`basisu -level 2 x.png`
`basisu -comp_level 2 x.png`
Important: The -level option isn't the first option you should go to in order to increase quality. The -q option controls the quality vs. bitrate tradeoff. -level controls the quality vs. encoding performance tradeoff, and is primarily intended for videos.
To unpack a .basis file to multiple .png/.ktx files:

View File

@@ -147,8 +147,10 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="basisu_astc_decomp.cpp" />
<ClCompile Include="basisu_ssim.cpp" />
<ClCompile Include="transcoder\basisu_transcoder.cpp" />
<ClInclude Include="basisu_astc_decomp.h" />
<ClInclude Include="basisu_basis_file.h" />
<ClInclude Include="basisu_comp.h" />
<ClInclude Include="basisu_enc.h" />
@@ -185,6 +187,12 @@
<ClCompile Include="basisu_tool.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="transcoder\basisu_transcoder_tables_astc.inc" />
<None Include="transcoder\basisu_transcoder_tables_astc_0_255.inc" />
<None Include="transcoder\basisu_transcoder_tables_atc_55.inc" />
<None Include="transcoder\basisu_transcoder_tables_atc_56.inc" />
<None Include="transcoder\basisu_transcoder_tables_bc7_m5_alpha.inc" />
<None Include="transcoder\basisu_transcoder_tables_bc7_m5_color.inc" />
<None Include="transcoder\basisu_transcoder_tables_bc7_m6.inc" />
<None Include="transcoder\basisu_transcoder_tables_dxt1_5.inc" />
<None Include="transcoder\basisu_transcoder_tables_dxt1_6.inc" />

View File

@@ -18,6 +18,7 @@
<Filter>transcoder</Filter>
</ClCompile>
<ClCompile Include="basisu_ssim.cpp" />
<ClCompile Include="basisu_astc_decomp.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="basisu_basis_file.h" />
@@ -47,6 +48,7 @@
<Filter>transcoder</Filter>
</ClInclude>
<ClInclude Include="basisu_ssim.h" />
<ClInclude Include="basisu_astc_decomp.h" />
</ItemGroup>
<ItemGroup>
<None Include="transcoder\basisu_transcoder_tables_dxt1_6.inc">
@@ -58,6 +60,24 @@
<None Include="transcoder\basisu_transcoder_tables_bc7_m6.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_astc.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_astc_0_255.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_bc7_m5_alpha.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_bc7_m5_color.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_atc_55.inc">
<Filter>transcoder</Filter>
</None>
<None Include="transcoder\basisu_transcoder_tables_atc_56.inc">
<Filter>transcoder</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Filter Include="transcoder">

View File

@@ -15,6 +15,7 @@
#include "basisu_gpu_texture.h"
#include "basisu_enc.h"
#include "basisu_pvrtc1_4.h"
#include "basisu_astc_decomp.h"
namespace basisu
{
@@ -263,6 +264,59 @@ namespace basisu
unpack_bc4((const uint8_t *)pBlock_bits + sizeof(bc4_block), &pPixels[0].g, sizeof(color_rgba));
}
// ATC isn't officially documented, so I'm assuming these references:
// http://www.guildsoftware.com/papers/2012.Converting.DXTC.to.ATC.pdf
// https://github.com/Triang3l/S3TConv/blob/master/s3tconv_atitc.c
// The paper incorrectly says the ATC lerp factors are 1/3 and 2/3, but they are actually 3/8 and 5/8.
void unpack_atc(const void* pBlock_bits, color_rgba* pPixels)
{
const uint8_t* pBytes = static_cast<const uint8_t*>(pBlock_bits);
const uint16_t color0 = pBytes[0] | (pBytes[1] << 8U);
const uint16_t color1 = pBytes[2] | (pBytes[3] << 8U);
uint32_t sels = pBytes[4] | (pBytes[5] << 8U) | (pBytes[6] << 16U) | (pBytes[7] << 24U);
const bool mode = (color0 & 0x8000) != 0;
color_rgba c[4];
c[0].set((color0 >> 10) & 31, (color0 >> 5) & 31, color0 & 31, 255);
c[0].r = (c[0].r << 3) | (c[0].r >> 2);
c[0].g = (c[0].g << 3) | (c[0].g >> 2);
c[0].b = (c[0].b << 3) | (c[0].b >> 2);
c[3].set((color1 >> 11) & 31, (color1 >> 5) & 63, color1 & 31, 255);
c[3].r = (c[3].r << 3) | (c[3].r >> 2);
c[3].g = (c[3].g << 2) | (c[3].g >> 4);
c[3].b = (c[3].b << 3) | (c[3].b >> 2);
if (mode)
{
c[1].set(std::max(0, c[0].r - (c[3].r >> 2)), std::max(0, c[0].g - (c[3].g >> 2)), std::max(0, c[0].b - (c[3].b >> 2)), 255);
c[2] = c[0];
c[0].set(0, 0, 0, 255);
}
else
{
c[1].r = (c[0].r * 5 + c[3].r * 3) >> 3;
c[1].g = (c[0].g * 5 + c[3].g * 3) >> 3;
c[1].b = (c[0].b * 5 + c[3].b * 3) >> 3;
c[2].r = (c[0].r * 3 + c[3].r * 5) >> 3;
c[2].g = (c[0].g * 3 + c[3].g * 5) >> 3;
c[2].b = (c[0].b * 3 + c[3].b * 5) >> 3;
}
for (uint32_t i = 0; i < 16; i++)
{
const uint32_t s = sels & 3;
pPixels[i] = c[s];
sels >>= 2;
}
}
struct bc7_mode_6
{
struct
@@ -311,7 +365,7 @@ namespace basisu
};
static const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 };
// The transcoder only outputs mode 6 at the moment, so this is easy.
bool unpack_bc7_mode6(const void *pBlock_bits, color_rgba *pPixels)
{
@@ -365,6 +419,183 @@ namespace basisu
return true;
}
static inline uint32_t get_block_bits(const uint8_t* pBytes, uint32_t bit_ofs, uint32_t bits_wanted)
{
assert(bits_wanted < 32);
uint32_t v = 0;
uint32_t total_bits = 0;
while (total_bits < bits_wanted)
{
uint32_t k = pBytes[bit_ofs >> 3];
k >>= (bit_ofs & 7);
uint32_t num_bits_in_byte = 8 - (bit_ofs & 7);
v |= (k << total_bits);
total_bits += num_bits_in_byte;
bit_ofs += num_bits_in_byte;
}
return v & ((1 << bits_wanted) - 1);
}
struct bc7_mode_5
{
union
{
struct
{
uint64_t m_mode : 6;
uint64_t m_rot : 2;
uint64_t m_r0 : 7;
uint64_t m_r1 : 7;
uint64_t m_g0 : 7;
uint64_t m_g1 : 7;
uint64_t m_b0 : 7;
uint64_t m_b1 : 7;
uint64_t m_a0 : 8;
uint64_t m_a1_0 : 6;
} m_lo;
uint64_t m_lo_bits;
};
union
{
struct
{
uint64_t m_a1_1 : 2;
// bit 2
uint64_t m_c00 : 1;
uint64_t m_c10 : 2;
uint64_t m_c20 : 2;
uint64_t m_c30 : 2;
uint64_t m_c01 : 2;
uint64_t m_c11 : 2;
uint64_t m_c21 : 2;
uint64_t m_c31 : 2;
uint64_t m_c02 : 2;
uint64_t m_c12 : 2;
uint64_t m_c22 : 2;
uint64_t m_c32 : 2;
uint64_t m_c03 : 2;
uint64_t m_c13 : 2;
uint64_t m_c23 : 2;
uint64_t m_c33 : 2;
// bit 33
uint64_t m_a00 : 1;
uint64_t m_a10 : 2;
uint64_t m_a20 : 2;
uint64_t m_a30 : 2;
uint64_t m_a01 : 2;
uint64_t m_a11 : 2;
uint64_t m_a21 : 2;
uint64_t m_a31 : 2;
uint64_t m_a02 : 2;
uint64_t m_a12 : 2;
uint64_t m_a22 : 2;
uint64_t m_a32 : 2;
uint64_t m_a03 : 2;
uint64_t m_a13 : 2;
uint64_t m_a23 : 2;
uint64_t m_a33 : 2;
} m_hi;
uint64_t m_hi_bits;
};
color_rgba get_low_color() const
{
return color_rgba(cNoClamp,
(int)((m_lo.m_r0 << 1) | (m_lo.m_r0 >> 6)),
(int)((m_lo.m_g0 << 1) | (m_lo.m_g0 >> 6)),
(int)((m_lo.m_b0 << 1) | (m_lo.m_b0 >> 6)),
m_lo.m_a0);
}
color_rgba get_high_color() const
{
return color_rgba(cNoClamp,
(int)((m_lo.m_r1 << 1) | (m_lo.m_r1 >> 6)),
(int)((m_lo.m_g1 << 1) | (m_lo.m_g1 >> 6)),
(int)((m_lo.m_b1 << 1) | (m_lo.m_b1 >> 6)),
(int)m_lo.m_a1_0 | ((int)m_hi.m_a1_1 << 6));
}
void get_block_colors(color_rgba* pColors) const
{
const color_rgba low_color(get_low_color());
const color_rgba high_color(get_high_color());
for (uint32_t i = 0; i < 4; i++)
{
static const uint32_t s_bc7_weights2[4] = { 0, 21, 43, 64 };
pColors[i].set_noclamp_rgba(
(low_color.r * (64 - s_bc7_weights2[i]) + high_color.r * s_bc7_weights2[i] + 32) >> 6,
(low_color.g * (64 - s_bc7_weights2[i]) + high_color.g * s_bc7_weights2[i] + 32) >> 6,
(low_color.b * (64 - s_bc7_weights2[i]) + high_color.b * s_bc7_weights2[i] + 32) >> 6,
(low_color.a * (64 - s_bc7_weights2[i]) + high_color.a * s_bc7_weights2[i] + 32) >> 6);
}
}
uint32_t get_selector(uint32_t idx, bool alpha) const
{
const uint32_t size = (idx == 0) ? 1 : 2;
uint32_t ofs = alpha ? 97 : 66;
if (idx)
ofs += 1 + 2 * (idx - 1);
return get_block_bits(reinterpret_cast<const uint8_t*>(this), ofs, size);
}
};
bool unpack_bc7_mode5(const void* pBlock_bits, color_rgba* pPixels)
{
static_assert(sizeof(bc7_mode_5) == 16, "sizeof(bc7_mode_5) == 16");
const bc7_mode_5& block = *static_cast<const bc7_mode_5*>(pBlock_bits);
if (block.m_lo.m_mode != (1 << 5))
return false;
color_rgba block_colors[4];
block.get_block_colors(block_colors);
const uint32_t rot = block.m_lo.m_rot;
for (uint32_t i = 0; i < 16; i++)
{
const uint32_t cs = block.get_selector(i, false);
color_rgba c(block_colors[cs]);
const uint32_t as = block.get_selector(i, true);
c.a = block_colors[as].a;
if (rot > 0)
std::swap(c[3], c[rot - 1]);
pPixels[i] = c;
}
return true;
}
// Unpacks to RGBA, R, RG, or A
bool unpack_block(texture_format fmt, const void* pBlock, color_rgba* pPixels)
@@ -393,7 +624,14 @@ namespace basisu
}
case cBC7:
{
return unpack_bc7_mode6(pBlock, pPixels);
// We only support modes 5 and 6.
if (!unpack_bc7_mode5(pBlock, pPixels))
{
if (!unpack_bc7_mode6(pBlock, pPixels))
return false;
}
break;
}
// Full ETC2 color blocks (planar/T/H modes) is currently unsupported in basisu, but we do support ETC2 with alpha (using ETC1 for color)
case cETC2_RGB:
@@ -401,7 +639,6 @@ namespace basisu
case cETC1S:
{
return unpack_etc1(*static_cast<const etc_block*>(pBlock), pPixels);
break;
}
case cETC2_RGBA:
{
@@ -416,6 +653,23 @@ namespace basisu
unpack_etc2_eac(pBlock, pPixels);
break;
}
case cASTC4x4:
{
const bool astc_srgb = false;
basisu_astc::astc::decompress(reinterpret_cast<uint8_t*>(pPixels), static_cast<const uint8_t*>(pBlock), astc_srgb, 4, 4);
break;
}
case cATC_RGB:
{
unpack_atc(pBlock, pPixels);
break;
}
case cATC_RGBA_INTERPOLATED_ALPHA:
{
unpack_atc(static_cast<const uint8_t*>(pBlock) + 8, pPixels);
unpack_bc4(pBlock, &pPixels[0].a, sizeof(color_rgba));
break;
}
default:
{
assert(0);
@@ -490,9 +744,14 @@ namespace basisu
KTX_COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD,
KTX_COMPRESSED_RGB8_ETC2 = 0x9274,
KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB = 0x8E8C,
KTX_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C,
KTX_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D,
KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02,
KTX_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0,
KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0,
KTX_ATC_RGB_AMD = 0x8C92,
KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD = 0x87EE
};
struct ktx_header
@@ -634,7 +893,7 @@ namespace basisu
}
case cBC7:
{
internal_fmt = KTX_COMPRESSED_RGBA_BPTC_UNORM_ARB;
internal_fmt = KTX_COMPRESSED_RGBA_BPTC_UNORM;
base_internal_fmt = KTX_RGBA;
break;
}
@@ -649,6 +908,23 @@ namespace basisu
base_internal_fmt = KTX_RGBA;
break;
}
case cASTC4x4:
{
internal_fmt = KTX_COMPRESSED_RGBA_ASTC_4x4_KHR;
base_internal_fmt = KTX_RGBA;
break;
}
case cATC_RGB:
{
internal_fmt = KTX_ATC_RGB_AMD;
break;
}
case cATC_RGBA_INTERPOLATED_ALPHA:
{
internal_fmt = KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD;
base_internal_fmt = KTX_RGBA;
break;
}
default:
{
// TODO

View File

@@ -140,7 +140,11 @@ namespace basisu
bool unpack_bc3(const void *pBlock_bits, color_rgba *pPixels);
void unpack_bc5(const void *pBlock_bits, color_rgba *pPixels);
bool unpack_bc7_mode6(const void *pBlock_bits, color_rgba *pPixels);
bool unpack_bc7_mode5(const void* pBlock_bits, color_rgba* pPixels);
void unpack_atc(const void* pBlock_bits, color_rgba* pPixels);
// unpack_block() is only capable of unpacking texture data created by the transcoder.
// For some texture formats (like BC7, or ETC2) it's not a complete implementation.
bool unpack_block(texture_format fmt, const void *pBlock, color_rgba *pPixels);
} // namespace basisu

View File

@@ -105,6 +105,59 @@ namespace basisu
m_modulation = byteswap32(m_modulation);
m_endpoints = byteswap32(m_endpoints);
}
// opaque endpoints: 554, 555
// transparent endpoints: 3443 or 3444
inline void set_endpoint_raw(uint32_t endpoint_index, const color_rgba& c, bool opaque_endpoint)
{
assert(endpoint_index < 2);
const uint32_t m = m_endpoints & 1;
uint32_t r = c[0], g = c[1], b = c[2], a = c[3];
uint32_t packed;
if (opaque_endpoint)
{
if (!endpoint_index)
{
// 554
// 1RRRRRGGGGGBBBBM
assert((r < 32) && (g < 32) && (b < 16));
packed = 0x8000 | (r << 10) | (g << 5) | (b << 1) | m;
}
else
{
// 555
// 1RRRRRGGGGGBBBBB
assert((r < 32) && (g < 32) && (b < 32));
packed = 0x8000 | (r << 10) | (g << 5) | b;
}
}
else
{
if (!endpoint_index)
{
// 3443
// 0AAA RRRR GGGG BBBM
assert((r < 16) && (g < 16) && (b < 8) && (a < 8));
packed = (a << 12) | (r << 8) | (g << 4) | (b << 1) | m;
}
else
{
// 3444
// 0AAA RRRR GGGG BBBB
assert((r < 16) && (g < 16) && (b < 16) && (a < 8));
packed = (a << 12) | (r << 8) | (g << 4) | b;
}
}
assert(packed <= 0xFFFF);
if (endpoint_index)
m_endpoints = (m_endpoints & 0xFFFFU) | (packed << 16);
else
m_endpoints = (m_endpoints & 0xFFFF0000U) | packed;
}
};
typedef vector2D<pvrtc4_block> pvrtc4_block_vector2D;

View File

@@ -27,7 +27,7 @@
using namespace basisu;
#define BASISU_TOOL_VERSION "1.08.00"
#define BASISU_TOOL_VERSION "1.09.00"
enum tool_mode
{
@@ -60,7 +60,6 @@ static void print_usage()
" -multifile_printf: printf() format strint to use to compose multiple filenames\n"
" -multifile_first: The index of the first file to process, default is 0 (must specify -multifile_printf and -multifile_num)\n"
" -multifile_num: The total number of files to process.\n"
" -level X: Set encoding speed vs. quality tradeoff. Range is 0-5, default is 1. Higher values=slower, but higher quality.\n"
" -q X: Set quality level, 1-255, default is 128, lower=better compression/lower quality/faster, higher=less compression/higher quality/slower, default is 128. For even higher quality, use -max_endpoints/-max_selectors.\n"
" -linear: Use linear colorspace metrics (instead of the default sRGB), and by default linear (not sRGB) mipmap filtering.\n"
" -output_file filename: Output .basis/.ktx filename\n"
@@ -73,6 +72,7 @@ static void print_usage()
" For video, the .basis file will be written with the first frame being an I-Frame, and subsequent frames being P-Frames (using conditional replenishment). Playback must always occur in order from first to last image.\n"
" -framerate X: Set framerate in header to X/frames sec.\n"
" -individual: Process input images individually and output multiple .basis files (not as a texture array)\n"
" -comp_level X: Set encoding speed vs. quality tradeoff. Range is 0-5, default is 1. Higher values=MUCH slower, but slightly higher quality. Mostly intended for videos. Use -q first!\n"
" -fuzz_testing: Use with -validate: Disables CRC16 validation of file contents before transcoding\n"
"\n"
"More options:\n"
@@ -87,6 +87,7 @@ static void print_usage()
" -no_ktx: Disable KTX writing when unpacking (faster)\n"
" -etc1_only: Only unpack to ETC1, skipping the other texture formats during -unpack\n"
" -disable_hierarchical_endpoint_codebooks: Disable hierarchical endpoint codebook usage, slower but higher quality on some compression levels\n"
" -compare_ssim: Compute and display SSIM of image comparison (slow)\n"
"\n"
"Mipmap generation options:\n"
" -mipmap: Generate mipmaps for each source image\n"
@@ -223,7 +224,8 @@ public:
m_individual(false),
m_no_ktx(false),
m_etc1_only(false),
m_fuzz_testing(false)
m_fuzz_testing(false),
m_compare_ssim(false)
{
}
@@ -246,6 +248,8 @@ public:
m_mode = cUnpack;
else if (strcasecmp(pArg, "-validate") == 0)
m_mode = cValidate;
else if (strcasecmp(pArg, "-compare_ssim") == 0)
m_compare_ssim = true;
else if (strcasecmp(pArg, "-file") == 0)
{
REMAINING_ARGS_CHECK(1);
@@ -307,7 +311,7 @@ public:
m_comp_params.m_debug_images = true;
else if (strcasecmp(pArg, "-stats") == 0)
m_comp_params.m_compute_stats = true;
else if (strcasecmp(pArg, "-level") == 0)
else if (strcasecmp(pArg, "-comp_level") == 0)
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_compression_level = atoi(arg_v[arg_index + 1]);
@@ -571,6 +575,7 @@ public:
bool m_no_ktx;
bool m_etc1_only;
bool m_fuzz_testing;
bool m_compare_ssim;
};
static bool expand_multifile(command_line_params &opts)
@@ -811,7 +816,7 @@ static bool compress_mode(command_line_params &opts)
static bool unpack_and_validate_mode(command_line_params &opts, bool validate_flag)
{
basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb);
if (!opts.m_input_filenames.size())
{
error_printf("No input files to process!\n");
@@ -823,7 +828,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
for (uint32_t file_index = 0; file_index < opts.m_input_filenames.size(); file_index++)
{
const char *pInput_filename = opts.m_input_filenames[file_index].c_str();
const char* pInput_filename = opts.m_input_filenames[file_index].c_str();
std::string base_filename;
string_split_path(pInput_filename, nullptr, nullptr, &base_filename, nullptr);
@@ -864,14 +869,14 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
}
printf("File version and CRC checks succeeded\n");
basist::basisu_file_info fileinfo;
if (!dec.get_file_info(&basis_data[0], (uint32_t)basis_data.size(), fileinfo))
{
error_printf("Failed retrieving Basis file information!\n");
return false;
}
assert(fileinfo.m_total_images == fileinfo.m_image_mipmap_levels.size());
assert(fileinfo.m_total_images == dec.get_total_images(&basis_data[0], (uint32_t)basis_data.size()));
@@ -889,7 +894,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
printf(" Total slices: %u\n", (uint32_t)fileinfo.m_slice_info.size());
printf(" Total images: %i\n", fileinfo.m_total_images);
printf(" Y Flipped: %u, Has alpha slices: %u\n", fileinfo.m_y_flipped, fileinfo.m_has_alpha_slices);
printf(" userdata0: 0x%X userdata1: 0x%X\n", fileinfo.m_userdata0, fileinfo.m_userdata1);
printf(" userdata0: 0x%X userdata1: 0x%X\n", fileinfo.m_userdata0, fileinfo.m_userdata1);
printf(" Per-image mipmap levels: ");
for (uint32_t i = 0; i < fileinfo.m_total_images; i++)
printf("%u ", fileinfo.m_image_mipmap_levels[i]);
@@ -905,14 +910,14 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
return false;
}
printf("Image %u: MipLevels: %u OrigDim: %ux%u, BlockDim: %ux%u, FirstSlice: %u, HasAlpha: %u\n", i, ii.m_total_levels, ii.m_orig_width, ii.m_orig_height,
printf("Image %u: MipLevels: %u OrigDim: %ux%u, BlockDim: %ux%u, FirstSlice: %u, HasAlpha: %u\n", i, ii.m_total_levels, ii.m_orig_width, ii.m_orig_height,
ii.m_num_blocks_x, ii.m_num_blocks_y, ii.m_first_slice_index, (uint32_t)ii.m_alpha_flag);
}
printf("\nSlice info:\n");
for (uint32_t i = 0; i < fileinfo.m_slice_info.size(); i++)
{
const basist::basisu_slice_info &sliceinfo = fileinfo.m_slice_info[i];
const basist::basisu_slice_info& sliceinfo = fileinfo.m_slice_info[i];
printf("%u: OrigWidthHeight: %ux%u, BlockDim: %ux%u, TotalBlocks: %u, Compressed size: %u, Image: %u, Level: %u, UnpackedCRC16: 0x%X, alpha: %u, iframe: %i\n",
i,
sliceinfo.m_orig_width, sliceinfo.m_orig_height,
@@ -926,16 +931,21 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
}
printf("\n");
interval_timer tm;
tm.start();
if (!dec.start_transcoding(&basis_data[0], (uint32_t)basis_data.size()))
{
error_printf("start_transcoding() failed!\n");
return false;
}
printf("start_transcoding time: %3.3f ms\n", tm.get_elapsed_ms());
std::vector< gpu_image_vec > gpu_images[basist::cTFTotalTextureFormats];
int first_format = 0;
int last_format = basist::cTFTotalTextureFormats;
int last_format = basist::cTFTotalBlockTextureFormats;
if (opts.m_etc1_only)
{
@@ -946,74 +956,80 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
for (int format_iter = first_format; format_iter < last_format; format_iter++)
{
basist::transcoder_texture_format tex_fmt = static_cast<basist::transcoder_texture_format>(format_iter);
gpu_images[tex_fmt].resize(fileinfo.m_total_images);
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
gpu_images[tex_fmt][image_index].resize(fileinfo.m_image_mipmap_levels[image_index]);
}
// Now transcode the file to all supported texture formats and save mipmapped KTX files
for (int format_iter = first_format; format_iter < last_format; format_iter++)
{
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
const basist::transcoder_texture_format transcoder_tex_fmt = static_cast<basist::transcoder_texture_format>(format_iter);
if (transcoder_tex_fmt == basist::cTFPVRTC1_4_OPAQUE_ONLY)
if ((transcoder_tex_fmt == basist::cTFPVRTC1_4_RGB) || (transcoder_tex_fmt == basist::cTFPVRTC1_4_RGBA))
{
if (!is_pow2(level_info.m_width) || !is_pow2(level_info.m_height))
{
total_pvrtc_nonpow2_warnings++;
printf("Warning: Will not transcode image %u level %u res %ux%u to PVRTC1 (one or more dimension is not a power of 2)\n", image_index, level_index, level_info.m_width, level_info.m_height);
// Can't transcode this image level to PVRTC because it's not a pow2 (we're going to support transcoding non-pow2 to the next larger pow2 soon)
continue;
}
}
basisu::texture_format tex_fmt = basis_get_basisu_texture_format(transcoder_tex_fmt);
gpu_image &gi = gpu_images[transcoder_tex_fmt][image_index][level_index];
gpu_image& gi = gpu_images[transcoder_tex_fmt][image_index][level_index];
gi.init(tex_fmt, level_info.m_orig_width, level_info.m_orig_height);
// Fill the buffer with psuedo-random bytes, to help more visibly detect cases where the transcoder fails to write to part of the output.
fill_buffer_with_random_bytes(gi.get_ptr(), gi.get_size_in_bytes());
tm.start();
#if 1
if (!dec.transcode_image_level(&basis_data[0], (uint32_t)basis_data.size(), image_index, level_index, gi.get_ptr(), gi.get_total_blocks(), transcoder_tex_fmt, 0))
{
error_printf("Failed transcoding image level (%u %u %u)!\n", image_index, level_index, format_iter);
return false;
}
double total_transcode_time = tm.get_elapsed_ms();
#else
// quick and dirty row pitch parameter test, to be moved into a unit test
uint8_vec temp;
uint32_t block_pitch_to_test = level_info.m_num_blocks_x;
if (transcoder_tex_fmt != basist::cTFPVRTC1_4_OPAQUE_ONLY)
if ((transcoder_tex_fmt != basist::cTFPVRTC1_4_RGB) || (transcoder_tex_fmt != basist::cTFPVRTC1_4_RGBA))
block_pitch_to_test += 5;
temp.resize(level_info.m_num_blocks_y * block_pitch_to_test * gi.get_bytes_per_block());
fill_buffer_with_random_bytes(&temp[0], temp.size());
tm.start();
if (!dec.transcode_image_level(&basis_data[0], (uint32_t)basis_data.size(), image_index, level_index, &temp[0], (uint32_t)(temp.size() / gi.get_bytes_per_block()), transcoder_tex_fmt, 0, block_pitch_to_test))
{
error_printf("Failed transcoding image level (%u %u %u)!\n", image_index, level_index, format_iter);
return false;
}
double total_transcode_time = tm.get_elapsed_ms();
if (transcoder_tex_fmt == basist::cTFPVRTC1_4_OPAQUE_ONLY)
if ((transcoder_tex_fmt == basist::cTFPVRTC1_4_RGB) || (transcoder_tex_fmt == basist::cTFPVRTC1_4_RGBA))
{
assert(temp.size() == gi.get_size_in_bytes());
memcpy(gi.get_ptr(), &temp[0], gi.get_size_in_bytes());
@@ -1025,10 +1041,10 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
}
#endif
printf("Transcode of image %u level %u res %ux%u format %s succeeded\n", image_index, level_index, level_info.m_orig_width, level_info.m_orig_height, basist::basis_get_format_name(transcoder_tex_fmt));
printf("Transcode of image %u level %u res %ux%u format %s succeeded in %3.3f ms\n", image_index, level_index, level_info.m_orig_width, level_info.m_orig_height, basist::basis_get_format_name(transcoder_tex_fmt), total_transcode_time);
} // format_iter
} // level_index
} // image_info
@@ -1036,7 +1052,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
if (!validate_flag)
{
// Now write KTX files and unpack them to individual PNG's
for (int format_iter = first_format; format_iter < last_format; format_iter++)
{
const basist::transcoder_texture_format transcoder_tex_fmt = static_cast<basist::transcoder_texture_format>(format_iter);
@@ -1062,11 +1078,11 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
gpu_image_vec &gi = gpu_images[format_iter][image_index];
gpu_image_vec& gi = gpu_images[format_iter][image_index];
if (!gi.size())
continue;
uint32_t level;
for (level = 0; level < gi.size(); level++)
if (!gi[level].get_total_blocks())
@@ -1103,7 +1119,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
total_unpack_warnings++;
}
//u.crop(level_info.m_orig_width, level_info.m_orig_height);
std::string rgb_filename;
if (gi.size() > 1)
rgb_filename = base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index);
@@ -1136,10 +1152,189 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
} // image_index
} // format_iter
} // if (!validate_flag)
} // image_index
// Now unpack to RGBA using the transcoder itself to do the unpacking to raster images
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
{
const basist::transcoder_texture_format transcoder_tex_fmt = basist::cTFRGBA32;
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
image img(level_info.m_orig_width, level_info.m_orig_height);
fill_buffer_with_random_bytes(&img(0, 0), img.get_total_pixels() * sizeof(uint32_t));
tm.start();
if (!dec.transcode_image_level(&basis_data[0], (uint32_t)basis_data.size(), image_index, level_index, &img(0, 0).r, img.get_total_pixels(), transcoder_tex_fmt, 0, img.get_pitch(), nullptr, img.get_height()))
{
error_printf("Failed transcoding image level (%u %u %u)!\n", image_index, level_index, transcoder_tex_fmt);
return false;
}
double total_transcode_time = tm.get_elapsed_ms();
printf("Transcode of image %u level %u res %ux%u format %s succeeded in %3.3f ms\n", image_index, level_index, level_info.m_orig_width, level_info.m_orig_height, basist::basis_get_format_name(transcoder_tex_fmt), total_transcode_time);
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(rgb_filename, img, cImageSaveIgnoreAlpha))
{
error_printf("Failed writing to PNG file \"%s\"\n", rgb_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", rgb_filename.c_str());
std::string a_filename(base_filename + string_format("_unpacked_a_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(a_filename, img, cImageSaveGrayscale, 3))
{
error_printf("Failed writing to PNG file \"%s\"\n", a_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", a_filename.c_str());
} // level_index
} // image_index
// Now unpack to RGB565 using the transcoder itself to do the unpacking to raster images
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
{
const basist::transcoder_texture_format transcoder_tex_fmt = basist::cTFRGB565;
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
std::vector<uint16_t> packed_img(level_info.m_orig_width * level_info.m_orig_height);
fill_buffer_with_random_bytes(&packed_img[0], packed_img.size() * sizeof(uint16_t));
tm.start();
if (!dec.transcode_image_level(&basis_data[0], (uint32_t)basis_data.size(), image_index, level_index, &packed_img[0], (uint32_t)packed_img.size(), transcoder_tex_fmt, 0, level_info.m_orig_width, nullptr, level_info.m_orig_height))
{
error_printf("Failed transcoding image level (%u %u %u)!\n", image_index, level_index, transcoder_tex_fmt);
return false;
}
double total_transcode_time = tm.get_elapsed_ms();
image img(level_info.m_orig_width, level_info.m_orig_height);
for (uint32_t y = 0; y < level_info.m_orig_height; y++)
{
for (uint32_t x = 0; x < level_info.m_orig_width; x++)
{
const uint16_t p = packed_img[x + y * level_info.m_orig_width];
uint32_t r = p >> 11, g = (p >> 5) & 63, b = p & 31;
r = (r << 3) | (r >> 2);
g = (g << 2) | (g >> 4);
b = (b << 3) | (b >> 2);
img(x, y).set(r, g, b, 255);
}
}
printf("Transcode of image %u level %u res %ux%u format %s succeeded in %3.3f ms\n", image_index, level_index, level_info.m_orig_width, level_info.m_orig_height, basist::basis_get_format_name(transcoder_tex_fmt), total_transcode_time);
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(rgb_filename, img, cImageSaveIgnoreAlpha))
{
error_printf("Failed writing to PNG file \"%s\"\n", rgb_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", rgb_filename.c_str());
std::string a_filename(base_filename + string_format("_unpacked_a_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(a_filename, img, cImageSaveGrayscale, 3))
{
error_printf("Failed writing to PNG file \"%s\"\n", a_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", a_filename.c_str());
} // level_index
} // image_index
// Now unpack to RGBA4444 using the transcoder itself to do the unpacking to raster images
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
for (uint32_t level_index = 0; level_index < fileinfo.m_image_mipmap_levels[image_index]; level_index++)
{
const basist::transcoder_texture_format transcoder_tex_fmt = basist::cTFRGBA4444;
basist::basisu_image_level_info level_info;
if (!dec.get_image_level_info(&basis_data[0], (uint32_t)basis_data.size(), level_info, image_index, level_index))
{
error_printf("Failed retrieving image level information (%u %u)!\n", image_index, level_index);
return false;
}
std::vector<uint16_t> packed_img(level_info.m_orig_width * level_info.m_orig_height);
fill_buffer_with_random_bytes(&packed_img[0], packed_img.size() * sizeof(uint16_t));
tm.start();
if (!dec.transcode_image_level(&basis_data[0], (uint32_t)basis_data.size(), image_index, level_index, &packed_img[0], (uint32_t)packed_img.size(), transcoder_tex_fmt, 0, level_info.m_orig_width, nullptr, level_info.m_orig_height))
{
error_printf("Failed transcoding image level (%u %u %u)!\n", image_index, level_index, transcoder_tex_fmt);
return false;
}
double total_transcode_time = tm.get_elapsed_ms();
image img(level_info.m_orig_width, level_info.m_orig_height);
for (uint32_t y = 0; y < level_info.m_orig_height; y++)
{
for (uint32_t x = 0; x < level_info.m_orig_width; x++)
{
const uint16_t p = packed_img[x + y * level_info.m_orig_width];
uint32_t r = p >> 12, g = (p >> 8) & 15, b = (p >> 4) & 15, a = p & 15;
r = (r << 4) | r;
g = (g << 4) | g;
b = (b << 4) | b;
a = (a << 4) | a;
img(x, y).set(r, g, b, a);
}
}
printf("Transcode of image %u level %u res %ux%u format %s succeeded in %3.3f ms\n", image_index, level_index, level_info.m_orig_width, level_info.m_orig_height, basist::basis_get_format_name(transcoder_tex_fmt), total_transcode_time);
std::string rgb_filename(base_filename + string_format("_unpacked_rgb_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(rgb_filename, img, cImageSaveIgnoreAlpha))
{
error_printf("Failed writing to PNG file \"%s\"\n", rgb_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", rgb_filename.c_str());
std::string a_filename(base_filename + string_format("_unpacked_a_%s_%u_%04u.png", basist::basis_get_format_name(transcoder_tex_fmt), level_index, image_index));
if (!save_png(a_filename, img, cImageSaveGrayscale, 3))
{
error_printf("Failed writing to PNG file \"%s\"\n", a_filename.c_str());
return false;
}
printf("Wrote PNG file \"%s\"\n", a_filename.c_str());
} // level_index
} // image_index
} // file_index
if (total_pvrtc_nonpow2_warnings)
printf("Warning: %u images could not be transcoded to PVRTC1 because one or both dimensions were not a power of 2\n", total_pvrtc_nonpow2_warnings);
@@ -1148,7 +1343,7 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
printf("ATTENTION: %u total images had invalid GPU texture data!\n", total_unpack_warnings);
else
printf("Success\n");
return true;
}
@@ -1208,20 +1403,23 @@ static bool compare_mode(command_line_params &opts)
im.calc(a, b, 0, 0, true, true);
im.print("Y 601 " );
if (opts.m_compare_ssim)
{
vec4F s_rgb(compute_ssim(a, b, false, false));
vec4F s_rgb(compute_ssim(a, b, false, false));
printf("R SSIM: %f\n", s_rgb[0]);
printf("G SSIM: %f\n", s_rgb[1]);
printf("B SSIM: %f\n", s_rgb[2]);
printf("RGB Avg SSIM: %f\n", (s_rgb[0] + s_rgb[1] + s_rgb[2]) / 3.0f);
printf("A SSIM: %f\n", s_rgb[3]);
printf("R SSIM: %f\n", s_rgb[0]);
printf("G SSIM: %f\n", s_rgb[1]);
printf("B SSIM: %f\n", s_rgb[2]);
printf("RGB Avg SSIM: %f\n", (s_rgb[0] + s_rgb[1] + s_rgb[2]) / 3.0f);
printf("A SSIM: %f\n", s_rgb[3]);
vec4F s_y_709(compute_ssim(a, b, true, false));
printf("Y 709 SSIM: %f\n", s_y_709[0]);
vec4F s_y_709(compute_ssim(a, b, true, false));
printf("Y 709 SSIM: %f\n", s_y_709[0]);
vec4F s_y_601(compute_ssim(a, b, true, true));
printf("Y 601 SSIM: %f\n", s_y_601[0]);
vec4F s_y_601(compute_ssim(a, b, true, true));
printf("Y 601 SSIM: %f\n", s_y_601[0]);
}
image delta_img(a.get_width(), a.get_height());
@@ -1257,10 +1455,15 @@ static bool compare_mode(command_line_params &opts)
static int main_internal(int argc, const char **argv)
{
basisu_encoder_init();
printf("Basis Universal GPU Texture Compressor Reference Encoder v" BASISU_TOOL_VERSION ", Copyright (C) 2019 Binomial LLC, All rights reserved\n");
//interval_timer tm;
//tm.start();
basisu_encoder_init();
//printf("Encoder and transcoder libraries initialized in %3.3f ms\n", tm.get_elapsed_ms());
#if defined(DEBUG) || defined(_DEBUG)
printf("DEBUG build\n");
#endif

View File

@@ -16,23 +16,38 @@
#pragma once
#ifdef _MSC_VER
#pragma warning (disable : 4201)
#pragma warning (disable : 4127) // warning C4127: conditional expression is constant
#pragma warning (disable : 4530) // C++ exception handler used, but unwind semantics are not enabled.
#ifndef BASISU_NO_ITERATOR_DEBUG_LEVEL
//#define _HAS_ITERATOR_DEBUGGING 0
#if defined(_DEBUG) || defined(DEBUG)
#define _ITERATOR_DEBUG_LEVEL 1
#define _SECURE_SCL 1
#else
#define _SECURE_SCL 0
#define _ITERATOR_DEBUG_LEVEL 0
#endif
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#endif
#pragma warning (disable : 4201)
#pragma warning (disable : 4127) // warning C4127: conditional expression is constant
#pragma warning (disable : 4530) // C++ exception handler used, but unwind semantics are not enabled.
#ifndef BASISU_NO_ITERATOR_DEBUG_LEVEL
//#define _HAS_ITERATOR_DEBUGGING 0
#if defined(_DEBUG) || defined(DEBUG)
// This is madness, but we need to disable iterator debugging in debug builds or the encoder is unsable because MSVC's iterator debugging implementation is totally broken.
#ifndef _ITERATOR_DEBUG_LEVEL
#define _ITERATOR_DEBUG_LEVEL 1
#endif
#ifndef _SECURE_SCL
#define _SECURE_SCL 1
#endif
#else // defined(_DEBUG) || defined(DEBUG)
#ifndef _SECURE_SCL
#define _SECURE_SCL 0
#endif
#ifndef _ITERATOR_DEBUG_LEVEL
#define _ITERATOR_DEBUG_LEVEL 0
#endif
#endif // defined(_DEBUG) || defined(DEBUG)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#endif // BASISU_NO_ITERATOR_DEBUG_LEVEL
#endif // _MSC_VER
#include <stdlib.h>
#include <stdio.h>
@@ -289,8 +304,10 @@ namespace basisu
enum texture_format
{
cInvalidTextureFormat = -1,
cETC1,
cETC1S,
// Block-based formats
cETC1, // ETC1
cETC1S, // ETC1 (subset: diff colors only, no subblocks)
cETC2_RGB, // ETC2 color block
cETC2_RGBA, // ETC2 alpha block followed by ETC2 color block
cETC2_ALPHA, // ETC2 EAC alpha block
@@ -299,9 +316,18 @@ namespace basisu
cBC4, // DXT5A
cBC5, // 3DC/DXN (two DXT5A blocks)
cBC7,
cASTC4x4, // TODO: Add more ASTC variants
cASTC4x4,
cPVRTC1_4_RGB,
cPVRTC1_4_RGBA,
cATC_RGB,
cATC_RGBA_INTERPOLATED_ALPHA,
// Uncompressed/raw pixels
cRGBA32,
cRGB565,
cBGR565,
cRGBA4444,
cABGR4444
};
inline uint32_t get_bytes_per_block(texture_format fmt)
@@ -316,7 +342,10 @@ namespace basisu
case cBC4:
case cPVRTC1_4_RGB:
case cPVRTC1_4_RGBA:
case cATC_RGB:
return 8;
case cRGBA32:
return sizeof(uint32_t) * 16;
default:
break;
}

File diff suppressed because it is too large Load Diff

View File

@@ -24,39 +24,58 @@
namespace basist
{
// Low-level formats directly supported by the transcoder (other supported texture formats are combinations of these low-level block formats)
enum block_format
{
cETC1, // ETC1S RGB
cBC1, // DXT1 RGB
cBC4, // DXT5A (alpha block only)
cPVRTC1_4_OPAQUE_ONLY, // opaque only PVRTC1 4bpp
cBC7_M6_OPAQUE_ONLY, // RGB BC7 mode 6
cETC2_EAC_A8, // alpha block of ETC2 EAC (first 8 bytes of the 16-bit ETC2 EAC RGBA format)
cTotalBlockFormats
};
// High-level composite texture formats supported by the transcoder
// High-level composite texture formats supported by the transcoder.
// Each of these texture formats directly correspond to OpenGL/D3D/Vulkan etc. texture formats.
// Notes:
// - If you specify a texture format that supports alpha, but the .basis file doesn't have alpha, the transcoder will automatically output a
// fully opaque (255) alpha channel.
// - The PVRTC1 texture formats only support power of 2 dimension .basis files, but this may be relaxed in a future version.
// - The PVRTC1 transcoders are real-time encoders, so don't expect the highest quality. We may add a slower encoder with improved quality.
enum transcoder_texture_format
{
cTFETC1,
cTFBC1,
cTFBC4,
cTFPVRTC1_4_OPAQUE_ONLY,
cTFBC7_M6_OPAQUE_ONLY,
cTFETC2, // ETC2_EAC_A8 block followed by a ETC1 block
cTFBC3, // BC4 followed by a BC1 block
cTFBC5, // two BC4 blocks
// Compressed formats
// ETC1-2
cTFETC1, // Opaque only, returns RGB or alpha data if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
cTFETC2, // Opaque+alpha, ETC2_EAC_A8 block followed by a ETC1 block, alpha channel will be opaque for opaque .basis files
// BC1-5, BC7
cTFBC1, // Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
cTFBC3, // Opaque+alpha, BC4 followed by a BC1 block, alpha channel will be opaque for opaque .basis files
cTFBC4, // Red only, alpha slice is transcoded to output if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
cTFBC5, // XY: Two BC4 blocks, X=R and Y=Alpha, .basis file should have alpha data (if not Y will be all 255's)
cTFBC7_M6_OPAQUE_ONLY, // Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified. Highest quality of all the non-ETC1 formats.
cTFBC7_M5, // Opaque+alpha, alpha channel will be opaque for opaque .basis files
// PVRTC1 4bpp
cTFPVRTC1_4_RGB, // Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified, nearly lowest quality of any texture format.
cTFPVRTC1_4_RGBA, // Opaque+alpha, most useful for simple opacity maps. If .basis file doens't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format.
// ASTC
cTFASTC_4x4, // Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files. Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions.
// ATC
cTFATC_RGB, // Opaque, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified. ATI ATC (GL_ATC_RGB_AMD)
cTFATC_RGBA_INTERPOLATED_ALPHA, // Opaque+alpha, alpha channel will be opaque for opaque .basis files. ATI ATC (ATC_RGBA_INTERPOLATED_ALPHA_AMD)
cTFTotalBlockTextureFormats,
// Uncompressed (raw pixel) formats
cTFRGBA32 = cTFTotalBlockTextureFormats, // 32bpp RGBA image stored in raster (not block) order in memory, R is first byte, A is last byte.
cTFRGB565, // 166pp RGB image stored in raster (not block) order in memory, R at bit position 11
cTFBGR565, // 16bpp RGB image stored in raster (not block) order in memory, R at bit position 0
cTFRGBA4444, // 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0
cTFTotalTextureFormats
};
uint32_t basis_get_bytes_per_block(transcoder_texture_format fmt);
const char *basis_get_format_name(transcoder_texture_format fmt);
const char* basis_get_format_name(transcoder_texture_format fmt);
bool basis_transcoder_format_has_alpha(transcoder_texture_format fmt);
basisu::texture_format basis_get_basisu_texture_format(transcoder_texture_format fmt);
const char *basis_get_texture_type_name(basis_texture_type tex_type);
const char* basis_get_texture_type_name(basis_texture_type tex_type);
bool basis_transcoder_format_is_uncompressed(transcoder_texture_format tex_type);
bool basis_block_format_is_uncompressed(block_format tex_type);
class basisu_transcoder;
@@ -76,6 +95,7 @@ namespace basist
std::vector<uint32_t> m_prev_frame_indices[2][cMaxPrevFrameLevels]; // [alpha_flag][level_index]
};
// Low-level helper class that does the actual transcoding.
class basisu_lowlevel_transcoder
{
friend class basisu_transcoder;
@@ -90,15 +110,10 @@ namespace basist
bool decode_tables(const uint8_t *pTable_data, uint32_t table_data_size);
bool transcode_slice(void *pDst_blocks, uint32_t num_blocks_x, uint32_t num_blocks_y, const uint8_t *pImage_data, uint32_t image_data_size, block_format fmt,
uint32_t output_stride, bool wrap_addressing, bool bc1_allow_threecolor_blocks, const basis_file_header &header, const basis_slice_desc& slice_desc, uint32_t output_row_pitch_in_blocks = 0, basisu_transcoder_state *pState = nullptr);
uint32_t output_block_or_pixel_stride_in_bytes, bool wrap_addressing, bool bc1_allow_threecolor_blocks, const basis_file_header &header, const basis_slice_desc& slice_desc, uint32_t output_row_pitch_in_blocks_or_pixels = 0,
basisu_transcoder_state *pState = nullptr, bool astc_transcode_alpha = false, void* pAlpha_blocks = nullptr, uint32_t output_rows_in_pixels = 0);
private:
struct endpoint
{
color32 m_color5;
uint8_t m_inten5;
};
typedef std::vector<endpoint> endpoint_vec;
endpoint_vec m_endpoints;
@@ -213,6 +228,8 @@ namespace basist
bool m_has_alpha_slices; // true if the texture has alpha slices (even slices RGB, odd slices alpha)
};
// High-level transcoder class which accepts .basis file data and allows the caller to query information about the file and transcode image levels to various texture formats.
// If you're just starting out this is the class you care about.
class basisu_transcoder
{
basisu_transcoder(basisu_transcoder&);
@@ -264,40 +281,52 @@ namespace basist
// PVRTC1: decode non-pow2 ETC1S texture level to the next larger power of 2 (not implemented yet, but we're going to support it). Ignored if the slice's dimensions are already a power of 2.
cDecodeFlagsPVRTCDecodeToNextPow2 = 2,
// When decoding to an opaque texture format, if the basis file has alpha, decode the alpha slice instead of the color slice to the output texture format
// When decoding to an opaque texture format, if the basis file has alpha, decode the alpha slice instead of the color slice to the output texture format.
// This is primarily to allow decoding of textures with alpha to multiple ETC1 textures (one for color, another for alpha).
cDecodeFlagsTranscodeAlphaDataToOpaqueFormats = 4,
// Forbid usage of BC1 3 color blocks (we don't support BC1 punchthrough alpha yet).
cDecodeFlagsBC1ForbidThreeColorBlocks = 8
// This flag is used internally when decoding to BC3.
cDecodeFlagsBC1ForbidThreeColorBlocks = 8,
// The output buffer contains alpha endpoint/selector indices.
// Used internally when decoding formats like ASTC that require both color and alpha data to be available when transcoding to the output format.
cDecodeFlagsOutputHasAlphaIndices = 16,
};
// transcode_image_level() decodes a single mipmap level from the .basis file to any of the supported output texture formats.
// It'll first find the slice(s) to transcode, then call transcode_slice() one or two times to decode both the color and alpha texture data (or RG texture data from two slices for BC5).
// If the .basis file doesn't have alpha slices, the output alpha blocks will be set to fully opaque (all 255's).
// Currently, to decode to PVRTC1 the basis texture's dimensions in pixels must be a power of 2, due to PVRTC1 format requirements.
// output_blocks_buf_size_in_blocks should be at least the image level's total_blocks (num_blocks_x * num_blocks_y)
// If fmt isn't cETC1, basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables.
// output_row_pitch_in_blocks: Number of blocks per row. If 0, the transcoder uses the slice's num_blocks_x. Ignored for PVRTC1 (due to texture swizzling).
// output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32.
// output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling).
// output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4).
// Notes:
// - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function.
// - This method assumes the output texture buffer is readable. In some cases to handle alpha, the transcoder will write temporary data to the output texture in
// a first pass, which will be read in a second pass.
bool transcode_image_level(
const void *pData, uint32_t data_size,
uint32_t image_index, uint32_t level_index,
void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks,
void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
transcoder_texture_format fmt,
uint32_t decode_flags = cDecodeFlagsPVRTCWrapAddressing, uint32_t output_row_pitch_in_blocks = 0, basisu_transcoder_state *pState = nullptr) const;
uint32_t decode_flags = cDecodeFlagsPVRTCWrapAddressing, uint32_t output_row_pitch_in_blocks_or_pixels = 0, basisu_transcoder_state *pState = nullptr, uint32_t output_rows_in_pixels = 0) const;
// Finds the basis slice corresponding to the specified image/level/alpha params, or -1 if the slice can't be found.
int find_slice(const void *pData, uint32_t data_size, uint32_t image_index, uint32_t level_index, bool alpha_data) const;
// transcode_slice() decodes a single slice from the .basis file. It's a low-level API - most likely you want to use transcode_image_level().
// This is a low-level API, and will be needed to be called multiple times to decode some texture formats (like BC3, BC5, or ETC2).
// output_blocks_buf_size_in_blocks is just used for verification to make sure the output buffer is large enough.
// output_blocks_buf_size_in_blocks should be at least the slice's total_blocks (num_blocks_x * num_blocks_y)
// If fmt isn't cETC1, basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables.
// output_blocks_buf_size_in_blocks_or_pixels is just used for verification to make sure the output buffer is large enough.
// output_blocks_buf_size_in_blocks_or_pixels should be at least the image level's total_blocks (num_blocks_x * num_blocks_y), or the total number of output pixels if fmt==cTFRGBA32.
// output_block_stride_in_bytes: Number of bytes between each output block.
// output_row_pitch_in_blocks: Number of blocks per row. If 0, the transcoder uses the slice's num_blocks_x. Ignored for PVRTC1 (due to texture swizzling).
// output_row_pitch_in_blocks_or_pixels: Number of blocks or pixels per row. If 0, the transcoder uses the slice's num_blocks_x or orig_width (NOT num_blocks_x * 4). Ignored for PVRTC1 (due to texture swizzling).
// output_rows_in_pixels: Ignored unless fmt is cRGBA32. The total number of output rows in the output buffer. If 0, the transcoder assumes the slice's orig_height (NOT num_blocks_y * 4).
// Notes:
// - basisu_transcoder_init() must have been called first to initialize the transcoder lookup tables before calling this function.
bool transcode_slice(const void *pData, uint32_t data_size, uint32_t slice_index,
void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks,
block_format fmt, uint32_t output_block_stride_in_bytes, uint32_t decode_flags = cDecodeFlagsPVRTCWrapAddressing, uint32_t output_row_pitch_in_blocks = 0, basisu_transcoder_state * pState = nullptr) const;
void *pOutput_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
block_format fmt, uint32_t output_block_stride_in_bytes, uint32_t decode_flags = cDecodeFlagsPVRTCWrapAddressing, uint32_t output_row_pitch_in_blocks_or_pixels = 0, basisu_transcoder_state * pState = nullptr, void* pAlpha_blocks = nullptr, uint32_t output_rows_in_pixels = 0) const;
private:
mutable basisu_lowlevel_transcoder m_lowlevel_decoder;
@@ -318,4 +347,5 @@ namespace basist
};
uint32_t get_debug_flags();
void set_debug_flags(uint32_t f);
} // namespace basisu

View File

@@ -40,6 +40,40 @@ namespace basisu
namespace basist
{
// Low-level formats directly supported by the transcoder (other supported texture formats are combinations of these low-level block formats).
// You probably don't care about these enum's unless you are going pretty low-level and calling the transcoder to decode individual slices.
enum block_format
{
cETC1, // ETC1S RGB
cBC1, // DXT1 RGB
cBC4, // DXT5A (alpha block only)
cPVRTC1_4_RGB, // opaque-only PVRTC1 4bpp
cPVRTC1_4_RGBA, // PVRTC1 4bpp RGBA
cBC7_M6_OPAQUE_ONLY, // RGB BC7 mode 6
cBC7_M5_COLOR, // RGB BC7 mode 5 color (writes an opaque mode 5 block)
cBC7_M5_ALPHA, // alpha portion of BC7 mode 5 (cBC7_M5_COLOR output data must have been written to the output buffer first to set the mode/rot fields etc.)
cETC2_EAC_A8, // alpha block of ETC2 EAC (first 8 bytes of the 16-bit ETC2 EAC RGBA format)
cASTC_4x4, // ASTC 4x4 (either color-only or color+alpha). Note that the transcoder always currently assumes sRGB is not enabled when outputting ASTC
// data. If you use a sRGB ASTC format you'll get ~1 LSB of additional error, because of the different way ASTC decoders scale 8-bit endpoints to 16-bits during unpacking.
cATC_RGB,
cATC_RGBA_INTERPOLATED_ALPHA,
cIndices, // Used internally: Write 16-bit endpoint and selector indices directly to output (output block must be at least 32-bits)
cRGB32, // Writes RGB components to 32bpp output pixels
cRGBA32, // Writes RGB255 components to 32bpp output pixels
cA32, // Writes alpha component to 32bpp output pixels
cRGB565,
cBGR565,
cRGBA4444_COLOR,
cRGBA4444_ALPHA,
cRGBA4444_COLOR_OPAQUE,
cTotalBlockFormats
};
const int COLOR5_PAL0_PREV_HI = 9, COLOR5_PAL0_DELTA_LO = -9, COLOR5_PAL0_DELTA_HI = 31;
const int COLOR5_PAL1_PREV_HI = 21, COLOR5_PAL1_DELTA_LO = -21, COLOR5_PAL1_DELTA_HI = 21;
const int COLOR5_PAL2_PREV_HI = 31, COLOR5_PAL2_DELTA_LO = -31, COLOR5_PAL2_DELTA_HI = 9;
@@ -619,12 +653,19 @@ namespace basist
bool operator== (const color32&rhs) const { return m == rhs.m; }
};
struct endpoint
{
color32 m_color5;
uint8_t m_inten5;
};
struct selector
{
union
{
uint8_t m_bytes[4];
};
// Plain selectors (2-bits per value)
uint8_t m_selectors[4];
// ETC1 selectors
uint8_t m_bytes[4];
uint8_t m_lo_selector, m_hi_selector;
uint8_t m_num_unique_selectors;
@@ -656,25 +697,11 @@ namespace basist
}
}
inline uint32_t get_raw_selector(uint32_t x, uint32_t y) const
{
assert((x | y) < 4);
const uint32_t bit_index = x * 4 + y;
const uint32_t byte_bit_ofs = bit_index & 7;
const uint8_t *p = &m_bytes[3 - (bit_index >> 3)];
const uint32_t lsb = (p[0] >> byte_bit_ofs) & 1;
const uint32_t msb = (p[-2] >> byte_bit_ofs) & 1;
const uint32_t val = lsb | (msb << 1);
return val;
}
// Returned selector value ranges from 0-3 and is a direct index into g_etc1_inten_tables.
inline uint32_t get_selector(uint32_t x, uint32_t y) const
{
static const uint8_t s_etc1_to_selector_index[4] = { 2, 3, 1, 0 };
return s_etc1_to_selector_index[get_raw_selector(x, y)];
assert((x < 4) && (y < 4));
return (m_selectors[y] >> (x * 2)) & 3;
}
void set_selector(uint32_t x, uint32_t y, uint32_t val)
@@ -682,11 +709,15 @@ namespace basist
static const uint8_t s_selector_index_to_etc1[4] = { 3, 2, 0, 1 };
assert((x | y | val) < 4);
const uint32_t bit_index = x * 4 + y;
uint8_t *p = &m_bytes[3 - (bit_index >> 3)];
m_selectors[y] &= ~(3 << (x * 2));
m_selectors[y] |= (val << (x * 2));
const uint32_t byte_bit_ofs = bit_index & 7;
const uint32_t etc1_bit_index = x * 4 + y;
uint8_t *p = &m_bytes[3 - (etc1_bit_index >> 3)];
const uint32_t byte_bit_ofs = etc1_bit_index & 7;
const uint32_t mask = 1 << byte_bit_ofs;
const uint32_t etc1_val = s_selector_index_to_etc1[val];