// basis_wrappers.cpp - Simple C-style wrappers to the C++ transcoder for WebGL use. #include "basisu_transcoder.h" #include #include using namespace emscripten; using namespace basist; static basist::etc1_global_selector_codebook *g_pGlobal_codebook; void basis_init() { basisu_transcoder_init(); if (!g_pGlobal_codebook) g_pGlobal_codebook = new basist::etc1_global_selector_codebook(g_global_selector_cb_size, g_global_selector_cb); } #define MAGIC 0xDEADBEE1 struct basis_file { int m_magic = 0; basisu_transcoder m_transcoder; std::vector m_file; basis_file(const emscripten::val& jsBuffer) : m_file([&]() { size_t byteLength = jsBuffer["byteLength"].as(); return std::vector(byteLength); }()), m_transcoder(g_pGlobal_codebook) { 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())) { m_file.clear(); } // Initialized after validation m_magic = MAGIC; } void close() { assert(m_magic == MAGIC); m_file.clear(); } uint32_t getHasAlpha() { assert(m_magic == MAGIC); if (m_magic != 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 == MAGIC); if (m_magic != 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 == MAGIC); if (m_magic != 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 == MAGIC); if (m_magic != 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 == MAGIC); if (m_magic != 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; } uint32_t getImageTranscodedSizeInBytes(uint32_t image_index, uint32_t level_index, uint32_t format) { assert(m_magic == MAGIC); if (m_magic != 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(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; } } uint32_t startTranscoding() { assert(m_magic == MAGIC); if (m_magic != 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 == MAGIC); if (m_magic != 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; std::vector dst_data; uint32_t flags = get_alpha_for_opaque_formats ? basisu_transcoder::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(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; } }; EMSCRIPTEN_BINDINGS(basis_transcoder) { function("initializeBasis", &basis_init); class_("BasisFile") .constructor() .function("close", optional_override([](basis_file& self) { return self.close(); })) .function("getHasAlpha", optional_override([](basis_file& self) { return self.getHasAlpha(); })) .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); })) .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(); })) .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); })) ; }