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

astcenc: Misc improvements and optimizations #100848

Merged
merged 1 commit into from
Jan 3, 2025
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
160 changes: 77 additions & 83 deletions modules/astcenc/image_compress_astcenc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,41 +36,39 @@
#include <astcenc.h>

void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
const uint64_t start_time = OS::get_singleton()->get_ticks_msec();

// TODO: See how to handle lossy quality.

Image::Format img_format = r_img->get_format();
if (Image::is_format_compressed(img_format)) {
if (r_img->is_compressed()) {
return; // Do not compress, already compressed.
}

bool is_hdr = false;
if ((img_format >= Image::FORMAT_RH) && (img_format <= Image::FORMAT_RGBE9995)) {
is_hdr = true;
const Image::Format src_format = r_img->get_format();
const bool is_hdr = src_format >= Image::FORMAT_RF && src_format <= Image::FORMAT_RGBE9995;

if (src_format >= Image::FORMAT_RH && src_format <= Image::FORMAT_RGBAH) {
r_img->convert(Image::FORMAT_RGBAH);
} else if (src_format >= Image::FORMAT_RF && src_format <= Image::FORMAT_RGBE9995) {
r_img->convert(Image::FORMAT_RGBAF);
} else {
r_img->convert(Image::FORMAT_RGBA8);
}

// Determine encoder output format from our enum.
const astcenc_profile profile = is_hdr ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR;

Image::Format target_format = Image::FORMAT_RGBA8;
astcenc_profile profile = ASTCENC_PRF_LDR;
Image::Format target_format = Image::FORMAT_MAX;
unsigned int block_x = 4;
unsigned int block_y = 4;

if (p_format == Image::ASTCFormat::ASTC_FORMAT_4x4) {
if (is_hdr) {
target_format = Image::FORMAT_ASTC_4x4_HDR;
profile = ASTCENC_PRF_HDR;
} else {
target_format = Image::FORMAT_ASTC_4x4;
}
} else if (p_format == Image::ASTCFormat::ASTC_FORMAT_8x8) {
if (is_hdr) {
target_format = Image::FORMAT_ASTC_8x8_HDR;
profile = ASTCENC_PRF_HDR;
} else {
target_format = Image::FORMAT_ASTC_8x8;
}
Expand All @@ -79,8 +77,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
}

// Compress image data and (if required) mipmaps.

const bool mipmaps = r_img->has_mipmaps();
const bool has_mipmaps = r_img->has_mipmaps();
int width = r_img->get_width();
int height = r_img->get_height();
int required_width = (width % block_x) != 0 ? width + (block_x - (width % block_x)) : width;
Expand All @@ -93,11 +90,10 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
height = required_height;
}

print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), has_mipmaps ? ", with mipmaps" : ""));

// Initialize astcenc.

int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
const int64_t dest_size = Image::get_image_data_size(width, height, target_format, has_mipmaps);
Vector<uint8_t> dest_data;
dest_data.resize(dest_size);
uint8_t *dest_write = dest_data.ptrw();
Expand All @@ -113,42 +109,44 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));

// Context allocation.

astcenc_context *context;
const unsigned int thread_count = 1; // Godot compresses multiple images each on a thread, which is more efficient for large amount of images imported.
status = astcenc_context_alloc(&config, thread_count, &context);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));

Vector<uint8_t> image_data = r_img->get_data();
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const uint8_t *src_data = r_img->ptr();

int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
for (int i = 0; i < mip_count + 1; i++) {
int src_mip_w, src_mip_h;
int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
const int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
const uint8_t *mip_data = &src_data[src_ofs];

const uint8_t *slices = &image_data.ptr()[src_ofs];
const int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
uint8_t *dest_mip_write = &dest_write[dst_ofs];

int dst_mip_w, dst_mip_h;
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
if (unlikely(dst_ofs % 8 != 0)) {
astcenc_context_free(context);
ERR_FAIL_MSG("astcenc: Mip offset is not a multiple of 8.");
}
uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs];

// Compress image.

astcenc_image image;
image.dim_x = src_mip_w;
image.dim_y = src_mip_h;
image.dim_z = 1;
image.data_type = ASTCENC_TYPE_U8;
if (is_hdr) {

if (r_img->get_format() == Image::FORMAT_RGBA8) {
image.data_type = ASTCENC_TYPE_U8;
} else if (r_img->get_format() == Image::FORMAT_RGBAH) {
image.data_type = ASTCENC_TYPE_F16;
} else {
image.data_type = ASTCENC_TYPE_F32;
}
image.data = (void **)(&slices);

image.data = (void **)(&mip_data);

// Compute the number of ASTC blocks in each dimension.
unsigned int block_count_x = (src_mip_w + block_x - 1) / block_x;
Expand All @@ -160,56 +158,59 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
};

status = astcenc_compress_image(context, &image, &swizzle, dest_mip_write, comp_len, 0);

ERR_BREAK_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status)));

astcenc_compress_reset(context);
}

astcenc_context_free(context);

// Replace original image with compressed one.

r_img->set_data(width, height, mipmaps, target_format, dest_data);
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);

print_verbose(vformat("astcenc: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}

void _decompress_astc(Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
const uint64_t start_time = OS::get_singleton()->get_ticks_msec();

// Determine decompression parameters from image format.
const Image::Format src_format = r_img->get_format();

Image::Format img_format = r_img->get_format();
bool is_hdr = false;
unsigned int block_x = 0;
unsigned int block_y = 0;
if (img_format == Image::FORMAT_ASTC_4x4) {
block_x = 4;
block_y = 4;
is_hdr = false;
} else if (img_format == Image::FORMAT_ASTC_4x4_HDR) {
block_x = 4;
block_y = 4;
is_hdr = true;
} else if (img_format == Image::FORMAT_ASTC_8x8) {
block_x = 8;
block_y = 8;
is_hdr = false;
} else if (img_format == Image::FORMAT_ASTC_8x8_HDR) {
block_x = 8;
block_y = 8;
is_hdr = true;
} else {
ERR_FAIL_MSG("astcenc: Cannot decompress Image with a non-ASTC format.");

switch (src_format) {
case Image::FORMAT_ASTC_4x4: {
block_x = 4;
block_y = 4;
is_hdr = false;
} break;
case Image::FORMAT_ASTC_4x4_HDR: {
block_x = 4;
block_y = 4;
is_hdr = true;
} break;
case Image::FORMAT_ASTC_8x8: {
block_x = 8;
block_y = 8;
is_hdr = false;
} break;
case Image::FORMAT_ASTC_8x8_HDR: {
block_x = 8;
block_y = 8;
is_hdr = true;
} break;
default: {
ERR_FAIL_MSG(vformat("astcenc: Cannot decompress Image with a non-ASTC format: %s.", Image::get_format_name(src_format)));
} break;
}

// Initialize astcenc.
const astcenc_profile profile = is_hdr ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR;

astcenc_profile profile = ASTCENC_PRF_LDR;
if (is_hdr) {
profile = ASTCENC_PRF_HDR;
}
astcenc_config config;
const float quality = ASTCENC_PRE_MEDIUM;

Expand All @@ -218,76 +219,69 @@ void _decompress_astc(Image *r_img) {
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));

// Context allocation.

astcenc_context *context = nullptr;
const unsigned int thread_count = 1;

status = astcenc_context_alloc(&config, thread_count, &context);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));

Image::Format target_format = is_hdr ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8;
const Image::Format target_format = is_hdr ? Image::FORMAT_RGBAH : Image::FORMAT_RGBA8;

const bool mipmaps = r_img->has_mipmaps();
const bool has_mipmaps = r_img->has_mipmaps();
int width = r_img->get_width();
int height = r_img->get_height();
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);

const int64_t dest_size = Image::get_image_data_size(width, height, target_format, has_mipmaps);
Vector<uint8_t> dest_data;
dest_data.resize(dest_size);
uint8_t *dest_write = dest_data.ptrw();

// Decompress image.

Vector<uint8_t> image_data = r_img->get_data();
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const uint8_t *src_data = r_img->ptr();

for (int i = 0; i < mip_count + 1; i++) {
int src_mip_w, src_mip_h;
const int64_t src_ofs = Image::get_image_mipmap_offset(width, height, src_format, i);
const uint8_t *mip_data = &src_data[src_ofs];

int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
const uint8_t *src_data = &image_data.ptr()[src_ofs];
int64_t src_size;
if (i == mip_count) {
src_size = image_data.size() - src_ofs;
src_size = r_img->get_data_size() - src_ofs;
} else {
int auxw, auxh;
src_size = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i + 1, auxw, auxh) - src_ofs;
src_size = Image::get_image_mipmap_offset(width, height, src_format, i + 1) - src_ofs;
}

int dst_mip_w, dst_mip_h;
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);
const int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);

// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
ERR_FAIL_COND(dst_ofs % 8 != 0);
uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs];
uint8_t *dest_mip_write = &dest_write[dst_ofs];

astcenc_image image;
image.dim_x = dst_mip_w;
image.dim_y = dst_mip_h;
image.dim_z = 1;
image.data_type = ASTCENC_TYPE_U8;
if (is_hdr) {
target_format = Image::FORMAT_RGBAF;
image.data_type = ASTCENC_TYPE_F32;
}
image.data_type = is_hdr ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8;

image.data = (void **)(&dest_mip_write);

const astcenc_swizzle swizzle = {
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
};

status = astcenc_decompress_image(context, src_data, src_size, &image, &swizzle, 0);
ERR_BREAK_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status)));
ERR_BREAK_MSG(image.dim_z > 1,
"astcenc: ASTC decompression failed because this is a 3D texture, which is not supported.");
status = astcenc_decompress_image(context, mip_data, src_size, &image, &swizzle, 0);
ERR_BREAK_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status)));
ERR_BREAK_MSG(image.dim_z > 1, "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported.");

astcenc_compress_reset(context);
}

astcenc_context_free(context);

// Replace original image with compressed one.

r_img->set_data(width, height, mipmaps, target_format, dest_data);
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);

print_verbose(vformat("astcenc: Decompression took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}
22 changes: 16 additions & 6 deletions servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2185,10 +2185,15 @@ Ref<Image> TextureStorage::_validate_texture_format(const Ref<Image> &p_image, T
}
} else {
//not supported, reconvert
r_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
r_format.format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB;
image->decompress();
image->convert(Image::FORMAT_RGBA8);
if (p_image->get_format() == Image::FORMAT_ASTC_4x4) {
r_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
r_format.format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB;
image->convert(Image::FORMAT_RGBA8);
} else {
r_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
image->convert(Image::FORMAT_RGBAH);
}
}
r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
Expand All @@ -2205,10 +2210,15 @@ Ref<Image> TextureStorage::_validate_texture_format(const Ref<Image> &p_image, T
}
} else {
//not supported, reconvert
r_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
r_format.format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB;
image->decompress();
image->convert(Image::FORMAT_RGBA8);
if (p_image->get_format() == Image::FORMAT_ASTC_8x8) {
r_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
r_format.format_srgb = RD::DATA_FORMAT_R8G8B8A8_SRGB;
image->convert(Image::FORMAT_RGBA8);
} else {
r_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
image->convert(Image::FORMAT_RGBAH);
}
}
r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
Expand Down