Skip to content

Commit

Permalink
BasisU: Update to 1.50.0 and add HDR support
Browse files Browse the repository at this point in the history
  • Loading branch information
BlueCube3310 committed Oct 4, 2024
1 parent 5ccbf6e commit b3e2c54
Show file tree
Hide file tree
Showing 62 changed files with 17,244 additions and 918 deletions.
5 changes: 0 additions & 5 deletions editor/import/resource_importer_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,6 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String
}
}

if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) {
// Basis universal does not support float formats, fallback.
compress_mode = COMPRESS_VRAM_COMPRESSED;
}

bool detect_3d = int(p_options["detect_3d/compress_to"]) > 0;
bool detect_roughness = roughness == 0;
bool detect_normal = normal == 0;
Expand Down
4 changes: 4 additions & 0 deletions modules/basis_universal/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ thirdparty_obj = []
thirdparty_dir = "#thirdparty/basis_universal/"
# Sync list with upstream CMakeLists.txt
encoder_sources = [
"3rdparty/android_astc_decomp.cpp",
"basisu_astc_hdr_enc.cpp",
"basisu_backend.cpp",
"basisu_basis_file.cpp",
"basisu_bc7enc.cpp",
Expand Down Expand Up @@ -45,6 +47,8 @@ else:
if env["builtin_zstd"]:
env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"])

env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"])

if env.dev_build:
env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)])

Expand Down
206 changes: 137 additions & 69 deletions modules/basis_universal/image_compress_basisu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

#include "image_compress_basisu.h"

#include "core/os/os.h"
#include "core/string/print_string.h"
#include "servers/rendering_server.h"

#include <transcoder/basisu_transcoder.h>
Expand All @@ -46,9 +48,48 @@ void basis_universal_init() {
}

#ifdef TOOLS_ENABLED
template <typename T>
inline 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) {
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
const T *mip_src_data = reinterpret_cast<const T *>(p_image_mip_data);

// Reserve space in the padded buffer.
r_mip_data_padded.resize(p_next_width * p_next_height * sizeof(T));
T *data_padded_ptr = reinterpret_cast<T *>(r_mip_data_padded.ptrw());

// Pad mipmap to the nearest block by smearing.
int x = 0, y = 0;
for (y = 0; y < p_height; y++) {
for (x = 0; x < p_width; x++) {
data_padded_ptr[p_next_width * y + x] = mip_src_data[p_width * y + x];
}

// First, smear in x.
for (; x < p_next_width; x++) {
data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - 1];
}
}

// Then, smear in y.
for (; y < p_next_height; y++) {
for (x = 0; x < p_next_width; x++) {
data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - p_next_width];
}
}
}

Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();

Ref<Image> image = p_image->duplicate();
image->convert(Image::FORMAT_RGBA8);
bool is_hdr = false;

if (image->get_format() <= Image::FORMAT_RGB565) {
image->convert(Image::FORMAT_RGBA8);
} else if (image->get_format() <= Image::FORMAT_RGBE9995) {
image->convert(Image::FORMAT_RGBAF);
is_hdr = true;
}

basisu::basis_compressor_params params;

Expand All @@ -74,32 +115,42 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
params.m_pJob_pool = &job_pool;

BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG;
switch (p_channels) {
case Image::USED_CHANNELS_L: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_LA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_RGBA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;

if (is_hdr) {
decompress_format = BASIS_DECOMPRESS_HDR_RGB;
params.m_hdr = true;
params.m_uastc_hdr_options.set_quality_level(0);

} else {
switch (p_channels) {
case Image::USED_CHANNELS_L: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_LA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_RGBA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
}
}

ERR_FAIL_COND_V(decompress_format == BASIS_DECOMPRESS_MAX, Vector<uint8_t>());

// Copy the source image data with mipmaps into BasisU.
{
const int orig_width = image->get_width();
Expand All @@ -113,9 +164,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha

Vector<uint8_t> image_data = image->get_data();
basisu::vector<basisu::image> basisu_mipmaps;
basisu::vector<basisu::imagef> basisu_mipmaps_hdr;

// Buffer for storing padded mipmap data.
Vector<uint32_t> mip_data_padded;
Vector<uint8_t> mip_data_padded;

for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int64_t ofs, size;
Expand All @@ -126,31 +178,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha

// Pad the mipmap's data if its resolution isn't divisible by 4.
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);

// Reserve space in the padded buffer.
mip_data_padded.resize(next_width * next_height);
uint32_t *data_padded_ptr = mip_data_padded.ptrw();

// Pad mipmap to the nearest block by smearing.
int x = 0, y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
}

// First, smear in x.
for (; x < next_width; x++) {
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
}
}

// Then, smear in y.
for (; y < next_height; y++) {
for (x = 0; x < next_width; x++) {
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
}
if (is_hdr) {
_basisu_pad_mipmap<BasisRGBAF>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
} else {
_basisu_pad_mipmap<uint32_t>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
}

// Override the image_mip_data pointer with our temporary Vector.
Expand All @@ -159,52 +190,69 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
// Override the mipmap's properties.
width = next_width;
height = next_height;
size = mip_data_padded.size() * 4;
size = mip_data_padded.size();
}

// Get the next mipmap's resolution.
next_width /= 2;
next_height /= 2;

// Copy the source mipmap's data to a BasisU image.
basisu::image basisu_image(width, height);
memcpy(basisu_image.get_ptr(), image_mip_data, size);
if (is_hdr) {
basisu::imagef basisu_image(width, height);
memcpy(reinterpret_cast<uint8_t *>(basisu_image.get_ptr()), image_mip_data, size);

if (i == 0) {
params.m_source_images_hdr.push_back(basisu_image);
} else {
basisu_mipmaps_hdr.push_back(basisu_image);
}

if (i == 0) {
params.m_source_images.push_back(basisu_image);
} else {
basisu_mipmaps.push_back(basisu_image);
basisu::image basisu_image(width, height);
memcpy(basisu_image.get_ptr(), image_mip_data, size);

if (i == 0) {
params.m_source_images.push_back(basisu_image);
} else {
basisu_mipmaps.push_back(basisu_image);
}
}
}

params.m_source_mipmap_images.push_back(basisu_mipmaps);
if (is_hdr) {
params.m_source_mipmap_images_hdr.push_back(basisu_mipmaps_hdr);
} else {
params.m_source_mipmap_images.push_back(basisu_mipmaps);
}
}

// Encode the image data.
Vector<uint8_t> basisu_data;

basisu::basis_compressor compressor;
compressor.init(params);

int basisu_err = compressor.process();
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data);
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());

const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file();
basisu_data.resize(basisu_out.size() + 4);
const basisu::uint8_vec &basisu_encoded = compressor.get_output_basis_file();

// Copy the encoded data to the buffer.
{
uint8_t *wb = basisu_data.ptrw();
*(uint32_t *)wb = decompress_format;
Vector<uint8_t> basisu_data;
basisu_data.resize(basisu_encoded.size() + 4);
uint8_t *basisu_data_ptr = basisu_data.ptrw();

memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
}
// Copy the encoded BasisU data into the output buffer.
*(uint32_t *)basisu_data_ptr = decompress_format;
memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());

print_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));

return basisu_data;
}
#endif // TOOLS_ENABLED

Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();

Ref<Image> image;
ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");

Expand Down Expand Up @@ -320,6 +368,23 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}

} break;
case BASIS_DECOMPRESS_HDR_RGB: {
if (bptc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC6H;
image_format = Image::FORMAT_BPTC_RGBFU;
} else if (astc_supported) {
basisu_format = basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA;
image_format = Image::FORMAT_ASTC_4x4_HDR;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;
image_format = Image::FORMAT_RGBE9995;
}

} break;
default: {
ERR_FAIL_V(image);
} break;
}

src_ptr += 4;
Expand Down Expand Up @@ -371,6 +436,9 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
}
}

print_verbose(vformat("BasisU: Transcoding a %dx%d image with %d mipmaps into %s took %d ms.",
image->get_width(), image->get_height(), image->get_mipmap_count(), Image::get_format_name(image_format), OS::get_singleton()->get_ticks_msec() - start_time));

return image;
}

Expand Down
9 changes: 9 additions & 0 deletions modules/basis_universal/image_compress_basisu.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,20 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
BASIS_DECOMPRESS_R,
BASIS_DECOMPRESS_HDR_RGB,
BASIS_DECOMPRESS_MAX
};

void basis_universal_init();

#ifdef TOOLS_ENABLED
struct BasisRGBAF {
uint32_t r;
uint32_t g;
uint32_t b;
uint32_t a;
};

Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels);
#endif

Expand Down
2 changes: 1 addition & 1 deletion thirdparty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Files extracted from upstream source:
## basis_universal

- Upstream: https://github.com/BinomialLLC/basis_universal
- Version: 1.16.4 (900e40fb5d2502927360fe2f31762bdbb624455f, 2023)
- Version: 1.50.0 (051ad6d8a64bb95a79e8601c317055fd1782ad3e, 2024)
- License: Apache 2.0

Files extracted from upstream source:
Expand Down
Loading

0 comments on commit b3e2c54

Please sign in to comment.