Important: Changing the Basis file version, so any existing files will need to be recompressed!

Adding new fields to the basis header: texture type and framerate. Texture type may be 2D, 2D array, video, volume, or cubemap array. The compressor makes sure that anything other than pure 2D follows certain constraints (cubemap arrays must have a multiple of 6 input images, videos/texture array images all must have the same resolution/# of mipmaps, etc.)
When unpacking cubemaps, the -unpack command now writes cubemap .KTX files which various tools like PVRTexTool support.
This commit is contained in:
Rich Geldreich
2019-05-02 16:09:11 -07:00
parent c540728ed1
commit 33996b1a5f
16 changed files with 421 additions and 95 deletions

View File

@@ -122,7 +122,7 @@ namespace basisu
uint32_t m_num_macroblocks_x;
uint32_t m_num_macroblocks_y;
uint32_t m_source_file_index;
uint32_t m_source_file_index; // also the basis image index
uint32_t m_mip_index;
bool m_alpha;
};

View File

@@ -16,11 +16,11 @@
#include "transcoder/basisu_transcoder.h"
// The output file version. Keep in sync with BASISD_SUPPORTED_BASIS_VERSION.
#define BASIS_FILE_VERSION (0x11)
#define BASIS_FILE_VERSION (0x12)
namespace basisu
{
void basisu_file::create_header(const basisu_backend_output &encoder_output, uint32_t userdata0, uint32_t userdata1, bool y_flipped)
void basisu_file::create_header(const basisu_backend_output &encoder_output, basist::basis_texture_type tex_type, uint32_t userdata0, uint32_t userdata1, bool y_flipped, uint32_t us_per_frame)
{
m_header.m_header_size = sizeof(basist::basis_file_header);
@@ -34,13 +34,13 @@ namespace basisu
m_header.m_format = basist::cETC1;
m_header.m_flags = 0;
if (encoder_output.m_etc1s)
m_header.m_flags = m_header.m_flags | basist::cBASISHeaderFlagETC1S;
if (y_flipped)
m_header.m_flags = m_header.m_flags | basist::cBASISHeaderFlagYFlipped;
for (uint32_t i = 0; i < encoder_output.m_slice_desc.size(); i++)
{
if (encoder_output.m_slice_desc[i].m_alpha)
@@ -50,6 +50,9 @@ namespace basisu
}
}
m_header.m_tex_type = static_cast<uint8_t>(tex_type);
m_header.m_us_per_frame = clamp<uint32_t>(us_per_frame, 0, basist::cBASISMaxUSPerFrame);
m_header.m_userdata0 = userdata0;
m_header.m_userdata1 = userdata1;
@@ -151,7 +154,7 @@ namespace basisu
pHeader->m_ver = BASIS_FILE_VERSION;// basist::basis_file_header::cBASISFirstVersion;
}
bool basisu_file::init(const basisu_backend_output &encoder_output, uint32_t userdata0, uint32_t userdata1, bool y_flipped)
bool basisu_file::init(const basisu_backend_output &encoder_output, basist::basis_texture_type tex_type, uint32_t userdata0, uint32_t userdata1, bool y_flipped, uint32_t us_per_frame)
{
clear();
@@ -184,7 +187,7 @@ namespace basisu
m_total_file_size = (uint32_t)total_file_size;
create_header(encoder_output, userdata0, userdata1, y_flipped);
create_header(encoder_output, tex_type, userdata0, userdata1, y_flipped, us_per_frame);
if (!create_image_descs(encoder_output))
return false;

View File

@@ -43,7 +43,7 @@ namespace basisu
m_total_file_size = 0;
}
bool init(const basisu_backend_output& encoder_output, uint32_t userdata0 = 0, uint32_t userdata1 = 0, bool y_flipped = false);
bool init(const basisu_backend_output& encoder_output, basist::basis_texture_type tex_type, uint32_t userdata0, uint32_t userdata1, bool y_flipped, uint32_t us_per_frame);
const uint8_vec &get_compressed_data() const { return m_comp_data; }
@@ -61,7 +61,7 @@ namespace basisu
uint32_t m_first_image_file_ofs;
uint32_t m_total_file_size;
void create_header(const basisu_backend_output& encoder_output, uint32_t userdata0, uint32_t userdata1, bool y_flipped);
void create_header(const basisu_backend_output& encoder_output, basist::basis_texture_type tex_type, uint32_t userdata0, uint32_t userdata1, bool y_flipped, uint32_t us_per_frame);
bool create_image_descs(const basisu_backend_output& encoder_output);
void create_comp_data(const basisu_backend_output& encoder_output);
void fixup_crcs();

View File

@@ -88,6 +88,10 @@ namespace basisu
debug_printf("m_max_endpoint_clusters: %u\n", m_params.m_max_endpoint_clusters);
debug_printf("m_max_selector_clusters: %u\n", m_params.m_max_selector_clusters);
debug_printf("m_quality_level: %i\n", m_params.m_quality_level);
debug_printf("m_tex_type: %u\n", m_params.m_tex_type);
debug_printf("m_userdata0: 0x%X, m_userdata1: 0x%X\n", m_params.m_userdata0, m_params.m_userdata1);
debug_printf("m_us_per_frame: %i (%f fps)\n", m_params.m_us_per_frame, m_params.m_us_per_frame ? 1.0f / (m_params.m_us_per_frame / 1000000.0f) : 0);
#undef PRINT_BOOL_VALUE
#undef PRINT_INT_VALUE
@@ -111,6 +115,9 @@ namespace basisu
if (!read_source_images())
return cECFailedReadingSourceImages;
if (!validate_texture_type_constraints())
return cECFailedValidating;
if (!process_frontend())
return cECFailedFrontEnd;
@@ -214,7 +221,7 @@ namespace basisu
m_stats.resize(0);
m_slice_descs.resize(0);
m_source_images.resize(0);
m_slice_images.resize(0);
m_total_blocks = 0;
uint32_t total_macroblocks = 0;
@@ -389,20 +396,20 @@ namespace basisu
{
const bool is_alpha_slice = m_any_source_image_has_alpha && ((slice_index & 1) != 0);
image &source_image = slices[slice_index];
const uint32_t orig_width = source_image.get_width();
const uint32_t orig_height = source_image.get_height();
image &slice_image = slices[slice_index];
const uint32_t orig_width = slice_image.get_width();
const uint32_t orig_height = slice_image.get_height();
// Enlarge the source image to 4x4 block boundaries, duplicating edge pixels if necessary to avoid introducing extra colors into blocks.
source_image.crop_dup_borders(source_image.get_block_width(4) * 4, source_image.get_block_height(4) * 4);
slice_image.crop_dup_borders(slice_image.get_block_width(4) * 4, slice_image.get_block_height(4) * 4);
if (m_params.m_debug_images)
{
save_png(string_format("basis_debug_source_image_%u_%u.png", source_file_index, slice_index).c_str(), source_image);
save_png(string_format("basis_debug_source_image_%u_%u.png", source_file_index, slice_index).c_str(), slice_image);
}
enlarge_vector(m_stats, 1);
enlarge_vector(m_source_images, 1);
enlarge_vector(m_slice_images, 1);
enlarge_vector(m_slice_descs, 1);
const uint32_t dest_image_index = (uint32_t)m_stats.size() - 1;
@@ -411,9 +418,9 @@ namespace basisu
m_stats[dest_image_index].m_width = orig_width;
m_stats[dest_image_index].m_height = orig_height;
m_source_images[dest_image_index] = source_image;
m_slice_images[dest_image_index] = slice_image;
debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n", m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(), orig_width, orig_height, source_image.get_width(), source_image.get_height());
debug_printf("****** Slice %u: mip %u, alpha_slice: %u, filename: \"%s\", original: %ux%u actual: %ux%u\n", m_slice_descs.size() - 1, mip_indices[slice_index], is_alpha_slice, source_filename.c_str(), orig_width, orig_height, slice_image.get_width(), slice_image.get_height());
basisu_backend_slice_desc &slice_desc = m_slice_descs[dest_image_index];
@@ -422,11 +429,11 @@ namespace basisu
slice_desc.m_orig_width = orig_width;
slice_desc.m_orig_height = orig_height;
slice_desc.m_width = source_image.get_width();
slice_desc.m_height = source_image.get_height();
slice_desc.m_width = slice_image.get_width();
slice_desc.m_height = slice_image.get_height();
slice_desc.m_num_blocks_x = source_image.get_block_width(4);
slice_desc.m_num_blocks_y = source_image.get_block_height(4);
slice_desc.m_num_blocks_x = slice_image.get_block_width(4);
slice_desc.m_num_blocks_y = slice_image.get_block_height(4);
slice_desc.m_num_macroblocks_x = (slice_desc.m_num_blocks_x + 1) >> 1;
slice_desc.m_num_macroblocks_y = (slice_desc.m_num_blocks_y + 1) >> 1;
@@ -519,20 +526,85 @@ namespace basisu
return true;
}
// Do some basic validation for 2D arrays, cubemaps, video, and volumes.
bool basis_compressor::validate_texture_type_constraints()
{
debug_printf("basis_compressor::validate_texture_type_constraints\n");
// In 2D mode anything goes (each image may have a different resolution and # of mipmap levels).
if (m_params.m_tex_type == basist::cBASISTexType2D)
return true;
uint32_t total_basis_images = 0;
for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
total_basis_images = maximum<uint32_t>(total_basis_images, slice_desc.m_source_file_index + 1);
}
if (m_params.m_tex_type == basist::cBASISTexTypeCubemapArray)
{
// For cubemaps, validate that the total # of Basis images is a multiple of 6.
if ((total_basis_images % 6) != 0)
{
error_printf("basis_compressor::validate_texture_type_constraints: For cubemaps the total number of input images is not a multiple of 6!\n");
return false;
}
}
// Now validate that all the mip0's have the same dimensions, and that each image has the same # of mipmap levels.
uint_vec image_mipmap_levels(total_basis_images);
int width = -1, height = -1;
for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
image_mipmap_levels[slice_desc.m_source_file_index] = maximum(image_mipmap_levels[slice_desc.m_source_file_index], slice_desc.m_mip_index + 1);
if (slice_desc.m_mip_index != 0)
continue;
if (width < 0)
{
width = slice_desc.m_orig_width;
height = slice_desc.m_orig_height;
}
else if ((width != (int)slice_desc.m_orig_width) || (height != (int)slice_desc.m_orig_height))
{
error_printf("basis_compressor::validate_texture_type_constraints: The source image resolutions are not all equal!\n");
return false;
}
}
for (size_t i = 1; i < image_mipmap_levels.size(); i++)
{
if (image_mipmap_levels[0] != image_mipmap_levels[i])
{
error_printf("basis_compressor::validate_texture_type_constraints: Each image must have the same number of mipmap levels!\n");
return false;
}
}
return true;
}
bool basis_compressor::process_frontend()
{
debug_printf("basis_compressor::process_frontend\n");
m_source_blocks.resize(m_total_blocks);
for (uint32_t slice_index = 0; slice_index < m_source_images.size(); slice_index++)
for (uint32_t slice_index = 0; slice_index < m_slice_images.size(); slice_index++)
{
const basisu_backend_slice_desc &slice_desc = m_slice_descs[slice_index];
const uint32_t num_blocks_x = slice_desc.m_num_blocks_x;
const uint32_t num_blocks_y = slice_desc.m_num_blocks_y;
const image &source_image = m_source_images[slice_index];
const image &source_image = m_slice_images[slice_index];
for (uint32_t block_y = 0; block_y < num_blocks_y; block_y++)
for (uint32_t block_x = 0; block_x < num_blocks_x; block_x++)
@@ -800,7 +872,7 @@ namespace basisu
const basisu_backend_output &encoded_output = m_backend.get_output();
if (!m_basis_file.init(encoded_output, 0, 0, m_params.m_y_flip))
if (!m_basis_file.init(encoded_output, m_params.m_tex_type, m_params.m_userdata0, m_params.m_userdata1, m_params.m_y_flip, m_params.m_us_per_frame))
{
error_printf("basis_compressor::write_output_files_and_compute_stats: basisu_backend:init() failed!\n");
return false;
@@ -971,37 +1043,37 @@ namespace basisu
image_metrics em;
// best possible ETC1S stats
em.calc(m_source_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0);
em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0);
em.print("Unquantized ETC1S Luma: ");
s.m_best_luma_psnr = static_cast<float>(em.m_psnr);
s.m_best_luma_ssim = static_cast<float>(em.m_ssim);
em.calc(m_source_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3);
em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3);
em.print("Unquantized ETC1S RGB Avg: ");
s.m_best_rgb_avg_psnr = static_cast<float>(em.m_psnr);
// .basis ETC1S stats
em.calc(m_source_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0);
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0);
em.print(".basis ETC1S Luma: ");
s.m_basis_etc1_luma_psnr = static_cast<float>(em.m_psnr);
s.m_basis_etc1_luma_ssim = static_cast<float>(em.m_ssim);
em.calc(m_source_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3);
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3);
em.print(".basis ETC1S RGB Avg: ");
//debug_printf(".basis ETC1 Luma SSIM per bit/texel*1000: %3.3f\n", 1000.0f * s.m_basis_etc1_luma_ssim / ((m_backend.get_output().get_output_size_estimate() * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height)));
// .basis BC1 stats
em.calc(m_source_images[slice_index], m_decoded_output_textures_unpacked_bc1[slice_index], 0, 0);
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc1[slice_index], 0, 0);
em.print(".basis BC1 Luma: ");
s.m_basis_bc1_luma_psnr = static_cast<float>(em.m_psnr);
s.m_basis_bc1_luma_ssim = static_cast<float>(em.m_ssim);
em.calc(m_source_images[slice_index], m_decoded_output_textures_unpacked_bc1[slice_index], 0, 3);
em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc1[slice_index], 0, 3);
em.print(".basis BC1 RGB Avg: ");
s.m_basis_bc1_rgb_avg_psnr = static_cast<float>(em.m_psnr);
@@ -1022,7 +1094,7 @@ namespace basisu
{
gpu_image best_etc1s_gpu_image(m_best_etc1s_images[slice_index]);
best_etc1s_gpu_image.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
write_compressed_texture_file(out_basename + "_best_etc1s.ktx", best_etc1s_gpu_image);
write_compressed_texture_file((out_basename + "_best_etc1s.ktx").c_str(), best_etc1s_gpu_image);
image best_etc1s_unpacked;
best_etc1s_gpu_image.unpack(best_etc1s_unpacked);
@@ -1033,7 +1105,7 @@ namespace basisu
{
gpu_image decoded_etc1s(m_decoded_output_textures[slice_index]);
decoded_etc1s.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
write_compressed_texture_file(out_basename + "_decoded_etc1s.ktx", decoded_etc1s);
write_compressed_texture_file((out_basename + "_decoded_etc1s.ktx").c_str(), decoded_etc1s);
image temp(m_decoded_output_textures_unpacked[slice_index]);
temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);
@@ -1044,7 +1116,7 @@ namespace basisu
{
gpu_image decoded_bc1(m_decoded_output_textures_bc1[slice_index]);
decoded_bc1.override_dimensions(slice_desc.m_orig_width, slice_desc.m_orig_height);
write_compressed_texture_file(out_basename + "_decoded_bc1.ktx", decoded_bc1);
write_compressed_texture_file((out_basename + "_decoded_bc1.ktx").c_str(), decoded_bc1);
image temp(m_decoded_output_textures_unpacked_bc1[slice_index]);
temp.crop(slice_desc.m_orig_width, slice_desc.m_orig_height);

View File

@@ -231,6 +231,11 @@ namespace basisu
m_max_endpoint_clusters = 0;
m_max_selector_clusters = 0;
m_quality_level = -1;
m_tex_type = basist::cBASISTexType2D;
m_userdata0 = 0;
m_userdata1 = 0;
m_us_per_frame = 0;
}
// Pointer to the global selector codebook, or nullptr to not use a global selector codebook
@@ -314,6 +319,12 @@ namespace basisu
uint32_t m_max_endpoint_clusters;
uint32_t m_max_selector_clusters;
int m_quality_level;
// m_tex_type, m_userdata0, m_userdata1, m_framerate - These fields go directly into the Basis file header.
basist::basis_texture_type m_tex_type;
uint32_t m_userdata0;
uint32_t m_userdata1;
uint32_t m_us_per_frame;
};
class basis_compressor
@@ -329,6 +340,7 @@ namespace basisu
{
cECSuccess = 0,
cECFailedReadingSourceImages,
cECFailedValidating,
cECFailedFrontEnd,
cECFailedFontendExtract,
cECFailedBackend,
@@ -349,7 +361,7 @@ namespace basisu
private:
basis_compressor_params m_params;
std::vector<image> m_source_images;
std::vector<image> m_slice_images;
std::vector<image_stats> m_stats;
@@ -390,6 +402,7 @@ namespace basisu
bool create_basis_file_and_transcode();
bool write_output_files_and_compute_stats();
bool generate_mipmaps(const image &img, std::vector<image> &mips, bool has_alpha);
bool validate_texture_type_constraints();
};
} // namespace basisu

View File

@@ -521,17 +521,82 @@ namespace basisu
void clear() { clear_obj(*this); }
};
bool create_ktx_texture_file(uint8_vec &ktx_data, const gpu_image_vec& g)
// Input is a texture array of mipmapped gpu_image's: gpu_images[array_index][level_index]
bool create_ktx_texture_file(uint8_vec &ktx_data, const std::vector<gpu_image_vec>& gpu_images, bool cubemap_flag)
{
if (!g.size())
if (!gpu_images.size())
{
assert(0);
return false;
}
uint32_t width = 0, height = 0, total_levels = 0;
basisu::texture_format fmt = cInvalidTextureFormat;
if (cubemap_flag)
{
if ((gpu_images.size() % 6) != 0)
{
assert(0);
return false;
}
}
for (uint32_t array_index = 0; array_index < gpu_images.size(); array_index++)
{
const gpu_image_vec &levels = gpu_images[array_index];
if (!levels.size())
{
// Empty mip chain
assert(0);
return false;
}
if (!array_index)
{
width = levels[0].get_width();
height = levels[0].get_height();
total_levels = (uint32_t)levels.size();
fmt = levels[0].get_format();
}
else
{
if ((width != levels[0].get_width()) ||
(height != levels[0].get_height()) ||
(total_levels != levels.size()))
{
// All cubemap/texture array faces must be the same dimension
assert(0);
return false;
}
}
for (uint32_t level_index = 0; level_index < levels.size(); level_index++)
{
if (level_index)
{
if ( (levels[level_index].get_width() != maximum<uint32_t>(1, levels[0].get_width() >> level_index)) ||
(levels[level_index].get_height() != maximum<uint32_t>(1, levels[0].get_height() >> level_index)) )
{
// Malformed mipmap chain
assert(0);
return false;
}
}
if (fmt != levels[level_index].get_format())
{
// All input textures must use the same GPU format
assert(0);
return false;
}
}
}
uint32_t internal_fmt = KTX_ETC1_RGB8_OES, base_internal_fmt = KTX_RGB;
switch (g[0].get_format())
switch (fmt)
{
case cBC1:
{
@@ -602,51 +667,63 @@ namespace basisu
header.clear();
memcpy(&header.m_identifier, g_ktx_file_id, sizeof(g_ktx_file_id));
header.m_endianness = KTX_ENDIAN;
header.m_pixelWidth = g[0].get_width();
header.m_pixelHeight = g[0].get_height();
header.m_pixelWidth = width;
header.m_pixelHeight = height;
header.m_glInternalFormat = internal_fmt;
header.m_glBaseInternalFormat = base_internal_fmt;
header.m_numberOfMipmapLevels = (uint32_t)g.size();
header.m_numberOfFaces = 1;
header.m_numberOfArrayElements = (uint32_t)(cubemap_flag ? (gpu_images.size() / 6) : gpu_images.size());
if (header.m_numberOfArrayElements == 1)
header.m_numberOfArrayElements = 0;
header.m_numberOfMipmapLevels = total_levels;
header.m_numberOfFaces = cubemap_flag ? 6 : 1;
append_vector(ktx_data, (uint8_t *)&header, sizeof(header));
for (uint32_t level = 0; level < g.size(); level++)
for (uint32_t level_index = 0; level_index < total_levels; level_index++)
{
const gpu_image& img = g[level];
if (level)
{
if ( (img.get_format() != g[0].get_format()) ||
(img.get_width() != maximum<uint32_t>(1, g[0].get_width() >> level)) ||
(img.get_height() != maximum<uint32_t>(1, g[0].get_height() >> level)) )
{
// Bad input
assert(0);
return false;
}
}
packed_uint<4> img_size = (uint32_t)img.get_size_in_bytes();
assert(img_size && ((img_size & 3) == 0));
uint32_t img_size = gpu_images[0][level_index].get_size_in_bytes();
append_vector(ktx_data, (uint8_t *)&img_size, sizeof(img_size));
img_size = img_size * header.m_numberOfFaces * maximum<uint32_t>(1, header.m_numberOfArrayElements);
assert(img_size && ((img_size & 3) == 0));
append_vector(ktx_data, (uint8_t *)img.get_ptr(), img.get_size_in_bytes());
}
packed_uint<4> packed_img_size(img_size);
append_vector(ktx_data, (uint8_t *)&packed_img_size, sizeof(packed_img_size));
uint32_t bytes_written = 0;
for (uint32_t array_index = 0; array_index < maximum<uint32_t>(1, header.m_numberOfArrayElements); array_index++)
{
for (uint32_t face_index = 0; face_index < header.m_numberOfFaces; face_index++)
{
const gpu_image& img = gpu_images[cubemap_flag ? (array_index * 6 + face_index) : array_index][level_index];
append_vector(ktx_data, (uint8_t *)img.get_ptr(), img.get_size_in_bytes());
bytes_written += img.get_size_in_bytes();
}
} // array_index
assert(bytes_written == img_size);
} // level_index
return true;
}
bool write_compressed_texture_file(const char* pFilename, const gpu_image_vec& g)
bool write_compressed_texture_file(const char* pFilename, const std::vector<gpu_image_vec>& g, bool cubemap_flag)
{
std::string extension(string_tolower(string_get_extension(pFilename)));
uint8_vec filedata;
if (extension == "ktx")
{
if (!create_ktx_texture_file(filedata, g))
if (!create_ktx_texture_file(filedata, g, cubemap_flag))
return false;
}
else if (extension == "pvr")
@@ -671,9 +748,9 @@ namespace basisu
bool write_compressed_texture_file(const char* pFilename, const gpu_image& g)
{
gpu_image_vec v;
v.push_back(g);
return write_compressed_texture_file(pFilename, v);
std::vector<gpu_image_vec> v;
enlarge_vector(v, 1)->push_back(g);
return write_compressed_texture_file(pFilename, v, false);
}
} // basisu

View File

@@ -110,13 +110,18 @@ namespace basisu
// KTX file writing
bool create_ktx_texture_file(uint8_vec &ktx_data, const gpu_image_vec& g);
bool write_compressed_texture_file(const char *pFilename, const gpu_image& g);
bool write_compressed_texture_file(const char *pFilename, const gpu_image_vec& g);
bool create_ktx_texture_file(uint8_vec &ktx_data, const std::vector<gpu_image_vec>& gpu_images, bool cubemap_flag);
bool write_compressed_texture_file(const char *pFilename, const std::vector<gpu_image_vec>& g, bool cubemap_flag);
inline bool write_compressed_texture_file(const std::string &filename, const gpu_image &g) { return write_compressed_texture_file(filename.c_str(), g); }
inline bool write_compressed_texture_file(const std::string &filename, const gpu_image_vec &g) { return write_compressed_texture_file(filename.c_str(), g); }
inline bool write_compressed_texture_file(const char *pFilename, const gpu_image_vec &g)
{
std::vector<gpu_image_vec> a;
a.push_back(g);
return write_compressed_texture_file(pFilename, a, false);
}
bool write_compressed_texture_file(const char *pFilename, const gpu_image &g);
// GPU texture block unpacking

View File

@@ -65,6 +65,9 @@ static void print_usage()
" -debug_images: Enable codec debug images (much slower)\n"
" -compute_stats: Compute and display image quality metrics (slightly slower)\n"
" -slower: Enable optional stages in the compressor for slower but higher quality compression\n"
" -tex_type <2d, 2darray, 3d, video, cubemap>: Set Basis file header's texture type field. Cubemap arrays require multiples of 6 images, in X+, X-, Y+, Y-, Z+, Z- order, each image must be the same resolutions.\n"
" (2d=arbitrary 2D images, 2darray=2D array, 3D=volume texture slices, video=video frames, cubemap=array of faces. For 2darray/3d/cubemaps/video, each source image's dimensions and # of mipmap levels must be the same.)"
" -framerate X: Set framerate in header to X/frames sec\n"
"\n"
"More options:\n"
" -max_endpoint_clusters X: Manually set the max number of color endpoint clusters from 1-8192, use instead of -q\n"
@@ -98,6 +101,10 @@ static void print_usage()
" -no_endpoint_refinement: Disable endpoint codebook refinement stage (slightly faster, but lower quality)\n"
" -hybrid_sel_cb_quality_thresh X: Set hybrid selector codebook quality threshold, default is 2.0, try 1.5-3, higher is lower quality/smaller codebooks\n"
"\n"
"Set various fields in the Basis file header:\n"
" -userdata0 X: Set 32-bit userdata0 field in Basis file header to X (X is a signed 32-bit int)\n"
" -userdata1 X: Set 32-bit userdata1 field in Basis file header to X (X is a signed 32-bit int)\n"
"\n"
"Various command line examples:\n"
"basisu -srgb -file x.png -mipmap -y_flip : Compress a mipmapped x.basis file from an sRGB image named x.png, Y flip each source image\n"
"basisu -validate -file x.basis : Validate x.basis (check header, check file CRC's, attempt to transcode all slices)\n"
@@ -295,6 +302,50 @@ public:
m_comp_params.m_hybrid_sel_cb_quality_thresh = (float)atof(arg_v[arg_index + 1]);
arg_count++;
}
else if (strcasecmp(pArg, "-userdata0") == 0)
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_userdata0 = atoi(arg_v[arg_index + 1]);
arg_count++;
}
else if (strcasecmp(pArg, "-userdata1") == 0)
{
REMAINING_ARGS_CHECK(1);
m_comp_params.m_userdata1 = atoi(arg_v[arg_index + 1]);
arg_count++;
}
else if (strcasecmp(pArg, "-framerate") == 0)
{
REMAINING_ARGS_CHECK(1);
double fps = atof(arg_v[arg_index + 1]);
double us_per_frame = 0;
if (fps > 0)
us_per_frame = 1000000.0f / fps;
m_comp_params.m_us_per_frame = clamp<int>(static_cast<int>(us_per_frame + .5f), 0, basist::cBASISMaxUSPerFrame);
arg_count++;
}
else if (strcasecmp(pArg, "-tex_type") == 0)
{
REMAINING_ARGS_CHECK(1);
const char *pType = arg_v[arg_index + 1];
if (strcasecmp(pType, "2d") == 0)
m_comp_params.m_tex_type = basist::cBASISTexType2D;
else if (strcasecmp(pType, "2darray") == 0)
m_comp_params.m_tex_type = basist::cBASISTexType2DArray;
else if (strcasecmp(pType, "3d") == 0)
m_comp_params.m_tex_type = basist::cBASISTexTypeVolume;
else if (strcasecmp(pType, "cubemap") == 0)
m_comp_params.m_tex_type = basist::cBASISTexTypeCubemapArray;
else if (strcasecmp(pType, "video") == 0)
m_comp_params.m_tex_type = basist::cBASISTexTypeVideoFrames;
else
{
error_printf("Invalid texture type: %s\n", pType);
return false;
}
arg_count++;
}
else if (pArg[0] == '-')
{
error_printf("Unrecognized command line option: %s\n", pArg);
@@ -433,6 +484,9 @@ static bool compress_mode(command_line_params &opts)
case basis_compressor::cECFailedReadingSourceImages:
error_printf("Compressor failed reading a source image!\n");
break;
case basis_compressor::cECFailedValidating:
error_printf("Compressor failed 2darray/cubemap/video validation checks!\n");
break;
case basis_compressor::cECFailedFrontEnd:
error_printf("Compressor frontend stage failed!\n");
break;
@@ -507,11 +561,11 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
// Validate the file - note this isn't necessary for transcoding
if (!dec.validate_file_checksums(&basis_data[0], (uint32_t)basis_data.size(), true))
{
error_printf("File failed CRC checks!\n");
error_printf("File version is unsupported, or file fail CRC checks!\n");
return false;
}
printf("File CRC checks succeeded\n");
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))
@@ -532,14 +586,17 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
printf(" Endpoint codebook size: %u\n", fileinfo.m_endpoint_codebook_size);
printf(" Tables size: %u\n", fileinfo.m_tables_size);
printf(" Slices size: %u\n", fileinfo.m_slices_size);
printf(" Texture type: %s\n", basist::basis_get_texture_type_name(fileinfo.m_tex_type));
printf(" us per frame: %u (%f fps)\n", fileinfo.m_us_per_frame, fileinfo.m_us_per_frame ? (1.0f / ((float)fileinfo.m_us_per_frame / 1000000.0f)) : 0.0f);
printf(" Total slices: %u\n", (uint32_t)fileinfo.m_slice_info.size());
printf(" Total images: %i\n", fileinfo.m_total_images);
printf(" Image mipmap levels: ");
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(" Per-image mipmap levels: ");
for (uint32_t i = 0; i < fileinfo.m_total_images; i++)
printf("%u ", fileinfo.m_image_mipmap_levels[i]);
printf("\n");
printf(" Y Flipped: %u, Has alpha slices: %u\n", fileinfo.m_y_flipped, fileinfo.m_has_alpha_slices);
if (!dec.start_transcoding(&basis_data[0], (uint32_t)basis_data.size()))
{
error_printf("start_transcoding() failed!\n");
@@ -615,6 +672,25 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
{
const basist::transcoder_texture_format transcoder_tex_fmt = static_cast<basist::transcoder_texture_format>(format_iter);
if (fileinfo.m_tex_type == basist::cBASISTexTypeCubemapArray)
{
// No KTX tool that we know of supports cubemap arrays, so write individual cubemap files.
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index += 6)
{
std::vector<gpu_image_vec> cubemap;
for (uint32_t i = 0; i < 6; i++)
cubemap.push_back(gpu_images[format_iter][image_index + i]);
std::string ktx_filename(base_filename + string_format("_transcoded_cubemap_%s_%u.ktx", basist::basis_get_format_name(transcoder_tex_fmt), image_index / 6));
if (!write_compressed_texture_file(ktx_filename.c_str(), cubemap, true))
{
error_printf("Failed writing KTX file \"%s\"!\n", ktx_filename.c_str());
return false;
}
printf("Wrote KTX file \"%s\"\n", ktx_filename.c_str());
}
}
for (uint32_t image_index = 0; image_index < fileinfo.m_total_images; image_index++)
{
gpu_image_vec &gi = gpu_images[format_iter][image_index];
@@ -630,13 +706,16 @@ static bool unpack_and_validate_mode(command_line_params &opts, bool validate_fl
if (level < gi.size())
continue;
std::string ktx_filename(base_filename + string_format("_transcoded_%s_%u.ktx", basist::basis_get_format_name(transcoder_tex_fmt), image_index));
if (!write_compressed_texture_file(ktx_filename, gi))
if (fileinfo.m_tex_type != basist::cBASISTexTypeCubemapArray)
{
error_printf("Failed writing KTX file \"%s\"!\n", ktx_filename.c_str());
return false;
std::string ktx_filename(base_filename + string_format("_transcoded_%s_%u.ktx", basist::basis_get_format_name(transcoder_tex_fmt), image_index));
if (!write_compressed_texture_file(ktx_filename.c_str(), gi))
{
error_printf("Failed writing KTX file \"%s\"!\n", ktx_filename.c_str());
return false;
}
printf("Wrote KTX file \"%s\"\n", ktx_filename.c_str());
}
printf("Wrote KTX file \"%s\"\n", ktx_filename.c_str());
for (uint32_t level_index = 0; level_index < gi.size(); level_index++)
{

View File

@@ -42,12 +42,27 @@ namespace basist
basisu::packed_uint<2> m_slice_data_crc16;
};
enum
enum basis_header_flags
{
cBASISHeaderFlagETC1S = 1,
cBASISHeaderFlagYFlipped = 2,
cBASISHeaderFlagHasAlphaSlices = 4,
cBASISHeaderFlagHasExtendedData = 8
cBASISHeaderFlagHasAlphaSlices = 4
};
// The image type field attempts to describe how to interpret the image data in a Basis file.
// The encoder library doesn't really do anything special or different with these texture types, this is mostly here for the benefit of the user.
enum basis_texture_type
{
cBASISTexType2D = 0, // An arbitrary array of 2D RGB or RGBA images with optional mipmaps, array size = # images, each image may have a different resolution and # of mipmap levels
cBASISTexType2DArray = 1, // An array of 2D RGB or RGBA images with optional mipmaps, array size = # images, each image has the same resolution and mipmap levels
cBASISTexTypeCubemapArray = 2, // an array of cubemap levels, total # of images must be divisable by 6, in X+, X-, Y+, Y-, Z+, Z- order, with optional mipmaps
cBASISTexTypeVideoFrames = 3, // An array of 2D video frames, with optional mipmaps, # frames = # images, each image has the same resolution and # of mipmap levels
cBASISTexTypeVolume = 4 // A 3D texture with optional mipmaps, Z dimension = # images, each image has the same resolution and # of mipmap levels
};
enum
{
cBASISMaxUSPerFrame = 0xFFFFFF
};
struct basis_file_header
@@ -71,7 +86,9 @@ namespace basist
basisu::packed_uint<3> m_total_images;
basisu::packed_uint<1> m_format; // enum basist::block_format
basisu::packed_uint<2> m_flags;
basisu::packed_uint<2> m_flags; // enum basist::header_flags
basisu::packed_uint<1> m_tex_type; // enum basist::basis_texture_type
basisu::packed_uint<3> m_us_per_frame; // framerate of video, in microseconds per frame
basisu::packed_uint<4> m_reserved;
basisu::packed_uint<4> m_userdata0;

View File

@@ -14,12 +14,11 @@
// limitations under the License.
#include "basisu_transcoder.h"
#include "basisu_file_headers.h"
#include <limits.h>
#include <vector>
// The supported .basis file header version. Keep in sync with BASIS_FILE_VERSION.
#define BASISD_SUPPORTED_BASIS_VERSION (0x11)
#define BASISD_SUPPORTED_BASIS_VERSION (0x12)
#define BASISD_SUPPORT_DXT1 1
#define BASISD_SUPPORT_DXT5A 1
@@ -3895,6 +3894,34 @@ namespace basist
return true;
}
basis_texture_type basisu_transcoder::get_texture_type(const void *pData, uint32_t data_size) const
{
if (!validate_header_quick(pData, data_size))
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_texture_type: header validation failed\n");
return cBASISTexType2DArray;
}
const basis_file_header *pHeader = static_cast<const basis_file_header *>(pData);
return static_cast<basis_texture_type>(static_cast<uint8_t>(pHeader->m_tex_type));
}
bool basisu_transcoder::get_userdata(const void *pData, uint32_t data_size, uint32_t &userdata0, uint32_t &userdata1) const
{
if (!validate_header_quick(pData, data_size))
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_userdata: header validation failed\n");
return false;
}
const basis_file_header *pHeader = static_cast<const basis_file_header *>(pData);
userdata0 = pHeader->m_userdata0;
userdata1 = pHeader->m_userdata1;
return true;
}
uint32_t basisu_transcoder::get_total_images(const void *pData, uint32_t data_size) const
{
if (!validate_header_quick(pData, data_size))
@@ -4100,6 +4127,11 @@ namespace basist
file_info.m_slice_info.resize(total_slices);
file_info.m_slices_size = 0;
file_info.m_tex_type = static_cast<basis_texture_type>(static_cast<uint8_t>(pHeader->m_tex_type));
file_info.m_us_per_frame = pHeader->m_us_per_frame;
file_info.m_userdata0 = pHeader->m_userdata0;
file_info.m_userdata1 = pHeader->m_userdata1;
file_info.m_image_mipmap_levels.resize(0);
file_info.m_image_mipmap_levels.resize(pHeader->m_total_images);
@@ -4673,6 +4705,23 @@ namespace basist
return "";
}
const char *basis_get_texture_type_name(basis_texture_type tex_type)
{
switch (tex_type)
{
case cBASISTexType2D: return "2D";
case cBASISTexType2DArray: return "2D array";
case cBASISTexTypeCubemapArray: return "cubemap array";
case cBASISTexTypeVideoFrames: return "video";
case cBASISTexTypeVolume: return "3D";
default:
assert(0);
BASISU_DEVEL_ERROR("basis_get_texture_type_name: Invalid tex_type\n");
break;
}
return "";
}
bool basis_transcoder_format_has_alpha(transcoder_texture_format fmt)
{
switch (fmt)

View File

@@ -20,6 +20,7 @@
#include "basisu_transcoder_internal.h"
#include "basisu_global_selector_palette.h"
#include "basisu_file_headers.h"
namespace basist
{
@@ -55,6 +56,7 @@ namespace basist
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);
class basisu_transcoder;
@@ -173,11 +175,17 @@ namespace basist
uint32_t m_tables_size;
uint32_t m_slices_size;
basis_texture_type m_tex_type;
uint32_t m_us_per_frame;
// Low-level slice information (1 slice per image for color-only basis files, 2 for alpha basis files)
basisu_slice_info_vec m_slice_info;
uint32_t m_total_images; // total # of images
std::vector<uint32_t> m_image_mipmap_levels; // the # of mipmap levels for each image
uint32_t m_userdata0;
uint32_t m_userdata1;
bool m_etc1s; // always true for basis universal
bool m_y_flipped; // true if the image was Y flipped
@@ -197,6 +205,9 @@ namespace basist
// Quick header validation - no crc16 checks.
bool validate_header(const void *pData, uint32_t data_size) const;
basis_texture_type get_texture_type(const void *pData, uint32_t data_size) const;
bool get_userdata(const void *pData, uint32_t data_size, uint32_t &userdata0, uint32_t &userdata1) const;
// Returns the total number of images in the basis file (always 1 or more).
// Note that the number of mipmap levels for each image may differ, and that images may have different resolutions.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -296,7 +296,7 @@ function viewAlpha() { drawMode = 2; redraw(); }
<br>
<br>
.basis file:
<input id="file" type="text" size=30 value="xmen.basis"></input>
<input id="file" type="text" size=30 value="kodim20.basis"></input>
<input type="button" value="Run!" onclick="run()"></input>
<br>
<br>

Binary file not shown.

Binary file not shown.