Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BasisU: Update to 1.50.0 and add HDR support #97582

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions editor/import/resource_importer_layered_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,6 @@ Error ResourceImporterLayeredTexture::import(const String &p_source_file, const
return err;
}

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

if (compress_mode == COMPRESS_VRAM_COMPRESSED) {
//if using video ram, optimize
if (channel_pack == 0) {
Expand Down
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
5 changes: 3 additions & 2 deletions thirdparty/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ 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:

- `encoder/` and `transcoder/` folders, minus `jpgd.{cpp,h}`
- `encoder/` and `transcoder/` folders, with the following files removed from `encoder`:
`jpgd.{cpp,h}`, `3rdparty/{qoi.h,tinydds.h,tinyexr.cpp,tinyexr.h}`
- `LICENSE`

Applied upstream PR https://github.com/BinomialLLC/basis_universal/pull/344 to
Expand Down
Loading
Loading