blob: a1c6428aa74927aa616e394d800147c15d24fb3f [file] [log] [blame]
// basis_wrappers.cpp - Wrappers to the C++ compressor and transcoder for WebAssembly/WebGL use.
//
// **Important**:
// Compile with -fno-strict-aliasing
// This code HAS NOT been tested with strict aliasing enabled.
// The "initializeBasis()" function MUST be called at least once before using either the compressor or transcoder.
//
// There are four main categories of wrappers in this module:
// 1. Transcoding, low-level .basis file information: See class basis_file. Only depends on transcoder/basisu_transcoder.cpp.
// getFileDesc(), getImageDesc(), getImageLevelDesc(): These functions return low-level information about where compressed data is located for each image in a .basis file.
// This is useful for when you want to extract the compressed data and embed it into your own file formats, for container independent transcoding.
//
// 2. Encoding (optional): See class basis_encoder. Encodes PNG or 32bpp images to .basis files in memory. Must compile with BASISU_SUPPORT_ENCODING=1.
// Requires basisu_transcoder.cpp as well as all the .cpp files in the "encoder" directory. Results in a larger WebAssembly executable.
//
// 3. Low level transcoding/container independent transcoding: See class lowlevel_etc1s_image_transcoder or function transcodeUASTCImage(). For
// transcoding raw compressed ETC1S/UASTC texture data from non-.basis files (say from KTX2) to GPU texture data.
//
// 4. Helpers, transcoder texture format information: See functions getBytesPerBlockOrPixel(), formatHasAlpha(), etc.
// If BASISU_SUPPORT_ENCODING is 1, wrappers for the compressor will be included. Otherwise, only the wrappers for the transcoder will be compiled in.
#ifndef BASISU_SUPPORT_ENCODING
#define BASISU_SUPPORT_ENCODING 0
#endif
// Enable debug printf()'s in this module.
#ifndef BASISU_DEBUG_PRINTF
#define BASISU_DEBUG_PRINTF 0
#endif
#include "basisu_transcoder.h"
#include <emscripten/bind.h>
#include <algorithm>
#if BASISU_SUPPORT_ENCODING
#include "../../encoder/basisu_comp.h"
#include "../../encoder/basisu_resampler_filters.h"
#endif
using namespace emscripten;
using namespace basist;
using namespace basisu;
static basist::etc1_global_selector_codebook* g_pGlobal_codebook;
void basis_init()
{
#if BASISU_DEBUG_PRINTF
printf("basis_init()\n");
#endif
if (g_pGlobal_codebook)
return;
#if BASISU_SUPPORT_ENCODING
basisu_encoder_init();
#endif
basisu_transcoder_init();
g_pGlobal_codebook = new basist::etc1_global_selector_codebook(g_global_selector_cb_size, g_global_selector_cb);
}
static void copy_from_jsbuffer(const emscripten::val& srcBuffer, basisu::vector<uint8_t>& dstVec)
{
unsigned int length = srcBuffer["length"].as<unsigned int>();
dstVec.resize(length);
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = srcBuffer["constructor"].new_(memory, reinterpret_cast<uintptr_t>(dstVec.data()), length);
// Copy the bytes from the Javascript buffer.
memoryView.call<void>("set", srcBuffer);
}
static bool copy_to_jsbuffer(const emscripten::val& dstBuffer, const basisu::vector<uint8_t>& srcVec)
{
if (srcVec.empty())
{
#if BASISU_DEBUG_PRINTF
printf("copy_to_jsbuffer: Provided source buffer is empty\n");
#endif
return false;
}
// Make sure the provided buffer from Javascript is big enough. If not, bail.
int dstBufferLen = dstBuffer["byteLength"].as<int>();
if (srcVec.size() > dstBufferLen)
{
#if BASISU_DEBUG_PRINTF
printf("copy_to_jsbuffer: Provided destination buffer is too small (wanted %u bytes, got %u bytes)!\n", (uint32_t)srcVec.size(), dstBufferLen);
#endif
assert(0);
return false;
}
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = emscripten::val::global("Uint8Array").new_(memory, reinterpret_cast<uintptr_t>(srcVec.data()), srcVec.size());
// Copy the bytes into the Javascript buffer.
dstBuffer.call<void>("set", memoryView);
return true;
}
#define BASIS_MAGIC 0xDEADBEE1
#define KTX2_MAGIC 0xDEADBEE2
struct basis_file_desc
{
uint32_t m_version;
uint32_t m_us_per_frame;
uint32_t m_total_images;
uint32_t m_userdata0;
uint32_t m_userdata1;
// Type of texture (cETC1S or cUASTC4x4)
uint32_t m_tex_format; // basis_tex_format
bool m_y_flipped;
bool m_has_alpha_slices;
// ETC1S endpoint codebook
uint32_t m_num_endpoints;
uint32_t m_endpoint_palette_ofs;
uint32_t m_endpoint_palette_len;
// ETC1S selector codebook
uint32_t m_num_selectors;
uint32_t m_selector_palette_ofs;
uint32_t m_selector_palette_len;
// Huffman codelength tables
uint32_t m_tables_ofs;
uint32_t m_tables_len;
};
struct basis_image_desc
{
uint32_t m_orig_width;
uint32_t m_orig_height;
uint32_t m_num_blocks_x;
uint32_t m_num_blocks_y;
uint32_t m_num_levels;
// Will be true if the image has alpha (for UASTC this may vary per-image)
bool m_alpha_flag;
bool m_iframe_flag;
};
struct basis_image_level_desc
{
// File offset/length of the compressed ETC1S or UASTC texture data.
uint32_t m_rgb_file_ofs;
uint32_t m_rgb_file_len;
// Optional alpha data file offset/length - will be 0's for UASTC or opaque ETC1S files.
uint32_t m_alpha_file_ofs;
uint32_t m_alpha_file_len;
};
struct basis_file
{
int m_magic = 0;
basisu_transcoder m_transcoder;
basisu::vector<uint8_t> m_file;
basis_file(const emscripten::val& jsBuffer)
: m_file([&]() {
size_t byteLength = jsBuffer["byteLength"].as<size_t>();
return basisu::vector<uint8_t>(byteLength);
}()),
m_transcoder(g_pGlobal_codebook)
{
if (!g_pGlobal_codebook)
{
#if BASISU_DEBUG_PRINTF
printf("basis_file::basis_file: Must call basis_init() first!\n");
#endif
assert(0);
return;
}
unsigned int length = jsBuffer["length"].as<unsigned int>();
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = jsBuffer["constructor"].new_(memory, reinterpret_cast<uintptr_t>(m_file.data()), length);
memoryView.call<void>("set", jsBuffer);
if (!m_transcoder.validate_header(m_file.data(), m_file.size()))
{
#if BASISU_DEBUG_PRINTF
printf("basis_file::basis_file: m_transcoder.validate_header() failed!\n");
#endif
m_file.clear();
}
// Initialized after validation
m_magic = BASIS_MAGIC;
}
void close()
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return;
m_file.clear();
}
uint32_t getHasAlpha()
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
basisu_image_level_info li;
if (!m_transcoder.get_image_level_info(m_file.data(), m_file.size(), li, 0, 0))
return 0;
return li.m_alpha_flag;
}
uint32_t getNumImages()
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
return m_transcoder.get_total_images(m_file.data(), m_file.size());
}
uint32_t getNumLevels(uint32_t image_index)
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
basisu_image_info ii;
if (!m_transcoder.get_image_info(m_file.data(), m_file.size(), ii, image_index))
return 0;
return ii.m_total_levels;
}
uint32_t getImageWidth(uint32_t image_index, uint32_t level_index)
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
return orig_width;
}
uint32_t getImageHeight(uint32_t image_index, uint32_t level_index)
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
return orig_height;
}
basis_file_desc getFileDesc()
{
basis_file_desc result;
memset(&result, 0, sizeof(result));
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return result;
basisu_file_info file_info;
if (!m_transcoder.get_file_info(m_file.data(), m_file.size(), file_info))
{
assert(0);
return result;
}
result.m_version = file_info.m_version;
result.m_us_per_frame = file_info.m_us_per_frame;
result.m_total_images = file_info.m_total_images;
result.m_userdata0 = file_info.m_userdata0;
result.m_userdata1 = file_info.m_userdata1;
result.m_tex_format = static_cast<uint32_t>(file_info.m_tex_format);
result.m_y_flipped = file_info.m_y_flipped;
result.m_has_alpha_slices = file_info.m_has_alpha_slices;
result.m_num_endpoints = file_info.m_total_endpoints;
result.m_endpoint_palette_ofs = file_info.m_endpoint_codebook_ofs;
result.m_endpoint_palette_len = file_info.m_endpoint_codebook_size;
result.m_num_selectors = file_info.m_total_selectors;
result.m_selector_palette_ofs = file_info.m_selector_codebook_ofs;
result.m_selector_palette_len = file_info.m_selector_codebook_size;
result.m_tables_ofs = file_info.m_tables_ofs;
result.m_tables_len = file_info.m_tables_size;
return result;
}
basis_image_desc getImageDesc(uint32_t image_index)
{
basis_image_desc result;
memset(&result, 0, sizeof(result));
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return result;
basisu_image_info image_info;
// bool get_image_info(const void *pData, uint32_t data_size, basisu_image_info &image_info, uint32_t image_index) const;
if (!m_transcoder.get_image_info(m_file.data(), m_file.size(), image_info, image_index))
{
assert(0);
return result;
}
result.m_orig_width = image_info.m_orig_width;
result.m_orig_height = image_info.m_orig_height;
result.m_num_blocks_x = image_info.m_num_blocks_x;
result.m_num_blocks_y = image_info.m_num_blocks_y;
result.m_num_levels = image_info.m_total_levels;
result.m_alpha_flag = image_info.m_alpha_flag;
result.m_iframe_flag = image_info.m_iframe_flag;
return result;
}
basis_image_level_desc getImageLevelDesc(uint32_t image_index, uint32_t level_index)
{
basis_image_level_desc result;
memset(&result, 0, sizeof(result));
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return result;
basisu_image_level_info image_info;
if (!m_transcoder.get_image_level_info(m_file.data(), m_file.size(), image_info, image_index, level_index))
{
assert(0);
return result;
}
result.m_rgb_file_ofs = image_info.m_rgb_file_ofs;
result.m_rgb_file_len = image_info.m_rgb_file_len;
result.m_alpha_file_ofs = image_info.m_alpha_file_ofs;
result.m_alpha_file_len = image_info.m_alpha_file_len;
return result;
}
uint32_t getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format)
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
if (format >= (int)transcoder_texture_format::cTFTotalTextureFormats)
return 0;
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
// Uncompressed formats are just plain raster images.
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;
return bytes_per_slice;
}
else
{
// Compressed formats are 2D arrays of blocks.
const uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
const uint32_t size_in_bytes = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
return size_in_bytes;
}
return total_blocks * bytes_per_block;
}
}
bool isUASTC()
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return false;
return m_transcoder.get_tex_format(m_file.data(), m_file.size()) == basis_tex_format::cUASTC4x4;
}
uint32_t startTranscoding()
{
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
return m_transcoder.start_transcoding(m_file.data(), m_file.size());
}
uint32_t transcodeImage(const emscripten::val& dst, uint32_t image_index, uint32_t level_index, uint32_t format, uint32_t unused, uint32_t get_alpha_for_opaque_formats)
{
(void)unused;
assert(m_magic == BASIS_MAGIC);
if (m_magic != BASIS_MAGIC)
return 0;
if (format >= (int)transcoder_texture_format::cTFTotalTextureFormats)
return 0;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
uint32_t orig_width, orig_height, total_blocks;
if (!m_transcoder.get_image_level_desc(m_file.data(), m_file.size(), image_index, level_index, orig_width, orig_height, total_blocks))
return 0;
basisu::vector<uint8_t> dst_data;
uint32_t flags = get_alpha_for_opaque_formats ? cDecodeFlagsTranscodeAlphaDataToOpaqueFormats : 0;
uint32_t status;
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;
dst_data.resize(bytes_per_slice);
status = m_transcoder.transcode_image_level(
m_file.data(), m_file.size(), image_index, level_index,
dst_data.data(), orig_width * orig_height,
transcoder_format,
flags,
orig_width,
nullptr,
orig_height);
}
else
{
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
uint32_t required_size = total_blocks * bytes_per_block;
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
// The transcoder will clear the extra bytes followed the used blocks to 0.
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
required_size = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
assert(required_size >= total_blocks * bytes_per_block);
}
dst_data.resize(required_size);
status = m_transcoder.transcode_image_level(
m_file.data(), m_file.size(), image_index, level_index,
dst_data.data(), dst_data.size() / bytes_per_block,
static_cast<basist::transcoder_texture_format>(format),
flags);
}
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = emscripten::val::global("Uint8Array").new_(memory, reinterpret_cast<uintptr_t>(dst_data.data()), dst_data.size());
dst.call<void>("set", memoryView);
return status;
}
};
#if BASISD_SUPPORT_KTX2
struct ktx2_header_js
{
uint32_t m_vk_format;
uint32_t m_type_size;
uint32_t m_pixel_width;
uint32_t m_pixel_height;
uint32_t m_pixel_depth;
uint32_t m_layer_count;
uint32_t m_face_count;
uint32_t m_level_count;
uint32_t m_supercompression_scheme;
uint32_t m_dfd_byte_offset;
uint32_t m_dfd_byte_length;
uint32_t m_kvd_byte_offset;
uint32_t m_kvd_byte_length;
uint32_t m_sgd_byte_offset;
uint32_t m_sgd_byte_length;
};
struct ktx2_file
{
int m_magic = 0;
basist::ktx2_transcoder m_transcoder;
basisu::vector<uint8_t> m_file;
bool m_is_valid = false;
ktx2_file(const emscripten::val& jsBuffer)
: m_file([&]() {
size_t byteLength = jsBuffer["byteLength"].as<size_t>();
return basisu::vector<uint8_t>(byteLength);
}()),
m_transcoder(g_pGlobal_codebook)
{
if (!g_pGlobal_codebook)
{
#if BASISU_DEBUG_PRINTF
printf("basis_file::basis_file: Must call basis_init() first!\n");
#endif
assert(0);
return;
}
unsigned int length = jsBuffer["length"].as<unsigned int>();
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = jsBuffer["constructor"].new_(memory, reinterpret_cast<uintptr_t>(m_file.data()), length);
memoryView.call<void>("set", jsBuffer);
if (!m_transcoder.init(m_file.data(), m_file.size()))
{
#if BASISU_DEBUG_PRINTF
printf("m_transcoder.init() failed!\n");
#endif
assert(0);
m_file.clear();
}
m_is_valid = true;
// Initialized after validation
m_magic = KTX2_MAGIC;
}
bool isValid()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return false;
return m_is_valid;
}
void close()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return;
m_file.clear();
m_transcoder.clear();
}
uint32_t getDFDSize()
{
return m_transcoder.get_dfd().size();
}
uint32_t getDFD(const emscripten::val& dst)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
const uint8_vec &dst_data = m_transcoder.get_dfd();
if (dst_data.size())
return copy_to_jsbuffer(dst, dst_data);
return 1;
}
ktx2_header_js getHeader()
{
ktx2_header_js hdr;
memset(&hdr, 0, sizeof(hdr));
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return hdr;
const basist::ktx2_header& h = m_transcoder.get_header();
hdr.m_vk_format = h.m_vk_format;
hdr.m_type_size = h.m_type_size;
hdr.m_pixel_width = h.m_pixel_width;
hdr.m_pixel_height = h.m_pixel_height;
hdr.m_pixel_depth = h.m_pixel_depth;
hdr.m_layer_count = h.m_layer_count;
hdr.m_face_count = h.m_face_count;
hdr.m_level_count = h.m_level_count;
hdr.m_supercompression_scheme = h.m_supercompression_scheme;
hdr.m_dfd_byte_offset = h.m_dfd_byte_offset;
hdr.m_dfd_byte_length = h.m_dfd_byte_length;
hdr.m_kvd_byte_offset = h.m_kvd_byte_offset;
hdr.m_kvd_byte_length = h.m_kvd_byte_length;
// emscripten doesn't support binding uint64_t for some reason
assert(h.m_sgd_byte_offset <= UINT32_MAX);
assert(h.m_sgd_byte_length <= UINT32_MAX);
hdr.m_sgd_byte_offset = (uint32_t)h.m_sgd_byte_offset;
hdr.m_sgd_byte_length = (uint32_t)h.m_sgd_byte_length;
return hdr;
}
bool hasKey(std::string key_name)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return false;
return m_transcoder.find_key(key_name) != nullptr;
}
uint32_t getTotalKeys()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_key_values().size();
}
std::string getKey(uint32_t index)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return std::string("");
return std::string((const char*)m_transcoder.get_key_values()[index].m_key.data());
}
uint32_t getKeyValueSize(std::string key_name)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
const uint8_vec* p = m_transcoder.find_key(key_name);
return p ? p->size() : 0;
}
uint32_t getKeyValue(std::string key_name, const emscripten::val& dst)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
const uint8_vec* p = m_transcoder.find_key(key_name);
if (!p)
return 0;
if (p->size())
return copy_to_jsbuffer(dst, *p);
return 1;
}
uint32_t getWidth()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_width();
}
uint32_t getHeight()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_height();
}
uint32_t getFaces()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_faces();
}
uint32_t getLayers()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_layers();
}
uint32_t getLevels()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_levels();
}
uint32_t getFormat()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return (uint32_t)m_transcoder.get_format();
}
bool isUASTC()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return false;
return m_transcoder.is_uastc();
}
bool isETC1S()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return false;
return m_transcoder.is_etc1s();
}
bool getHasAlpha()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return false;
return m_transcoder.get_has_alpha();
}
uint32_t getDFDColorModel()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_color_model();
}
uint32_t getDFDColorPrimaries()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_color_primaries();
}
uint32_t getDFDTransferFunc()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_transfer_func();
}
uint32_t getDFDFlags()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_flags();
}
uint32_t getDFDTotalSamples()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_total_samples();
}
uint32_t getDFDChannelID0()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_channel_id0();
}
uint32_t getDFDChannelID1()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_dfd_channel_id1();
}
// isVideo() will return true if there was a KTXanimData key, or if (after calling start_transcoding()) there were any P-frames on ETC1S files.
bool isVideo()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.is_video();
}
// startTranscoding() must be called before calling getETC1SImageDescImageFlags().
uint32_t getETC1SImageDescImageFlags(uint32_t level_index, uint32_t layer_index, uint32_t face_index)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.get_etc1s_image_descs_image_flags(level_index, layer_index, face_index);
}
ktx2_image_level_info getImageLevelInfo(uint32_t level_index, uint32_t layer_index, uint32_t face_index)
{
ktx2_image_level_info info;
memset(&info, 0, sizeof(info));
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return info;
if (!m_transcoder.get_image_level_info(info, level_index, layer_index, face_index))
{
assert(0);
return info;
}
return info;
}
uint32_t getImageTranscodedSizeInBytes(uint32_t level_index, uint32_t layer_index, uint32_t face_index, uint32_t format)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
if (format >= (int)transcoder_texture_format::cTFTotalTextureFormats)
return 0;
ktx2_image_level_info info;
if (!m_transcoder.get_image_level_info(info, level_index, layer_index, face_index))
return 0;
uint32_t orig_width = info.m_orig_width, orig_height = info.m_orig_height, total_blocks = info.m_total_blocks;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
// Uncompressed formats are just plain raster images.
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;
return bytes_per_slice;
}
else
{
// Compressed formats are 2D arrays of blocks.
const uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
const uint32_t size_in_bytes = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
return size_in_bytes;
}
return total_blocks * bytes_per_block;
}
}
// Must be called before transcodeImage() can be called.
// On ETC1S files this method decompresses the ETC1S global data, along with fetching the ETC1S image desc array, so it's not free to call.
uint32_t startTranscoding()
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
return m_transcoder.start_transcoding();
}
// get_alpha_for_opaque_formats defaults to false
// channel0/channel1 default to -1
uint32_t transcodeImage(const emscripten::val& dst, uint32_t level_index, uint32_t layer_index, uint32_t face_index, uint32_t format, uint32_t get_alpha_for_opaque_formats, int channel0, int channel1)
{
assert(m_magic == KTX2_MAGIC);
if (m_magic != KTX2_MAGIC)
return 0;
if (format >= (int)transcoder_texture_format::cTFTotalTextureFormats)
return 0;
const transcoder_texture_format transcoder_format = static_cast<transcoder_texture_format>(format);
ktx2_image_level_info info;
if (!m_transcoder.get_image_level_info(info, level_index, layer_index, face_index))
return 0;
uint32_t orig_width = info.m_orig_width, orig_height = info.m_orig_height, total_blocks = info.m_total_blocks;
basisu::vector<uint8_t> dst_data;
uint32_t flags = get_alpha_for_opaque_formats ? cDecodeFlagsTranscodeAlphaDataToOpaqueFormats : 0;
uint32_t status;
if (basis_transcoder_format_is_uncompressed(transcoder_format))
{
const uint32_t bytes_per_pixel = basis_get_uncompressed_bytes_per_pixel(transcoder_format);
const uint32_t bytes_per_line = orig_width * bytes_per_pixel;
const uint32_t bytes_per_slice = bytes_per_line * orig_height;
dst_data.resize(bytes_per_slice);
status = m_transcoder.transcode_image_level(
level_index, layer_index, face_index,
dst_data.data(), orig_width * orig_height,
transcoder_format,
flags,
orig_width,
orig_height,
channel0, channel1,
nullptr);
}
else
{
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(transcoder_format);
uint32_t required_size = total_blocks * bytes_per_block;
if (transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGB || transcoder_format == transcoder_texture_format::cTFPVRTC1_4_RGBA)
{
// For PVRTC1, Basis only writes (or requires) total_blocks * bytes_per_block. But GL requires extra padding for very small textures:
// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
// The transcoder will clear the extra bytes followed the used blocks to 0.
const uint32_t width = (orig_width + 3) & ~3;
const uint32_t height = (orig_height + 3) & ~3;
required_size = (std::max(8U, width) * std::max(8U, height) * 4 + 7) / 8;
assert(required_size >= total_blocks * bytes_per_block);
}
dst_data.resize(required_size);
status = m_transcoder.transcode_image_level(
level_index, layer_index, face_index,
dst_data.data(), dst_data.size() / bytes_per_block,
static_cast<basist::transcoder_texture_format>(format),
flags,
0,
0,
channel0, channel1,
nullptr);
}
emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"];
emscripten::val memoryView = emscripten::val::global("Uint8Array").new_(memory, reinterpret_cast<uintptr_t>(dst_data.data()), dst_data.size());
dst.call<void>("set", memoryView);
return status;
}
};
#endif // BASISD_SUPPORT_KTX2
#if BASISU_SUPPORT_ENCODING
class basis_encoder
{
public:
basis_compressor_params m_params;
basis_encoder()
{
}
bool set_slice_source_image(uint32_t slice_index, const emscripten::val& src_image_js_val, uint32_t src_image_width, uint32_t src_image_height, bool src_image_is_png)
{
// Resize the source_images array if necessary
if (slice_index >= m_params.m_source_images.size())
m_params.m_source_images.resize(slice_index + 1);
// First copy the src image buffer to the heap.
basisu::vector<uint8_t> src_image_buf;
copy_from_jsbuffer(src_image_js_val, src_image_buf);
// Now extract the source image.
image& src_img = m_params.m_source_images[slice_index];
if (src_image_is_png)
{
// It's a PNG file, so try and parse it.
if (!load_png(src_image_buf.data(), src_image_buf.size(), src_img, nullptr))
{
#if BASISU_DEBUG_PRINTF
printf("basis_encoder::set_slice_source_image: Failed parsing provided PNG file!\n");
#endif
return false;
}
src_image_width = src_img.get_width();
src_image_height = src_img.get_height();
}
else
{
// It's a raw image, so check the buffer's size.
if (src_image_buf.size() != src_image_width * src_image_height * sizeof(uint32_t))
{
#if BASISU_DEBUG_PRINTF
printf("basis_encoder::set_slice_source_image: Provided source buffer has an invalid size!\n");
#endif
return false;
}
// Copy the raw image's data into our source image.
src_img.resize(src_image_width, src_image_height);
memcpy(src_img.get_ptr(), src_image_buf.data(), src_image_width * src_image_height * sizeof(uint32_t));
}
return true;
}
uint32_t encode(const emscripten::val& dst_basis_file_js_val)
{
if (!g_pGlobal_codebook)
{
#if BASISU_DEBUG_PRINTF
printf("basis_encoder::encode: Must call basis_init() first!\n");
#endif
assert(0);
return 0;
}
basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size, basist::g_global_selector_cb);
// We don't use threading for now, but the compressor needs a job pool.
job_pool jpool(1);
// Initialize the compression parameters structure. This is the same structure that the command line tool fills in.
basis_compressor_params &params = m_params;
params.m_pJob_pool = &jpool;
// Disabling multithreading for now, which sucks.
params.m_multithreading = false;
params.m_status_output = params.m_debug;
params.m_read_source_images = false;
params.m_write_output_basis_files = false;
params.m_pSel_codebook = &sel_codebook;
basis_compressor comp;
if (!comp.init(params))
{
#if BASISU_DEBUG_PRINTF
printf("Failed initializing BasisU compressor! One or more provided parameters may be invalid.\n");
#endif
return 0;
}
#if BASISU_DEBUG_PRINTF
printf("Begin BasisU compression\n");
#endif
basis_compressor::error_code ec = comp.process();
#if BASISU_DEBUG_PRINTF
printf("BasisU compression done, status %u, %u bytes\n", (uint32_t)ec, (uint32_t)comp.get_output_basis_file().size());
#endif
if (ec != basis_compressor::cECSuccess)
{
// Something failed during compression.
#if BASISU_DEBUG_PRINTF
printf("BasisU compression failed with status %u!\n", (uint32_t)ec);
#endif
return 0;
}
if (params.m_create_ktx2_file)
{
// Compression succeeded, so copy the .ktx2 file bytes to the caller's buffer.
if (!copy_to_jsbuffer(dst_basis_file_js_val, comp.get_output_ktx2_file()))
return 0;
// Return the file size of the .basis file in bytes.
return (uint32_t)comp.get_output_ktx2_file().size();
}
else
{
// Compression succeeded, so copy the .basis file bytes to the caller's buffer.
if (!copy_to_jsbuffer(dst_basis_file_js_val, comp.get_output_basis_file()))
return 0;
// Return the file size of the .basis file in bytes.
return (uint32_t)comp.get_output_basis_file().size();
}
}
};
#endif
class lowlevel_etc1s_image_transcoder : public basisu_lowlevel_etc1s_transcoder
{
// Using our own transcoder state, for video support.
basisu_transcoder_state m_state;
public:
lowlevel_etc1s_image_transcoder() :
basisu_lowlevel_etc1s_transcoder(g_pGlobal_codebook)
{
}
bool decode_palettes(uint32_t num_endpoints, const emscripten::val& endpoint_data, uint32_t num_selectors, const emscripten::val& selector_data)
{
basisu::vector<uint8_t> temp_endpoint_data, temp_selector_data;
copy_from_jsbuffer(endpoint_data, temp_endpoint_data);
copy_from_jsbuffer(selector_data, temp_selector_data);
#if 0
printf("decode_palettes: %u %u %u %u, %u %u\n",
num_endpoints, (uint32_t)temp_endpoint_data.size(),
num_selectors, (uint32_t)temp_selector_data.size(),
temp_endpoint_data[0], temp_selector_data[0]);
#endif
if (!temp_endpoint_data.size() || !temp_selector_data.size())
{
#if BASISU_DEBUG_PRINTF
printf("decode_tables: endpoint_data and/or selector_data is empty\n");
#endif
assert(0);
return false;
}
return basisu_lowlevel_etc1s_transcoder::decode_palettes(num_endpoints, &temp_endpoint_data[0], (uint32_t)temp_endpoint_data.size(),
num_selectors, &temp_selector_data[0], (uint32_t)temp_selector_data.size());
}
bool decode_tables(const emscripten::val& table_data)
{
basisu::vector<uint8_t> temp_table_data;
copy_from_jsbuffer(table_data, temp_table_data);
if (!temp_table_data.size())
{
#if BASISU_DEBUG_PRINTF
printf("decode_tables: table_data is empty\n");
#endif
assert(0);
return false;
}
return basisu_lowlevel_etc1s_transcoder::decode_tables(&temp_table_data[0], (uint32_t)temp_table_data.size());
}
bool transcode_image(
uint32_t target_format, // see transcoder_texture_format
const emscripten::val& output_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
const emscripten::val& compressed_data,
uint32_t num_blocks_x, uint32_t num_blocks_y, uint32_t orig_width, uint32_t orig_height, uint32_t level_index,
uint32_t rgb_offset, uint32_t rgb_length, uint32_t alpha_offset, uint32_t alpha_length,
uint32_t decode_flags, // see cDecodeFlagsPVRTCDecodeToNextPow2
bool basis_file_has_alpha_slices,
bool is_video,
uint32_t output_row_pitch_in_blocks_or_pixels,
uint32_t output_rows_in_pixels)
{
if (!g_pGlobal_codebook)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_etc1s_image: basis_init() must be called first\n");
#endif
assert(0);
return false;
}
// FIXME: Access the JavaScript buffer directly vs. copying it.
basisu::vector<uint8_t> temp_comp_data;
copy_from_jsbuffer(compressed_data, temp_comp_data);
if (!temp_comp_data.size())
{
#if BASISU_DEBUG_PRINTF
printf("transcode_etc1s_image: compressed_data is empty\n");
#endif
assert(0);
return false;
}
uint32_t output_blocks_len = output_blocks["byteLength"].as<uint32_t>();
if (!output_blocks_len)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_etc1s_image: output_blocks is empty\n");
#endif
assert(0);
return false;
}
basisu::vector<uint8_t> temp_output_blocks(output_blocks_len);
bool status = basisu_lowlevel_etc1s_transcoder::transcode_image(
(transcoder_texture_format)target_format,
&temp_output_blocks[0], output_blocks_buf_size_in_blocks_or_pixels,
&temp_comp_data[0], temp_comp_data.size(),
num_blocks_x, num_blocks_y, orig_width, orig_height, level_index,
rgb_offset, rgb_length, alpha_offset, alpha_length,
decode_flags,
basis_file_has_alpha_slices,
is_video,
output_row_pitch_in_blocks_or_pixels,
&m_state,
output_rows_in_pixels);
if (!status)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_etc1s_image: basisu_lowlevel_etc1s_transcoder::transcode_image failed\n");
#endif
assert(0);
return false;
}
if (!copy_to_jsbuffer(output_blocks, temp_output_blocks))
return false;
return true;
}
};
bool transcode_uastc_image(
uint32_t target_format_int, // see transcoder_texture_format
const emscripten::val& output_blocks, uint32_t output_blocks_buf_size_in_blocks_or_pixels,
const emscripten::val& compressed_data,
uint32_t num_blocks_x, uint32_t num_blocks_y, uint32_t orig_width, uint32_t orig_height, uint32_t level_index,
uint32_t slice_offset, uint32_t slice_length,
uint32_t decode_flags, // see cDecodeFlagsPVRTCDecodeToNextPow2
bool has_alpha,
bool is_video,
uint32_t output_row_pitch_in_blocks_or_pixels,
uint32_t output_rows_in_pixels,
int channel0, int channel1)
{
transcoder_texture_format target_format = static_cast<transcoder_texture_format>(target_format_int);
if (!g_pGlobal_codebook)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_uastc_image: basis_init() must be called first\n");
#endif
assert(0);
return false;
}
// FIXME: Access the JavaScript buffer directly vs. copying it.
basisu::vector<uint8_t> temp_comp_data;
copy_from_jsbuffer(compressed_data, temp_comp_data);
if (!temp_comp_data.size())
{
#if BASISU_DEBUG_PRINTF
printf("transcode_uastc_image: compressed_data is empty\n");
#endif
assert(0);
return false;
}
uint32_t output_blocks_len = output_blocks["byteLength"].as<uint32_t>();
if (!output_blocks_len)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_uastc_image: output_blocks is empty\n");
#endif
assert(0);
return false;
}
#if 0
printf("format: %u\n", (uint32_t)target_format);
printf("output_blocks size: %u buf size: %u\n", output_blocks_len, output_blocks_buf_size_in_blocks_or_pixels);
printf("compressed_data size: %u\n", compressed_data["byteLength"].as<uint32_t>());
printf("%u %u %u %u %u\n", num_blocks_x, num_blocks_y, orig_width, orig_height, level_index);
printf("%u %u\n", slice_offset, slice_length);
printf("%u\n", decode_flags);
printf("has_alpha: %u is_video: %u\n", has_alpha, is_video);
#endif
basisu::vector<uint8_t> temp_output_blocks(output_blocks_len);
basisu_lowlevel_uastc_transcoder transcoder;
bool status = transcoder.transcode_image(
(transcoder_texture_format)target_format,
&temp_output_blocks[0], output_blocks_buf_size_in_blocks_or_pixels,
&temp_comp_data[0], temp_comp_data.size(),
num_blocks_x, num_blocks_y, orig_width, orig_height, level_index,
slice_offset, slice_length,
decode_flags,
has_alpha,
is_video,
output_row_pitch_in_blocks_or_pixels,
nullptr,
output_rows_in_pixels,
channel0, channel1);
if (!status)
{
#if BASISU_DEBUG_PRINTF
printf("transcode_uastc_image: basisu_lowlevel_uastc_transcoder::transcode_image failed\n");
#endif
assert(0);
return false;
}
if (!copy_to_jsbuffer(output_blocks, temp_output_blocks))
return false;
return true;
}
uint32_t get_bytes_per_block_or_pixel(uint32_t transcoder_tex_fmt)
{
return basis_get_bytes_per_block_or_pixel(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
bool format_has_alpha(uint32_t transcoder_tex_fmt)
{
return basis_transcoder_format_has_alpha(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
bool format_is_uncompressed(uint32_t transcoder_tex_fmt)
{
return basis_transcoder_format_is_uncompressed(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
bool is_format_supported(uint32_t transcoder_tex_fmt)
{
return basis_is_format_supported(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
uint32_t get_format_block_width(uint32_t transcoder_tex_fmt)
{
return basis_get_block_width(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
uint32_t get_format_block_height(uint32_t transcoder_tex_fmt)
{
return basis_get_block_height(static_cast<transcoder_texture_format>(transcoder_tex_fmt));
}
EMSCRIPTEN_BINDINGS(basis_codec) {
function("initializeBasis", &basis_init);
// Expose BasisFileDesc structure
value_object<basis_file_desc>("BasisFileDesc")
.field("version", &basis_file_desc::m_version)
.field("usPerFrame", &basis_file_desc::m_us_per_frame)
.field("totalImages", &basis_file_desc::m_total_images)
.field("userdata0", &basis_file_desc::m_userdata0)
.field("userdata1", &basis_file_desc::m_userdata1)
.field("texFormat", &basis_file_desc::m_tex_format)
.field("yFlipped", &basis_file_desc::m_y_flipped)
.field("hasAlphaSlices", &basis_file_desc::m_has_alpha_slices)
.field("numEndpoints", &basis_file_desc::m_num_endpoints)
.field("endpointPaletteOfs", &basis_file_desc::m_endpoint_palette_ofs)
.field("endpointPaletteLen", &basis_file_desc::m_endpoint_palette_len)
.field("numSelectors", &basis_file_desc::m_num_selectors)
.field("selectorPaletteOfs", &basis_file_desc::m_selector_palette_ofs)
.field("selectorPaletteLen", &basis_file_desc::m_selector_palette_len)
.field("tablesOfs", &basis_file_desc::m_tables_ofs)
.field("tablesLen", &basis_file_desc::m_tables_len)
;
// Expose BasisImageDesc structure
value_object<basis_image_desc>("BasisImageDesc")
.field("origWidth", &basis_image_desc::m_orig_width)
.field("origHeight", &basis_image_desc::m_orig_height)
.field("numBlocksX", &basis_image_desc::m_num_blocks_x)
.field("numBlocksY", &basis_image_desc::m_num_blocks_y)
.field("numLevels", &basis_image_desc::m_num_levels)
.field("alphaFlag", &basis_image_desc::m_alpha_flag)
.field("iframeFlag", &basis_image_desc::m_iframe_flag)
;
// Expose BasisImageLevelDesc structure
value_object<basis_image_level_desc>("BasisImageLevelDesc")
.field("rgbFileOfs", &basis_image_level_desc::m_rgb_file_ofs)
.field("rgbFileLen", &basis_image_level_desc::m_rgb_file_len)
.field("alphaFileOfs", &basis_image_level_desc::m_alpha_file_ofs)
.field("alphaFileLen", &basis_image_level_desc::m_alpha_file_len)
;
// Expose some key enums to JavaScript code.
// enum class transcoder_texture_format
enum_<transcoder_texture_format>("transcoder_texture_format")
.value("cTFETC1_RGB", transcoder_texture_format::cTFETC1_RGB)
.value("cTFETC2_RGBA", transcoder_texture_format::cTFETC2_RGBA)
.value("cTFBC1_RGB", transcoder_texture_format::cTFBC1_RGB)
.value("cTFBC3_RGBA", transcoder_texture_format::cTFBC3_RGBA)
.value("cTFBC4_R", transcoder_texture_format::cTFBC4_R)
.value("cTFBC5_RG", transcoder_texture_format::cTFBC5_RG)
.value("cTFBC7_RGBA", transcoder_texture_format::cTFBC7_RGBA)
.value("cTFPVRTC1_4_RGB", transcoder_texture_format::cTFPVRTC1_4_RGB)
.value("cTFPVRTC1_4_RGBA", transcoder_texture_format::cTFPVRTC1_4_RGBA)
.value("cTFASTC_4x4_RGBA", transcoder_texture_format::cTFASTC_4x4_RGBA)
.value("cTFATC_RGB", transcoder_texture_format::cTFATC_RGB)
.value("cTFATC_RGBA", transcoder_texture_format::cTFATC_RGBA)
.value("cTFFXT1_RGB", transcoder_texture_format::cTFFXT1_RGB)
.value("cTFPVRTC2_4_RGB", transcoder_texture_format::cTFPVRTC2_4_RGB)
.value("cTFPVRTC2_4_RGBA", transcoder_texture_format::cTFPVRTC2_4_RGBA)
.value("cTFETC2_EAC_R11", transcoder_texture_format::cTFETC2_EAC_R11)
.value("cTFETC2_EAC_RG11", transcoder_texture_format::cTFETC2_EAC_RG11)
.value("cTFRGBA32", transcoder_texture_format::cTFRGBA32)
.value("cTFRGB565", transcoder_texture_format::cTFRGB565)
.value("cTFBGR565", transcoder_texture_format::cTFBGR565)
.value("cTFRGBA4444", transcoder_texture_format::cTFRGBA4444)
.value("cTFTotalTextureFormats", transcoder_texture_format::cTFTotalTextureFormats)
;
// Expose some useful transcoder_texture_format helper functions
function("getBytesPerBlockOrPixel", &get_bytes_per_block_or_pixel);
function("formatHasAlpha", &format_has_alpha);
function("formatIsUncompressed", &format_is_uncompressed);
function("isFormatSupported", &is_format_supported);
function("getFormatBlockWidth", &get_format_block_width);
function("getFormatBlockHeight", &get_format_block_height);
// Expose enum basis_texture_type
enum_<basis_texture_type>("basis_texture_type")
.value("cBASISTexType2D", cBASISTexType2D)
.value("cBASISTexType2DArray", cBASISTexType2DArray)
.value("cBASISTexTypeCubemapArray", cBASISTexTypeCubemapArray)
.value("cBASISTexTypeVideoFrames", cBASISTexTypeVideoFrames)
.value("cBASISTexTypeVolume", cBASISTexTypeVolume)
;
// Expose enum basis_tex_format
enum_<basis_tex_format>("basis_tex_format")
.value("cETC1S", basis_tex_format::cETC1S)
.value("cUASTC4x4", basis_tex_format::cUASTC4x4)
;
// .basis file transcoder object. If all you want to do is transcode already encoded .basis files, this is all you really need.
class_<basis_file>("BasisFile")
.constructor<const emscripten::val&>()
.function("close", optional_override([](basis_file& self) {
return self.close();
}))
.function("getHasAlpha", optional_override([](basis_file& self) {
return self.getHasAlpha();
}))
.function("isUASTC", optional_override([](basis_file& self) {
return self.isUASTC();
}))
.function("getNumImages", optional_override([](basis_file& self) {
return self.getNumImages();
}))
.function("getNumLevels", optional_override([](basis_file& self, uint32_t imageIndex) {
return self.getNumLevels(imageIndex);
}))
.function("getImageWidth", optional_override([](basis_file& self, uint32_t imageIndex, uint32_t levelIndex) {
return self.getImageWidth(imageIndex, levelIndex);
}))
.function("getImageHeight", optional_override([](basis_file& self, uint32_t imageIndex, uint32_t levelIndex) {
return self.getImageHeight(imageIndex, levelIndex);
}))
// format is enum class transcoder_texture_format
.function("getImageTranscodedSizeInBytes", optional_override([](basis_file& self, uint32_t imageIndex, uint32_t levelIndex, uint32_t format) {
return self.getImageTranscodedSizeInBytes(imageIndex, levelIndex, format);
}))
.function("startTranscoding", optional_override([](basis_file& self) {
return self.startTranscoding();
}))
// format is enum class transcoder_texture_format
.function("transcodeImage", optional_override([](basis_file& self, const emscripten::val& dst, uint32_t imageIndex, uint32_t levelIndex, uint32_t format, uint32_t unused, uint32_t getAlphaForOpaqueFormats) {
return self.transcodeImage(dst, imageIndex, levelIndex, format, unused, getAlphaForOpaqueFormats);
}))
// Returns low-level information about the basis file.
.function("getFileDesc", optional_override([](basis_file& self) {
return self.getFileDesc();
}))
// Returns low-level information about a specific image in a basis file. An image can contain 1 or more mipmap levels.
.function("getImageDesc", optional_override([](basis_file& self, uint32_t imageIndex) {
return self.getImageDesc(imageIndex);
}))
// Returns low-level information about a specific image mipmap level in a basis file.
.function("getImageLevelDesc", optional_override([](basis_file& self, uint32_t imageIndex, uint32_t levelIndex) {
return self.getImageLevelDesc(imageIndex, levelIndex);
}))
;
// Low-level container independent transcoding of ETC1S and UASTC slice data.
// These functions allow the caller to transcode compressed ETC1S or UASTC texture data that is embedded within arbitrary data files, such as from KTX2.
enum_<basisu_decode_flags>("basisu_decode_flags")
.value("cDecodeFlagsPVRTCDecodeToNextPow2", cDecodeFlagsPVRTCDecodeToNextPow2)
.value("cDecodeFlagsTranscodeAlphaDataToOpaqueFormats", cDecodeFlagsTranscodeAlphaDataToOpaqueFormats)
.value("cDecodeFlagsBC1ForbidThreeColorBlocks", cDecodeFlagsBC1ForbidThreeColorBlocks)
.value("cDecodeFlagsOutputHasAlphaIndices", cDecodeFlagsOutputHasAlphaIndices)
.value("cDecodeFlagsHighQuality", cDecodeFlagsHighQuality)
;
// The low-level ETC1S transcoder is a class because it has persistent state (such as the endpoint/selector codebooks and Huffman tables, and transcoder state for video)
class_<lowlevel_etc1s_image_transcoder>("LowLevelETC1SImageTranscoder")
.constructor<>()
.function("decodePalettes", &lowlevel_etc1s_image_transcoder::decode_palettes)
.function("decodeTables", &lowlevel_etc1s_image_transcoder::decode_tables)
.function("transcodeImage", &lowlevel_etc1s_image_transcoder::transcode_image)
;
// The low-level UASTC transcoder is a single function.
function("transcodeUASTCImage", &transcode_uastc_image);
function("transcoderSupportsKTX2", &basisu_transcoder_supports_ktx2);
function("transcoderSupportsKTX2Zstd", &basisu_transcoder_supports_ktx2_zstd);
#if BASISD_SUPPORT_KTX2
// KTX2 enums/constants
enum_<ktx2_supercompression>("ktx2_supercompression")
.value("KTX2_SS_NONE", KTX2_SS_NONE)
.value("KTX2_SS_BASISLZ", KTX2_SS_BASISLZ)
.value("KTX2_SS_ZSTANDARD", KTX2_SS_ZSTANDARD)
;
constant("KTX2_VK_FORMAT_UNDEFINED", KTX2_VK_FORMAT_UNDEFINED);
constant("KTX2_KDF_DF_MODEL_UASTC", KTX2_KDF_DF_MODEL_UASTC);
constant("KTX2_KDF_DF_MODEL_ETC1S", KTX2_KDF_DF_MODEL_ETC1S);
constant("KTX2_IMAGE_IS_P_FRAME", KTX2_IMAGE_IS_P_FRAME);
constant("KTX2_UASTC_BLOCK_SIZE", KTX2_UASTC_BLOCK_SIZE);
constant("KTX2_MAX_SUPPORTED_LEVEL_COUNT", KTX2_MAX_SUPPORTED_LEVEL_COUNT);
constant("KTX2_KHR_DF_TRANSFER_LINEAR", KTX2_KHR_DF_TRANSFER_LINEAR);
constant("KTX2_KHR_DF_TRANSFER_SRGB", KTX2_KHR_DF_TRANSFER_SRGB);
enum_<ktx2_df_channel_id>("ktx2_df_channel_id")
.value("KTX2_DF_CHANNEL_ETC1S_RGB", KTX2_DF_CHANNEL_ETC1S_RGB)
.value("KTX2_DF_CHANNEL_ETC1S_RRR", KTX2_DF_CHANNEL_ETC1S_RRR)
.value("KTX2_DF_CHANNEL_ETC1S_GGG", KTX2_DF_CHANNEL_ETC1S_GGG)
.value("KTX2_DF_CHANNEL_ETC1S_AAA", KTX2_DF_CHANNEL_ETC1S_AAA)
.value("KTX2_DF_CHANNEL_UASTC_DATA", KTX2_DF_CHANNEL_UASTC_DATA)
.value("KTX2_DF_CHANNEL_UASTC_RGB", KTX2_DF_CHANNEL_UASTC_RGB)
.value("KTX2_DF_CHANNEL_UASTC_RGBA", KTX2_DF_CHANNEL_UASTC_RGBA)
.value("KTX2_DF_CHANNEL_UASTC_RRR", KTX2_DF_CHANNEL_UASTC_RRR)
.value("KTX2_DF_CHANNEL_UASTC_RRRG", KTX2_DF_CHANNEL_UASTC_RRRG)
.value("KTX2_DF_CHANNEL_UASTC_RG", KTX2_DF_CHANNEL_UASTC_RG)
;
enum_<ktx2_df_color_primaries>("ktx2_df_color_primaries")
.value("KTX2_DF_PRIMARIES_UNSPECIFIED", KTX2_DF_PRIMARIES_UNSPECIFIED)
.value("KTX2_DF_PRIMARIES_BT709", KTX2_DF_PRIMARIES_BT709)
.value("KTX2_DF_PRIMARIES_SRGB", KTX2_DF_PRIMARIES_SRGB)
.value("KTX2_DF_PRIMARIES_BT601_EBU", KTX2_DF_PRIMARIES_BT601_EBU)
.value("KTX2_DF_PRIMARIES_BT601_SMPTE", KTX2_DF_PRIMARIES_BT601_SMPTE)
.value("KTX2_DF_PRIMARIES_BT2020", KTX2_DF_PRIMARIES_BT2020)
.value("KTX2_DF_PRIMARIES_CIEXYZ", KTX2_DF_PRIMARIES_CIEXYZ)
.value("KTX2_DF_PRIMARIES_ACES", KTX2_DF_PRIMARIES_ACES)
.value("KTX2_DF_PRIMARIES_ACESCC", KTX2_DF_PRIMARIES_ACESCC)
.value("KTX2_DF_PRIMARIES_NTSC1953", KTX2_DF_PRIMARIES_NTSC1953)
.value("KTX2_DF_PRIMARIES_PAL525", KTX2_DF_PRIMARIES_PAL525)
.value("KTX2_DF_PRIMARIES_DISPLAYP3", KTX2_DF_PRIMARIES_DISPLAYP3)
.value("KTX2_DF_PRIMARIES_ADOBERGB", KTX2_DF_PRIMARIES_ADOBERGB)
;
// Expose ktx2_image_level_info structure
value_object<ktx2_image_level_info>("KTX2ImageLevelInfo")
.field("levelIndex", &ktx2_image_level_info::m_level_index)
.field("layerIndex", &ktx2_image_level_info::m_layer_index)
.field("faceIndex", &ktx2_image_level_info::m_face_index)
.field("origWidth", &ktx2_image_level_info::m_orig_width)
.field("origHeight", &ktx2_image_level_info::m_orig_height)
.field("width", &ktx2_image_level_info::m_width)
.field("height", &ktx2_image_level_info::m_height)
.field("numBlocksX", &ktx2_image_level_info::m_num_blocks_x)
.field("numBlocksY", &ktx2_image_level_info::m_num_blocks_y)
.field("totalBlocks", &ktx2_image_level_info::m_total_blocks)
.field("alphaFlag", &ktx2_image_level_info::m_alpha_flag)
.field("iframeFlag", &ktx2_image_level_info::m_iframe_flag)
;
// Expose the ktx2_header_js structure
value_object<ktx2_header_js>("KTX2Header")
.field("vkFormat", &ktx2_header_js::m_vk_format)
.field("typeSize", &ktx2_header_js::m_type_size)
.field("pixelWidth", &ktx2_header_js::m_pixel_width)
.field("pixelHeight", &ktx2_header_js::m_pixel_height)
.field("pixelDepth", &ktx2_header_js::m_pixel_depth)
.field("layerCount", &ktx2_header_js::m_layer_count)
.field("faceCount", &ktx2_header_js::m_face_count)
.field("levelCount", &ktx2_header_js::m_level_count)
.field("supercompressionScheme", &ktx2_header_js::m_supercompression_scheme)
.field("dfdByteOffset", &ktx2_header_js::m_dfd_byte_offset)
.field("dfdByteLength", &ktx2_header_js::m_dfd_byte_length)
.field("kvdByteOffset", &ktx2_header_js::m_kvd_byte_offset)
.field("kvdByteLength", &ktx2_header_js::m_kvd_byte_length)
.field("sgdByteOffset", &ktx2_header_js::m_sgd_byte_offset)
.field("sgdByteLength", &ktx2_header_js::m_sgd_byte_length)
;
// KTX2 transcoder class
class_<ktx2_file>("KTX2File")
.constructor<const emscripten::val&>()
.function("isValid", &ktx2_file::isValid)
.function("close", &ktx2_file::close)
.function("getDFDSize", &ktx2_file::getDFDSize)
.function("getDFD", &ktx2_file::getDFD)
.function("getHeader", &ktx2_file::getHeader)
.function("hasKey", &ktx2_file::hasKey)
.function("getTotalKeys", &ktx2_file::getTotalKeys)
.function("getKey", &ktx2_file::getKey)
.function("getKeyValueSize", &ktx2_file::getKeyValueSize)
.function("getKeyValue", &ktx2_file::getKeyValue)
.function("getWidth", &ktx2_file::getWidth)
.function("getHeight", &ktx2_file::getHeight)
.function("getFaces", &ktx2_file::getFaces)
.function("getLayers", &ktx2_file::getLayers)
.function("getLevels", &ktx2_file::getLevels)
.function("getFormat", &ktx2_file::getFormat)
.function("isUASTC", &ktx2_file::isUASTC)
.function("isETC1S", &ktx2_file::isETC1S)
.function("getHasAlpha", &ktx2_file::getHasAlpha)
.function("getDFDColorModel", &ktx2_file::getDFDColorModel)
.function("getDFDColorPrimaries", &ktx2_file::getDFDColorPrimaries)
.function("getDFDTransferFunc", &ktx2_file::getDFDTransferFunc)
.function("getDFDFlags", &ktx2_file::getDFDFlags)
.function("getDFDTotalSamples", &ktx2_file::getDFDTotalSamples)
.function("getDFDChannelID0", &ktx2_file::getDFDChannelID0)
.function("getDFDChannelID1", &ktx2_file::getDFDChannelID1)
.function("isVideo", &ktx2_file::isVideo)
.function("getETC1SImageDescImageFlags", &ktx2_file::getETC1SImageDescImageFlags)
.function("getImageLevelInfo", &ktx2_file::getImageLevelInfo)
.function("getImageTranscodedSizeInBytes", &ktx2_file::getImageTranscodedSizeInBytes)
.function("startTranscoding", &ktx2_file::startTranscoding)
.function("transcodeImage", &ktx2_file::transcodeImage)
;
#endif // BASISD_SUPPORT_KTX2
// Optional encoding/compression support of .basis and .KTX2 files (the same class encodes/compresses to either format).
#if BASISU_SUPPORT_ENCODING
// Compressor Constants
constant("BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION", BASISU_MAX_SUPPORTED_TEXTURE_DIMENSION);
constant("BASISU_DEFAULT_ENDPOINT_RDO_THRESH", BASISU_DEFAULT_ENDPOINT_RDO_THRESH);
constant("BASISU_DEFAULT_SELECTOR_RDO_THRESH", BASISU_DEFAULT_SELECTOR_RDO_THRESH);
constant("BASISU_DEFAULT_QUALITY", BASISU_DEFAULT_QUALITY);
constant("BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH", BASISU_DEFAULT_HYBRID_SEL_CB_QUALITY_THRESH);
constant("BASISU_MAX_IMAGE_DIMENSION", BASISU_MAX_IMAGE_DIMENSION);
constant("BASISU_QUALITY_MIN", BASISU_QUALITY_MIN);
constant("BASISU_QUALITY_MAX", BASISU_QUALITY_MAX);
constant("BASISU_MAX_ENDPOINT_CLUSTERS", BASISU_MAX_ENDPOINT_CLUSTERS);
constant("BASISU_MAX_SELECTOR_CLUSTERS", BASISU_MAX_SELECTOR_CLUSTERS);
constant("BASISU_MAX_SLICES", BASISU_MAX_SLICES);
constant("BASISU_RDO_UASTC_DICT_SIZE_DEFAULT", BASISU_RDO_UASTC_DICT_SIZE_DEFAULT);
constant("BASISU_RDO_UASTC_DICT_SIZE_MIN", BASISU_RDO_UASTC_DICT_SIZE_MIN);
constant("BASISU_RDO_UASTC_DICT_SIZE_MAX", BASISU_RDO_UASTC_DICT_SIZE_MAX);
constant("BASISU_MAX_RESAMPLER_FILTERS", g_num_resample_filters);
constant("BASISU_DEFAULT_COMPRESSION_LEVEL", BASISU_DEFAULT_COMPRESSION_LEVEL);
constant("BASISU_MAX_COMPRESSION_LEVEL", BASISU_MAX_COMPRESSION_LEVEL);
constant("cPackUASTCLevelFastest", cPackUASTCLevelFastest);
constant("cPackUASTCLevelFaster", cPackUASTCLevelFaster);
constant("cPackUASTCLevelDefault", cPackUASTCLevelDefault);
constant("cPackUASTCLevelSlower", cPackUASTCLevelSlower);
constant("cPackUASTCLevelVerySlow", cPackUASTCLevelVerySlow);
constant("cPackUASTCLevelMask", cPackUASTCLevelMask);
constant("cPackUASTCFavorUASTCError", cPackUASTCFavorUASTCError);
constant("cPackUASTCFavorBC7Error", cPackUASTCFavorBC7Error);
constant("cPackUASTCETC1FasterHints", cPackUASTCETC1FasterHints);
constant("cPackUASTCETC1FastestHints", cPackUASTCETC1FastestHints);
constant("cPackUASTCETC1DisableFlipAndIndividual", cPackUASTCETC1DisableFlipAndIndividual);
constant("UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO", UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO);
constant("UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH", UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH);
// Compression/encoding object.
// You create this object, call the set() methods to fill in the parameters/source images/options, call encode(), and you get back a .basis or .KTX2 file.
// You can call .encode() multiple times, changing the parameters/options in between calls.
// By default this class encodes to .basis, but call setCreateKTX2File() with true to get .KTX2 files.
class_<basis_encoder>("BasisEncoder")
.constructor<>()
// Compresses the provided source slice(s) to an output .basis file.
// At the minimum, you must provided at least 1 source slice by calling setSliceSourceImage() before calling this method.
.function("encode", optional_override([](basis_encoder& self, const emscripten::val& dst_basis_file_js_val) {
return self.encode(dst_basis_file_js_val);
}))
// Sets the slice's source image, either from a PNG file or from a raw 32-bit RGBA raster image.
// If the input is a raster image, the buffer must be width*height*4 bytes in size. The raster image is stored in top down scanline order.
// The first texel is the top-left texel. The texel byte order in memory is R,G,B,A (R first at offset 0, A last at offset 3).
// slice_index is the slice to change. Valid range is [0,BASISU_MAX_SLICES-1].
.function("setSliceSourceImage", optional_override([](basis_encoder& self, uint32_t slice_index, const emscripten::val& src_image_js_val, uint32_t width, uint32_t height, bool src_image_is_png) {
return self.set_slice_source_image(slice_index, src_image_js_val, width, height, src_image_is_png);
}))
// If true, the encoder will output a UASTC texture, otherwise a ETC1S texture.
.function("setUASTC", optional_override([](basis_encoder& self, bool uastc_flag) {
self.m_params.m_uastc = uastc_flag;
}))
// If true the source images will be Y flipped before compression.
.function("setYFlip", optional_override([](basis_encoder& self, bool y_flip_flag) {
self.m_params.m_y_flip = y_flip_flag;
}))
// Enables debug output to stdout
.function("setDebug", optional_override([](basis_encoder& self, bool debug_flag) {
self.m_params.m_debug = debug_flag;
g_debug_printf = debug_flag;
}))
// If true, the input is assumed to be in sRGB space. Be sure to set this correctly! (Examples: True on photos, albedo/spec maps, and false on normal maps.)
.function("setPerceptual", optional_override([](basis_encoder& self, bool perceptual_flag) {
self.m_params.m_perceptual = perceptual_flag;
}))
// Check source images for active/used alpha channels
.function("setCheckForAlpha", optional_override([](basis_encoder& self, bool check_for_alpha_flag) {
self.m_params.m_check_for_alpha = check_for_alpha_flag;
}))
// Fource output .basis file to have an alpha channel
.function("setForceAlpha", optional_override([](basis_encoder& self, bool force_alpha_flag) {
self.m_params.m_force_alpha = force_alpha_flag;
}))
// Set source image component swizzle.
// r,g,b,a - valid range is [0,3]
.function("setSwizzle", optional_override([](basis_encoder& self, uint32_t r, uint32_t g, uint32_t b, uint32_t a) {
assert((r < 4) && (g < 4) && (b < 4) && (a < 4));
self.m_params.m_swizzle[0] = (char)r;
self.m_params.m_swizzle[1] = (char)g;
self.m_params.m_swizzle[2] = (char)b;
self.m_params.m_swizzle[3] = (char)a;
}))
// If true, the input is assumed to be a normal map, and all source texels will be renormalized before encoding.
.function("setRenormalize", optional_override([](basis_encoder& self, bool renormalize_flag) {
self.m_params.m_renormalize = renormalize_flag;
}))
// Sets the max # of endpoint clusters for ETC1S mode. Use instead of setQualityLevel.
// Default is 512, range is [1,BASISU_MAX_ENDPOINT_CLUSTERS]
.function("setMaxEndpointClusters", optional_override([](basis_encoder& self, uint32_t max_endpoint_clusters) {
assert(max_endpoint_clusters <= BASISU_MAX_ENDPOINT_CLUSTERS);
self.m_params.m_max_endpoint_clusters = max_endpoint_clusters;
}))
// Sets the max # of selectors clusters for ETC1S mode. Use instead of setQualityLevel.
// Default is 512, range is [1,BASISU_MAX_ENDPOINT_CLUSTERS]
.function("setMaxSelectorClusters", optional_override([](basis_encoder& self, uint32_t max_selector_clusters) {
assert(max_selector_clusters <= BASISU_MAX_SELECTOR_CLUSTERS);
self.m_params.m_max_selector_clusters = max_selector_clusters;
}))
// Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff.
// Default is -1 (meaning unused - the compressor will use m_max_endpoint_clusters/m_max_selector_clusters instead to control the codebook sizes).
// Range is [1,BASISU_QUALITY_MAX]
.function("setQualityLevel", optional_override([](basis_encoder& self, int quality_level) {
assert(quality_level >= -1 && quality_level <= BASISU_QUALITY_MAX);
self.m_params.m_quality_level = quality_level;
}))
// The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files.
// It does not directly control file size vs. quality - see quality_level().
// Default is BASISU_DEFAULT_COMPRESSION_LEVEL, range is [0,BASISU_MAX_COMPRESSION_LEVEL]
.function("setCompressionLevel", optional_override([](basis_encoder& self, int comp_level) {
assert(comp_level >= 0 && comp_level <= BASISU_MAX_COMPRESSION_LEVEL);
self.m_params.m_compression_level = comp_level;
}))
// setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps.
.function("setNormalMap", optional_override([](basis_encoder& self) {
self.m_params.m_perceptual = false;
self.m_params.m_mip_srgb = false;
self.m_params.m_no_selector_rdo = true;
self.m_params.m_no_endpoint_rdo = true;
}))
// Sets selector RDO threshold
// Default is BASISU_DEFAULT_SELECTOR_RDO_THRESH, range is [0,1e+10]
.function("setSelectorRDOThresh", optional_override([](basis_encoder& self, float selector_rdo_thresh) {
self.m_params.m_selector_rdo_thresh = selector_rdo_thresh;
}))
// Sets endpoint RDO threshold
// Default is BASISU_DEFAULT_ENDPOINT_RDO_THRESH, range is [0,1e+10]
.function("setEndpointRDOThresh", optional_override([](basis_encoder& self, float endpoint_rdo_thresh) {
self.m_params.m_endpoint_rdo_thresh = endpoint_rdo_thresh;
}))
#if BASISD_SUPPORT_KTX2
// --- KTX2 related options
//
// Create .KTX2 files instead of .basis files. By default this is FALSE.
.function("setCreateKTX2File", optional_override([](basis_encoder& self, bool create_ktx2_file) {
self.m_params.m_create_ktx2_file = create_ktx2_file;
}))
// KTX2: Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE.
.function("setKTX2UASTCSupercompression", optional_override([](basis_encoder& self, bool use_zstandard) {
self.m_params.m_ktx2_uastc_supercompression = use_zstandard ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;
}))
// KTX2: Use sRGB transfer func in the file's DFD. Default is FALSE. This should very probably match the "perceptual" setting.
.function("setKTX2SRGBTransferFunc", optional_override([](basis_encoder& self, bool srgb_transfer_func) {
self.m_params.m_ktx2_srgb_transfer_func = srgb_transfer_func;
}))
// TODO: Expose KTX2 key value array, other options to JavaScript. See encoder/basisu_comp.h.
#endif
// --- Mip-map options
// If true mipmaps will be generated from the source images
.function("setMipGen", optional_override([](basis_encoder& self, bool mip_gen_flag) {
self.m_params.m_mip_gen = mip_gen_flag;
}))
// Set mipmap filter's scale factor
// default is 1, range is [.000125, 4.0]
.function("setMipScale", optional_override([](basis_encoder& self, float mip_scale) {
self.m_params.m_mip_scale = mip_scale;
}))
// Sets the mipmap filter to apply
// mip_filter must be < BASISU_MAX_RESAMPLER_FILTERS
// See the end of basisu_resample_filters.cpp: g_resample_filters[]
.function("setMipFilter", optional_override([](basis_encoder& self, uint32_t mip_filter) {
assert(mip_filter < g_num_resample_filters);
if (mip_filter < g_num_resample_filters)
self.m_params.m_mip_filter = g_resample_filters[mip_filter].name;
}))
// If true mipmap filtering occurs in sRGB space - this generally should match the perceptual parameter.
.function("setMipSRGB", optional_override([](basis_encoder& self, bool mip_srgb_flag) {
self.m_params.m_mip_srgb = mip_srgb_flag;
}))
// If true, the input is assumed to be a normal map, and the texels of each mipmap will be renormalized before encoding.
.function("setMipRenormalize", optional_override([](basis_encoder& self, bool mip_renormalize_flag) {
self.m_params.m_mip_renormalize = mip_renormalize_flag;
}))
// If true the source texture will be sampled using wrap addressing during mipmap generation, otherwise clamp.
.function("setMipWrapping", optional_override([](basis_encoder& self, bool mip_wrapping_flag) {
self.m_params.m_mip_wrapping = mip_wrapping_flag;
}))
// Sets the mipmap generator's smallest allowed dimension.
// default is 1, range is [1,16384]
.function("setMipSmallestDimension", optional_override([](basis_encoder& self, int mip_smallest_dimension) {
self.m_params.m_mip_smallest_dimension = mip_smallest_dimension;
}))
// Sets the .basis texture type.
// cBASISTexTypeVideoFrames changes the encoder into video mode.
// tex_type is enum basis_texture_type
// default is cBASISTexType2D
.function("setTexType", optional_override([](basis_encoder& self, uint32_t tex_type) {
assert(tex_type < cBASISTexTypeTotal);
self.m_params.m_tex_type = (basist::basis_texture_type)tex_type;
}))
.function("setUserData0", optional_override([](basis_encoder& self, uint32_t userdata0) {
self.m_params.m_userdata0 = userdata0;
}))
.function("setUserData1", optional_override([](basis_encoder& self, uint32_t userdata1) {
self.m_params.m_userdata1 = userdata1;
}))
// UASTC specific flags.
// Sets the UASTC encoding performance vs. quality tradeoff, and other lesser used UASTC encoder flags.
// This is a combination of flags. See cPackUASTCLevelDefault, etc.
.function("setPackUASTCFlags", optional_override([](basis_encoder& self, uint32_t pack_uastc_flags) {
assert((pack_uastc_flags & cPackUASTCLevelMask) >= cPackUASTCLevelFastest);
assert((pack_uastc_flags & cPackUASTCLevelMask) <= cPackUASTCLevelVerySlow);
self.m_params.m_pack_uastc_flags = pack_uastc_flags;
}))
// If true, the RDO post-processor will be applied to the encoded UASTC texture data.
.function("setRDOUASTC", optional_override([](basis_encoder& self, bool rdo_uastc) {
self.m_params.m_rdo_uastc = rdo_uastc;
}))
// Default is 1.0 range is [0.001, 10.0]
.function("setRDOUASTCQualityScalar", optional_override([](basis_encoder& self, float rdo_quality) {
self.m_params.m_rdo_uastc_quality_scalar = rdo_quality;
}))
// Default is BASISU_RDO_UASTC_DICT_SIZE_DEFAULT, range is [BASISU_RDO_UASTC_DICT_SIZE_MIN, BASISU_RDO_UASTC_DICT_SIZE_MAX]
.function("setRDOUASTCDictSize", optional_override([](basis_encoder& self, int dict_size) {
assert((dict_size >= BASISU_RDO_UASTC_DICT_SIZE_MIN) && (dict_size <= BASISU_RDO_UASTC_DICT_SIZE_MAX));
self.m_params.m_rdo_uastc_dict_size = dict_size;
}))
// Default is UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO, range is [01, 100.0]
.function("setRDOUASTCMaxAllowedRMSIncreaseRatio", optional_override([](basis_encoder& self, float rdo_uastc_max_allowed_rms_increase_ratio) {
self.m_params.m_rdo_uastc_max_allowed_rms_increase_ratio = rdo_uastc_max_allowed_rms_increase_ratio;
}))
// Default is UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH, range is [.01f, 100.0f]
.function("setRDOUASTCSkipBlockRMSThresh", optional_override([](basis_encoder& self, float rdo_uastc_skip_block_rms_thresh) {
self.m_params.m_rdo_uastc_skip_block_rms_thresh = rdo_uastc_skip_block_rms_thresh;
}))
// --- Low level options
// Disables selector RDO
.function("setNoSelectorRDO", optional_override([](basis_encoder& self, bool no_selector_rdo_flag) {
self.m_params.m_no_selector_rdo = no_selector_rdo_flag;
}))
// Disables endpoint RDO
.function("setNoEndpointRDO", optional_override([](basis_encoder& self, bool no_endpoint_rdo_flag) {
self.m_params.m_no_endpoint_rdo = no_endpoint_rdo_flag;
}))
// Display output PSNR statistics
.function("setComputeStats", optional_override([](basis_encoder& self, bool compute_stats_flag) {
self.m_params.m_compute_stats = compute_stats_flag;
}))
// Write output .PNG files for debugging
.function("setDebugImages", optional_override([](basis_encoder& self, bool debug_images_flag) {
self.m_params.m_debug_images = debug_images_flag;
}))
;
#endif // BASISU_SUPPORT_ENCODING
}