Merge pull request #89567 from BlueCube3310/etcpak-cleanup

etcpak: Improve image padding and clean up the code
This commit is contained in:
Rémi Verschelde 2024-08-16 14:31:53 +02:00
commit 86df981cdc
No known key found for this signature in database
GPG Key ID: C3336907360768E1
2 changed files with 119 additions and 96 deletions

View File

@ -50,6 +50,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
default:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
}
@ -69,6 +70,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
return EtcpakType::ETCPAK_TYPE_DXT1;
case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_DXT5;
default:
return EtcpakType::ETCPAK_TYPE_DXT5;
}
@ -79,71 +81,86 @@ void _compress_etc1(Image *r_img) {
}
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
EtcpakType type = _determine_etc_type(p_channels);
_compress_etcpak(type, r_img);
_compress_etcpak(_determine_etc_type(p_channels), r_img);
}
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
EtcpakType type = _determine_dxt_type(p_channels);
_compress_etcpak(type, r_img);
_compress_etcpak(_determine_dxt_type(p_channels), r_img);
}
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
Image::Format img_format = r_img->get_format();
if (Image::is_format_compressed(img_format)) {
return; // Do not compress, already compressed.
}
if (img_format > Image::FORMAT_RGBA8) {
// TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually
// The image is already compressed, return.
if (r_img->is_compressed()) {
return;
}
// Use RGBA8 to convert.
if (img_format != Image::FORMAT_RGBA8) {
// Convert to RGBA8 for compression.
r_img->convert(Image::FORMAT_RGBA8);
}
// Determine output format based on Etcpak type.
Image::Format target_format = Image::FORMAT_RGBA8;
if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
switch (p_compress_type) {
case EtcpakType::ETCPAK_TYPE_ETC1:
target_format = Image::FORMAT_ETC;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
break;
case EtcpakType::ETCPAK_TYPE_ETC2:
target_format = Image::FORMAT_ETC2_RGB8;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) {
break;
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
target_format = Image::FORMAT_ETC2_RGBA8;
break;
case EtcpakType::ETCPAK_TYPE_ETC2_R:
target_format = Image::FORMAT_ETC2_R11;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
break;
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
target_format = Image::FORMAT_ETC2_RG11;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
break;
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
target_format = Image::FORMAT_ETC2_RA_AS_RG;
r_img->convert_rg_to_ra_rgba8();
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) {
target_format = Image::FORMAT_ETC2_RGBA8;
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
break;
case EtcpakType::ETCPAK_TYPE_DXT1:
target_format = Image::FORMAT_DXT1;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
break;
case EtcpakType::ETCPAK_TYPE_DXT5:
target_format = Image::FORMAT_DXT5;
break;
case EtcpakType::ETCPAK_TYPE_RGTC_R:
target_format = Image::FORMAT_RGTC_R;
break;
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
target_format = Image::FORMAT_RGTC_RG;
break;
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
target_format = Image::FORMAT_DXT5_RA_AS_RG;
r_img->convert_rg_to_ra_rgba8();
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
target_format = Image::FORMAT_DXT5;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
target_format = Image::FORMAT_RGTC_R;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
target_format = Image::FORMAT_RGTC_RG;
} else {
break;
default:
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
break;
}
// It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats.
if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) {
r_img->convert_rgba8_to_bgra8();
}
// 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();
@ -164,109 +181,115 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from,
the surface pitch, which can encompass additional padding beyond the physical surface size.
*/
int next_width = width <= 2 ? width : (width + 3) & ~3;
int next_height = height <= 2 ? height : (height + 3) & ~3;
if (next_width != width || next_height != height) {
r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS);
width = r_img->get_width();
height = r_img->get_height();
if (width % 4 != 0 || height % 4 != 0) {
width = width <= 2 ? width : (width + 3) & ~3;
height = height <= 2 ? height : (height + 3) & ~3;
}
// ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed.
// Multiple-of-4 should be guaranteed by above.
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
// which are individually compressed Image objects that violate the above rule.
// Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
const uint8_t *src_read = r_img->get_data().ptr();
print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
// Create the buffer for compressed image data.
Vector<uint8_t> dest_data;
dest_data.resize(dest_size);
dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps));
uint8_t *dest_write = dest_data.ptrw();
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const uint8_t *src_read = r_img->get_data().ptr();
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
Vector<uint32_t> padded_src;
for (int i = 0; i < mip_count + 1; i++) {
// Get write mip metrics for target image.
int orig_mip_w, orig_mip_h;
int64_t mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h);
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
ERR_FAIL_COND(mip_ofs % 8 != 0);
uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs];
int dest_mip_w, dest_mip_h;
int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h);
// Block size. Align stride to multiple of 4 (RGBA8).
int mip_w = (orig_mip_w + 3) & ~3;
int mip_h = (orig_mip_h + 3) & ~3;
const uint32_t blocks = mip_w * mip_h / 16;
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
ERR_FAIL_COND(dest_mip_ofs % 8 != 0);
uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs);
// Block size.
dest_mip_w = (dest_mip_w + 3) & ~3;
dest_mip_h = (dest_mip_h + 3) & ~3;
const uint32_t blocks = dest_mip_w * dest_mip_h / 16;
// Get mip data from source image for reading.
int64_t src_mip_ofs = r_img->get_mipmap_offset(i);
const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs];
int64_t src_mip_ofs, src_mip_size;
int src_mip_w, src_mip_h;
r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs);
// Pad textures to nearest block by smearing.
if (mip_w != orig_mip_w || mip_h != orig_mip_h) {
padded_src.resize(mip_w * mip_h);
if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) {
// Reserve the buffer for padded image data.
padded_src.resize(dest_mip_w * dest_mip_h);
uint32_t *ptrw = padded_src.ptrw();
int x = 0, y = 0;
for (y = 0; y < orig_mip_h; y++) {
for (x = 0; x < orig_mip_w; x++) {
ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x];
for (y = 0; y < src_mip_h; y++) {
for (x = 0; x < src_mip_w; x++) {
ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x];
}
// First, smear in x.
for (; x < mip_w; x++) {
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1];
for (; x < dest_mip_w; x++) {
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1];
}
}
// Then, smear in y.
for (; y < mip_h; y++) {
for (x = 0; x < mip_w; x++) {
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w];
for (; y < dest_mip_h; y++) {
for (x = 0; x < dest_mip_w; x++) {
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w];
}
}
// Override the src_mip_read pointer to our temporary Vector.
src_mip_read = padded_src.ptr();
}
switch (p_compresstype) {
switch (p_compress_type) {
case EtcpakType::ETCPAK_TYPE_ETC1:
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_ETC2:
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_R:
CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_DXT1:
CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_DXT5:
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
CompressDxt5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_RGTC_R:
CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
break;
default:
@ -276,7 +299,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
}
// 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("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}

View File

@ -51,6 +51,6 @@ void _compress_etc1(Image *r_img);
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels);
void _compress_bc(Image *r_img, Image::UsedChannels p_channels);
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img);
void _compress_etcpak(EtcpakType p_compress_type, Image *r_img);
#endif // IMAGE_COMPRESS_ETCPAK_H