| // 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 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<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); |
| }()) |
| { |
| 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<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); |
| }()) |
| { |
| 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<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_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<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_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<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_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<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 |
| |
| } |