// 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 #include #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 bool g_basis_initialized_flag; void basis_init() { #if BASISU_DEBUG_PRINTF printf("basis_init()\n"); #endif if (g_basis_initialized_flag) return; #if BASISU_SUPPORT_ENCODING basisu_encoder_init(); #endif basisu_transcoder_init(); g_basis_initialized_flag = true; } static void copy_from_jsbuffer(const emscripten::val& srcBuffer, basisu::vector& dstVec) { unsigned int length = srcBuffer["length"].as(); dstVec.resize(length); emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"]; emscripten::val memoryView = srcBuffer["constructor"].new_(memory, reinterpret_cast(dstVec.data()), length); // Copy the bytes from the Javascript buffer. memoryView.call("set", srcBuffer); } static bool copy_to_jsbuffer(const emscripten::val& dstBuffer, const basisu::vector& 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(); 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(srcVec.data()), srcVec.size()); // Copy the bytes into the Javascript buffer. dstBuffer.call("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 m_file; basis_file(const emscripten::val& jsBuffer) : m_file([&]() { size_t byteLength = jsBuffer["byteLength"].as(); return basisu::vector(byteLength); }()) { if (!g_basis_initialized_flag) { #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(); emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"]; emscripten::val memoryView = jsBuffer["constructor"].new_(memory, reinterpret_cast(m_file.data()), length); memoryView.call("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(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(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(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 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(format), flags); } emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"]; emscripten::val memoryView = emscripten::val::global("Uint8Array").new_(memory, reinterpret_cast(dst_data.data()), dst_data.size()); dst.call("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 m_file; bool m_is_valid = false; ktx2_file(const emscripten::val& jsBuffer) : m_file([&]() { size_t byteLength = jsBuffer["byteLength"].as(); return basisu::vector(byteLength); }()) { if (!g_basis_initialized_flag) { #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(); emscripten::val memory = emscripten::val::module_property("HEAP8")["buffer"]; emscripten::val memoryView = jsBuffer["constructor"].new_(memory, reinterpret_cast(m_file.data()), length); memoryView.call("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(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(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 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(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(dst_data.data()), dst_data.size()); dst.call("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 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_basis_initialized_flag) { #if BASISU_DEBUG_PRINTF printf("basis_encoder::encode: Must call basis_init() first!\n"); #endif assert(0); return 0; } // 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 ¶ms = 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; 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() { } bool decode_palettes(uint32_t num_endpoints, const emscripten::val& endpoint_data, uint32_t num_selectors, const emscripten::val& selector_data) { basisu::vector 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 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_basis_initialized_flag) { #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 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(); if (!output_blocks_len) { #if BASISU_DEBUG_PRINTF printf("transcode_etc1s_image: output_blocks is empty\n"); #endif assert(0); return false; } basisu::vector 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(target_format_int); if (!g_basis_initialized_flag) { #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 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(); 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()); 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 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_tex_fmt)); } bool format_has_alpha(uint32_t transcoder_tex_fmt) { return basis_transcoder_format_has_alpha(static_cast(transcoder_tex_fmt)); } bool format_is_uncompressed(uint32_t transcoder_tex_fmt) { return basis_transcoder_format_is_uncompressed(static_cast(transcoder_tex_fmt)); } bool is_format_supported(uint32_t transcoder_tex_fmt) { return basis_is_format_supported(static_cast(transcoder_tex_fmt)); } uint32_t get_format_block_width(uint32_t transcoder_tex_fmt) { return basis_get_block_width(static_cast(transcoder_tex_fmt)); } uint32_t get_format_block_height(uint32_t transcoder_tex_fmt) { return basis_get_block_height(static_cast(transcoder_tex_fmt)); } EMSCRIPTEN_BINDINGS(basis_codec) { function("initializeBasis", &basis_init); // Expose BasisFileDesc structure value_object("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("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("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") .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") .value("cBASISTexType2D", cBASISTexType2D) .value("cBASISTexType2DArray", cBASISTexType2DArray) .value("cBASISTexTypeCubemapArray", cBASISTexTypeCubemapArray) .value("cBASISTexTypeVideoFrames", cBASISTexTypeVideoFrames) .value("cBASISTexTypeVolume", cBASISTexTypeVolume) ; // Expose enum basis_tex_format enum_("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_("BasisFile") .constructor() .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") .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_("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") .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") .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") .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("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("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_("KTX2File") .constructor() .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_("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 }