Path: blob/master/modules/basis_universal/image_compress_basisu.cpp
20936 views
/**************************************************************************/1/* image_compress_basisu.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "image_compress_basisu.h"3132#include "core/config/project_settings.h"33#include "core/io/image.h"34#include "core/os/os.h"35#include "core/string/print_string.h"36#include "servers/rendering/rendering_server.h"3738GODOT_GCC_WARNING_PUSH39GODOT_GCC_WARNING_IGNORE("-Wenum-conversion")40GODOT_GCC_WARNING_IGNORE("-Wshadow")41GODOT_GCC_WARNING_IGNORE("-Wunused-value")4243#include <transcoder/basisu_transcoder.h>44#ifdef TOOLS_ENABLED45#include <encoder/basisu_comp.h>4647static Mutex init_mutex;48static bool initialized = false;49#endif5051GODOT_GCC_WARNING_POP5253void basis_universal_init() {54basist::basisu_transcoder_init();55}5657#ifdef TOOLS_ENABLED58template <typename T>59inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t> &r_mip_data_padded, int p_next_width, int p_next_height, int p_width, int p_height, int64_t p_size) {60// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.61const T *mip_src_data = reinterpret_cast<const T *>(p_image_mip_data);6263// Reserve space in the padded buffer.64r_mip_data_padded.resize(p_next_width * p_next_height * sizeof(T));65T *data_padded_ptr = reinterpret_cast<T *>(r_mip_data_padded.ptrw());6667// Pad mipmap to the nearest block by smearing.68int x = 0, y = 0;69for (y = 0; y < p_height; y++) {70for (x = 0; x < p_width; x++) {71data_padded_ptr[p_next_width * y + x] = mip_src_data[p_width * y + x];72}7374// First, smear in x.75for (; x < p_next_width; x++) {76data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - 1];77}78}7980// Then, smear in y.81for (; y < p_next_height; y++) {82for (x = 0; x < p_next_width; x++) {83data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - p_next_width];84}85}86}8788Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels, const Image::BasisUniversalPackerParams &p_basisu_params) {89init_mutex.lock();90if (!initialized) {91basisu::basisu_encoder_init();92initialized = true;93}94init_mutex.unlock();9596uint64_t start_time = OS::get_singleton()->get_ticks_msec();9798Ref<Image> image = p_image->duplicate();99bool is_hdr = false;100101if (image->get_format() <= Image::FORMAT_RGB565) {102image->convert(Image::FORMAT_RGBA8);103} else if (image->get_format() <= Image::FORMAT_RGBE9995) {104image->convert(Image::FORMAT_RGBAF);105is_hdr = true;106}107108int rdo_dict_size = GLOBAL_GET_CACHED(int, "rendering/textures/basis_universal/rdo_dict_size");109bool zstd_supercompression = GLOBAL_GET_CACHED(bool, "rendering/textures/basis_universal/zstd_supercompression");110int zstd_supercompression_level = GLOBAL_GET_CACHED(int, "rendering/textures/basis_universal/zstd_supercompression_level");111112basisu::basis_compressor_params params;113114params.m_uastc = true;115params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;116params.m_pack_uastc_ldr_4x4_flags |= p_basisu_params.uastc_level;117118params.m_rdo_uastc_ldr_4x4 = p_basisu_params.rdo_quality_loss >= 0.01;119params.m_rdo_uastc_ldr_4x4_quality_scalar = p_basisu_params.rdo_quality_loss;120params.m_rdo_uastc_ldr_4x4_dict_size = rdo_dict_size;121122params.m_create_ktx2_file = true;123params.m_ktx2_uastc_supercompression = zstd_supercompression ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;124params.m_ktx2_zstd_supercompression_level = zstd_supercompression_level;125126params.m_mip_fast = true;127params.m_multithreading = true;128params.m_check_for_alpha = false;129130if (!OS::get_singleton()->is_stdout_verbose()) {131params.m_print_stats = false;132params.m_compute_stats = false;133params.m_status_output = false;134}135136basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());137params.m_pJob_pool = &job_pool;138139BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;140141if (is_hdr) {142decompress_format = BASIS_DECOMPRESS_HDR_RGB;143params.m_hdr = true;144params.m_uastc_hdr_4x4_options.set_quality_level(p_basisu_params.uastc_level);145146} else {147switch (p_channels) {148case Image::USED_CHANNELS_L: {149decompress_format = BASIS_DECOMPRESS_RGB;150} break;151case Image::USED_CHANNELS_LA: {152params.m_force_alpha = true;153decompress_format = BASIS_DECOMPRESS_RGBA;154} break;155case Image::USED_CHANNELS_R: {156decompress_format = BASIS_DECOMPRESS_R;157} break;158case Image::USED_CHANNELS_RG: {159params.m_force_alpha = true;160image->convert_rg_to_ra_rgba8();161decompress_format = BASIS_DECOMPRESS_RG;162} break;163case Image::USED_CHANNELS_RGB: {164decompress_format = BASIS_DECOMPRESS_RGB;165} break;166case Image::USED_CHANNELS_RGBA: {167params.m_force_alpha = true;168decompress_format = BASIS_DECOMPRESS_RGBA;169} break;170}171}172173ERR_FAIL_COND_V(decompress_format == BASIS_DECOMPRESS_MAX, Vector<uint8_t>());174175// Copy the source image data with mipmaps into BasisU.176{177const int orig_width = image->get_width();178const int orig_height = image->get_height();179180bool is_res_div_4 = (orig_width % 4 == 0) && (orig_height % 4 == 0);181182// Image's resolution rounded up to the nearest values divisible by 4.183int next_width = orig_width <= 2 ? orig_width : (orig_width + 3) & ~3;184int next_height = orig_height <= 2 ? orig_height : (orig_height + 3) & ~3;185186Vector<uint8_t> image_data = image->get_data();187basisu::vector<basisu::image> basisu_mipmaps;188basisu::vector<basisu::imagef> basisu_mipmaps_hdr;189190// Buffer for storing padded mipmap data.191Vector<uint8_t> mip_data_padded;192193for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {194int64_t ofs, size;195int width, height;196image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);197198const uint8_t *image_mip_data = image_data.ptr() + ofs;199200// Pad the mipmap's data if its resolution isn't divisible by 4.201if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {202if (is_hdr) {203_basisu_pad_mipmap<BasisRGBAF>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);204} else {205_basisu_pad_mipmap<uint32_t>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);206}207208// Override the image_mip_data pointer with our temporary Vector.209image_mip_data = reinterpret_cast<const uint8_t *>(mip_data_padded.ptr());210211// Override the mipmap's properties.212width = next_width;213height = next_height;214size = mip_data_padded.size();215}216217// Get the next mipmap's resolution.218next_width /= 2;219next_height /= 2;220221// Copy the source mipmap's data to a BasisU image.222if (is_hdr) {223basisu::imagef basisu_image(width, height);224memcpy(reinterpret_cast<uint8_t *>(basisu_image.get_ptr()), image_mip_data, size);225226if (i == 0) {227params.m_source_images_hdr.push_back(basisu_image);228} else {229basisu_mipmaps_hdr.push_back(basisu_image);230}231232} else {233basisu::image basisu_image(width, height);234memcpy(basisu_image.get_ptr(), image_mip_data, size);235236if (i == 0) {237params.m_source_images.push_back(basisu_image);238} else {239basisu_mipmaps.push_back(basisu_image);240}241}242}243244if (is_hdr) {245params.m_source_mipmap_images_hdr.push_back(basisu_mipmaps_hdr);246} else {247params.m_source_mipmap_images.push_back(basisu_mipmaps);248}249}250251// Encode the image data.252basisu::basis_compressor compressor;253compressor.init(params);254255int basisu_err = compressor.process();256ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());257258const basisu::uint8_vec &basisu_encoded = compressor.get_output_ktx2_file();259260Vector<uint8_t> basisu_data;261basisu_data.resize(basisu_encoded.size() + 4);262uint8_t *basisu_data_ptr = basisu_data.ptrw();263264// Copy the encoded BasisU data into the output buffer.265*(uint32_t *)basisu_data_ptr = decompress_format | BASIS_DECOMPRESS_FLAG_KTX2;266memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());267268print_verbose(vformat("BasisU: Encoding a %dx%d image with %d mipmaps took %d ms.", p_image->get_width(), p_image->get_height(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));269270return basisu_data;271}272#endif // TOOLS_ENABLED273274Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {275uint64_t start_time = OS::get_singleton()->get_ticks_msec();276277Ref<Image> image;278ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");279280const uint8_t *src_ptr = p_data;281int src_size = p_size;282283basist::transcoder_texture_format basisu_format = basist::transcoder_texture_format::cTFTotalTextureFormats;284Image::Format image_format = Image::FORMAT_MAX;285286// Get supported compression formats.287bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");288bool astc_supported = RS::get_singleton()->has_os_feature("astc");289bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc");290bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");291bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");292bool astc_hdr_supported = RS::get_singleton()->has_os_feature("astc_hdr");293294bool needs_ra_rg_swap = false;295bool needs_rg_trim = false;296297uint32_t decompress_format = *(uint32_t *)(src_ptr);298bool is_ktx2 = decompress_format & BASIS_DECOMPRESS_FLAG_KTX2;299decompress_format &= ~BASIS_DECOMPRESS_FLAG_KTX2;300301switch (decompress_format) {302case BASIS_DECOMPRESS_R: {303if (rgtc_supported) {304basisu_format = basist::transcoder_texture_format::cTFBC4_R;305image_format = Image::FORMAT_RGTC_R;306} else if (s3tc_supported) {307basisu_format = basist::transcoder_texture_format::cTFBC1;308image_format = Image::FORMAT_DXT1;309} else if (etc2_supported) {310basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11;311image_format = Image::FORMAT_ETC2_R11;312} else {313// No supported VRAM compression formats, decompress.314basisu_format = basist::transcoder_texture_format::cTFRGBA32;315image_format = Image::FORMAT_RGBA8;316needs_rg_trim = true;317}318319} break;320case BASIS_DECOMPRESS_RG: {321if (rgtc_supported) {322basisu_format = basist::transcoder_texture_format::cTFBC5_RG;323image_format = Image::FORMAT_RGTC_RG;324} else if (s3tc_supported) {325basisu_format = basist::transcoder_texture_format::cTFBC3;326image_format = Image::FORMAT_DXT5_RA_AS_RG;327} else if (etc2_supported) {328basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11;329image_format = Image::FORMAT_ETC2_RG11;330} else {331// No supported VRAM compression formats, decompress.332basisu_format = basist::transcoder_texture_format::cTFRGBA32;333image_format = Image::FORMAT_RGBA8;334needs_ra_rg_swap = true;335needs_rg_trim = true;336}337338} break;339case BASIS_DECOMPRESS_RG_AS_RA: {340if (s3tc_supported) {341basisu_format = basist::transcoder_texture_format::cTFBC3;342image_format = Image::FORMAT_DXT5_RA_AS_RG;343} else if (etc2_supported) {344basisu_format = basist::transcoder_texture_format::cTFETC2;345image_format = Image::FORMAT_ETC2_RA_AS_RG;346} else {347// No supported VRAM compression formats, decompress.348basisu_format = basist::transcoder_texture_format::cTFRGBA32;349image_format = Image::FORMAT_RGBA8;350needs_ra_rg_swap = true;351needs_rg_trim = true;352}353354} break;355case BASIS_DECOMPRESS_RGB: {356if (bptc_supported) {357basisu_format = basist::transcoder_texture_format::cTFBC7_M6_OPAQUE_ONLY;358image_format = Image::FORMAT_BPTC_RGBA;359} else if (astc_supported) {360basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;361image_format = Image::FORMAT_ASTC_4x4;362} else if (s3tc_supported) {363basisu_format = basist::transcoder_texture_format::cTFBC1;364image_format = Image::FORMAT_DXT1;365} else if (etc2_supported) {366basisu_format = basist::transcoder_texture_format::cTFETC1;367image_format = Image::FORMAT_ETC2_RGB8;368} else {369// No supported VRAM compression formats, decompress.370basisu_format = basist::transcoder_texture_format::cTFRGBA32;371image_format = Image::FORMAT_RGBA8;372}373374} break;375case BASIS_DECOMPRESS_RGBA: {376if (bptc_supported) {377basisu_format = basist::transcoder_texture_format::cTFBC7_M5;378image_format = Image::FORMAT_BPTC_RGBA;379} else if (astc_supported) {380basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;381image_format = Image::FORMAT_ASTC_4x4;382} else if (s3tc_supported) {383basisu_format = basist::transcoder_texture_format::cTFBC3;384image_format = Image::FORMAT_DXT5;385} else if (etc2_supported) {386basisu_format = basist::transcoder_texture_format::cTFETC2;387image_format = Image::FORMAT_ETC2_RGBA8;388} else {389// No supported VRAM compression formats, decompress.390basisu_format = basist::transcoder_texture_format::cTFRGBA32;391image_format = Image::FORMAT_RGBA8;392}393394} break;395case BASIS_DECOMPRESS_HDR_RGB: {396if (bptc_supported) {397basisu_format = basist::transcoder_texture_format::cTFBC6H;398image_format = Image::FORMAT_BPTC_RGBFU;399} else if (astc_hdr_supported) {400basisu_format = basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA;401image_format = Image::FORMAT_ASTC_4x4_HDR;402} else {403// No supported VRAM compression formats, decompress.404basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;405image_format = Image::FORMAT_RGBE9995;406}407408} break;409default: {410ERR_FAIL_V(image);411} break;412}413414src_ptr += 4;415src_size -= 4;416417if (is_ktx2) {418basist::ktx2_transcoder transcoder;419ERR_FAIL_COND_V(!transcoder.init(src_ptr, src_size), image);420421transcoder.start_transcoding();422423// Create the buffer for transcoded/decompressed data.424Vector<uint8_t> out_data;425out_data.resize(Image::get_image_data_size(transcoder.get_width(), transcoder.get_height(), image_format, transcoder.get_levels() > 1));426427uint8_t *dst = out_data.ptrw();428memset(dst, 0, out_data.size());429430for (uint32_t i = 0; i < transcoder.get_levels(); i++) {431basist::ktx2_image_level_info basisu_level;432transcoder.get_image_level_info(basisu_level, i, 0, 0);433434uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;435int64_t ofs = Image::get_image_mipmap_offset(transcoder.get_width(), transcoder.get_height(), image_format, i);436437bool result = transcoder.transcode_image_level(i, 0, 0, dst + ofs, mip_block_or_pixel_count, basisu_format);438439if (!result) {440print_line(vformat("BasisUniversal cannot unpack level %d.", i));441break;442}443}444445image = Image::create_from_data(transcoder.get_width(), transcoder.get_height(), transcoder.get_levels() > 1, image_format, out_data);446} else {447basist::basisu_transcoder transcoder;448ERR_FAIL_COND_V(!transcoder.validate_header(src_ptr, src_size), image);449450transcoder.start_transcoding(src_ptr, src_size);451452basist::basisu_image_info basisu_info;453transcoder.get_image_info(src_ptr, src_size, basisu_info, 0);454455// Create the buffer for transcoded/decompressed data.456Vector<uint8_t> out_data;457out_data.resize(Image::get_image_data_size(basisu_info.m_width, basisu_info.m_height, image_format, basisu_info.m_total_levels > 1));458459uint8_t *dst = out_data.ptrw();460memset(dst, 0, out_data.size());461462for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) {463basist::basisu_image_level_info basisu_level;464transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i);465466uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;467int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i);468469bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format);470471if (!result) {472print_line(vformat("BasisUniversal cannot unpack level %d.", i));473break;474}475}476477image = Image::create_from_data(basisu_info.m_width, basisu_info.m_height, basisu_info.m_total_levels > 1, image_format, out_data);478}479480if (needs_ra_rg_swap) {481// Swap uncompressed RA-as-RG texture's color channels.482image->convert_ra_rgba8_to_rg();483}484485if (needs_rg_trim) {486// Remove unnecessary color channels from uncompressed textures.487if (decompress_format == BASIS_DECOMPRESS_R) {488image->convert(Image::FORMAT_R8);489} else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) {490image->convert(Image::FORMAT_RG8);491}492}493494print_verbose(vformat("BasisU: Transcoding a %dx%d image with %d mipmaps into %s took %d ms.",495image->get_width(), image->get_height(), image->get_mipmap_count(), Image::get_format_name(image_format), OS::get_singleton()->get_ticks_msec() - start_time));496497return image;498}499500Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) {501return basis_universal_unpacker_ptr(p_buffer.ptr(), p_buffer.size());502}503504505