Improve etcpak image padding and clean up the code

This commit is contained in:
BlueCube3310 2024-03-15 22:40:22 +01:00
parent e343dbbcc1
commit 00f662c07f
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; return EtcpakType::ETCPAK_TYPE_ETC2;
case Image::USED_CHANNELS_RGBA: case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
default: default:
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
} }
@ -69,6 +70,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
return EtcpakType::ETCPAK_TYPE_DXT1; return EtcpakType::ETCPAK_TYPE_DXT1;
case Image::USED_CHANNELS_RGBA: case Image::USED_CHANNELS_RGBA:
return EtcpakType::ETCPAK_TYPE_DXT5; return EtcpakType::ETCPAK_TYPE_DXT5;
default: default:
return EtcpakType::ETCPAK_TYPE_DXT5; 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) { void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
EtcpakType type = _determine_etc_type(p_channels); _compress_etcpak(_determine_etc_type(p_channels), r_img);
_compress_etcpak(type, r_img);
} }
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) { void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
EtcpakType type = _determine_dxt_type(p_channels); _compress_etcpak(_determine_dxt_type(p_channels), r_img);
_compress_etcpak(type, 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(); uint64_t start_time = OS::get_singleton()->get_ticks_msec();
Image::Format img_format = r_img->get_format(); // The image is already compressed, return.
if (Image::is_format_compressed(img_format)) { if (r_img->is_compressed()) {
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
return; return;
} }
// Use RGBA8 to convert. // Convert to RGBA8 for compression.
if (img_format != Image::FORMAT_RGBA8) { r_img->convert(Image::FORMAT_RGBA8);
r_img->convert(Image::FORMAT_RGBA8);
}
// Determine output format based on Etcpak type. // Determine output format based on Etcpak type.
Image::Format target_format = Image::FORMAT_RGBA8; Image::Format target_format = Image::FORMAT_RGBA8;
if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
target_format = Image::FORMAT_ETC; switch (p_compress_type) {
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. case EtcpakType::ETCPAK_TYPE_ETC1:
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { target_format = Image::FORMAT_ETC;
target_format = Image::FORMAT_ETC2_RGB8; break;
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) { case EtcpakType::ETCPAK_TYPE_ETC2:
target_format = Image::FORMAT_ETC2_R11; 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. break;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
target_format = Image::FORMAT_ETC2_RG11; case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. target_format = Image::FORMAT_ETC2_RGBA8;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { break;
target_format = Image::FORMAT_ETC2_RA_AS_RG;
r_img->convert_rg_to_ra_rgba8(); case EtcpakType::ETCPAK_TYPE_ETC2_R:
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. target_format = Image::FORMAT_ETC2_R11;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { break;
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. case EtcpakType::ETCPAK_TYPE_ETC2_RG:
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { target_format = Image::FORMAT_ETC2_RG11;
target_format = Image::FORMAT_DXT1; break;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
target_format = Image::FORMAT_DXT5_RA_AS_RG; case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
r_img->convert_rg_to_ra_rgba8(); target_format = Image::FORMAT_ETC2_RA_AS_RG;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) { r_img->convert_rg_to_ra_rgba8();
target_format = Image::FORMAT_DXT5; break;
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
target_format = Image::FORMAT_RGTC_R; case EtcpakType::ETCPAK_TYPE_DXT1:
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) { target_format = Image::FORMAT_DXT1;
target_format = Image::FORMAT_RGTC_RG; break;
} else {
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); 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();
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. // Compress image data and (if required) mipmaps.
const bool has_mipmaps = r_img->has_mipmaps();
const bool mipmaps = r_img->has_mipmaps();
int width = r_img->get_width(); int width = r_img->get_width();
int height = r_img->get_height(); 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, 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. 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 (width % 4 != 0 || height % 4 != 0) {
if (next_width != width || next_height != height) { width = width <= 2 ? width : (width + 3) & ~3;
r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS); height = height <= 2 ? height : (height + 3) & ~3;
width = r_img->get_width();
height = r_img->get_height();
} }
// ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed.
// Multiple-of-4 should be guaranteed by above. // Multiple-of-4 should be guaranteed by above.
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels, // However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
// which are individually compressed Image objects that violate the above rule. // 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. // Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
const uint8_t *src_read = r_img->get_data().ptr(); // Create the buffer for compressed image data.
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);
Vector<uint8_t> dest_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(); 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; Vector<uint32_t> padded_src;
for (int i = 0; i < mip_count + 1; i++) { for (int i = 0; i < mip_count + 1; i++) {
// Get write mip metrics for target image. // Get write mip metrics for target image.
int orig_mip_w, orig_mip_h; int dest_mip_w, dest_mip_h;
int64_t mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_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);
// 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];
// Block size. Align stride to multiple of 4 (RGBA8). // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
int mip_w = (orig_mip_w + 3) & ~3; ERR_FAIL_COND(dest_mip_ofs % 8 != 0);
int mip_h = (orig_mip_h + 3) & ~3; uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs);
const uint32_t blocks = mip_w * mip_h / 16;
// 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. // Get mip data from source image for reading.
int64_t src_mip_ofs = r_img->get_mipmap_offset(i); int64_t src_mip_ofs, src_mip_size;
const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; 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. // Pad textures to nearest block by smearing.
if (mip_w != orig_mip_w || mip_h != orig_mip_h) { if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) {
padded_src.resize(mip_w * mip_h); // Reserve the buffer for padded image data.
padded_src.resize(dest_mip_w * dest_mip_h);
uint32_t *ptrw = padded_src.ptrw(); uint32_t *ptrw = padded_src.ptrw();
int x = 0, y = 0; int x = 0, y = 0;
for (y = 0; y < orig_mip_h; y++) { for (y = 0; y < src_mip_h; y++) {
for (x = 0; x < orig_mip_w; x++) { for (x = 0; x < src_mip_w; x++) {
ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x]; ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x];
} }
// First, smear in x. // First, smear in x.
for (; x < mip_w; x++) { for (; x < dest_mip_w; x++) {
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1]; ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1];
} }
} }
// Then, smear in y. // Then, smear in y.
for (; y < mip_h; y++) { for (; y < dest_mip_h; y++) {
for (x = 0; x < mip_w; x++) { for (x = 0; x < dest_mip_w; x++) {
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w]; 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. // Override the src_mip_read pointer to our temporary Vector.
src_mip_read = padded_src.ptr(); src_mip_read = padded_src.ptr();
} }
switch (p_compresstype) { switch (p_compress_type) {
case EtcpakType::ETCPAK_TYPE_ETC1: 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; break;
case EtcpakType::ETCPAK_TYPE_ETC2: 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; break;
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: 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; break;
case EtcpakType::ETCPAK_TYPE_ETC2_R: 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; break;
case EtcpakType::ETCPAK_TYPE_ETC2_RG: 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; break;
case EtcpakType::ETCPAK_TYPE_DXT1: 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; break;
case EtcpakType::ETCPAK_TYPE_DXT5: case EtcpakType::ETCPAK_TYPE_DXT5:
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: 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; break;
case EtcpakType::ETCPAK_TYPE_RGTC_R: 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; break;
case EtcpakType::ETCPAK_TYPE_RGTC_RG: 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; break;
default: default:
@ -276,7 +299,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
} }
// Replace original image with compressed one. // 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)); 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_etc2(Image *r_img, Image::UsedChannels p_channels);
void _compress_bc(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 #endif // IMAGE_COMPRESS_ETCPAK_H