Implement basic ASTC support

Implements basic ASTC support:
* Only 4x4 and 8x8 block sizes.
* Other block sizes are too complex to handle for Godot image compression handling. May be implemented sometime in the future.

The need for ASTC is mostly for the following use cases:
* Implement a high quality compression option for textures on mobile and M1 Apple hardware.
* For this, the 4x4 is sufficient, since it uses the same size as BPTC.

ASTC supports a lot of block sizes, but the benefit of supporting most of them is slim, while the implementation complexity in Godot is very high.
Supporting only 4x4 (and 8x8) solves the real problem, which is lack of a BPTC alternative on hardware where it's missing.

Note: This does not yet support encoding on import, an ASTC encoder will need to be added.
This commit is contained in:
Juan Linietsky 2022-09-05 21:11:34 +02:00
parent 00fa4e23e4
commit 71d21c7ccb
5 changed files with 153 additions and 16 deletions

View file

@ -173,6 +173,14 @@ int Image::get_format_pixel_size(Format p_format) {
return 1;
case FORMAT_DXT5_RA_AS_RG:
return 1;
case FORMAT_ASTC_4x4:
return 1;
case FORMAT_ASTC_4x4_HDR:
return 1;
case FORMAT_ASTC_8x8:
return 1;
case FORMAT_ASTC_8x8_HDR:
return 1;
case FORMAT_MAX: {
}
}
@ -213,7 +221,18 @@ void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) {
r_h = 4;
} break;
case FORMAT_ASTC_4x4:
case FORMAT_ASTC_4x4_HDR: {
r_w = 4;
r_h = 4;
} break;
case FORMAT_ASTC_8x8:
case FORMAT_ASTC_8x8_HDR: {
r_w = 8;
r_h = 8;
} break;
default: {
r_w = 1;
r_h = 1;
@ -222,7 +241,9 @@ void Image::get_format_min_pixel_size(Format p_format, int &r_w, int &r_h) {
}
int Image::get_format_pixel_rshift(Format p_format) {
if (p_format == FORMAT_DXT1 || p_format == FORMAT_RGTC_R || p_format == FORMAT_ETC || p_format == FORMAT_ETC2_R11 || p_format == FORMAT_ETC2_R11S || p_format == FORMAT_ETC2_RGB8 || p_format == FORMAT_ETC2_RGB8A1) {
if (p_format == FORMAT_ASTC_8x8) {
return 2;
} else if (p_format == FORMAT_DXT1 || p_format == FORMAT_RGTC_R || p_format == FORMAT_ETC || p_format == FORMAT_ETC2_R11 || p_format == FORMAT_ETC2_R11S || p_format == FORMAT_ETC2_RGB8 || p_format == FORMAT_ETC2_RGB8A1) {
return 1;
} else {
return 0;
@ -260,6 +281,14 @@ int Image::get_format_block_size(Format p_format) {
{
return 4;
}
case FORMAT_ASTC_4x4:
case FORMAT_ASTC_4x4_HDR: {
return 4;
}
case FORMAT_ASTC_8x8:
case FORMAT_ASTC_8x8_HDR: {
return 8;
}
default: {
}
}
@ -2515,19 +2544,21 @@ Error Image::decompress() {
_image_decompress_etc1(this);
} else if (format >= FORMAT_ETC2_R11 && format <= FORMAT_ETC2_RA_AS_RG && _image_decompress_etc2) {
_image_decompress_etc2(this);
} else if (format >= FORMAT_ASTC_4x4 && format <= FORMAT_ASTC_8x8_HDR && _image_decompress_astc) {
_image_decompress_astc(this);
} else {
return ERR_UNAVAILABLE;
}
return OK;
}
Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_lossy_quality) {
Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_lossy_quality, ASTCFormat p_astc_format) {
ERR_FAIL_INDEX_V_MSG(p_mode, COMPRESS_MAX, ERR_INVALID_PARAMETER, "Invalid compress mode.");
ERR_FAIL_INDEX_V_MSG(p_source, COMPRESS_SOURCE_MAX, ERR_INVALID_PARAMETER, "Invalid compress source.");
return compress_from_channels(p_mode, detect_used_channels(p_source), p_lossy_quality);
return compress_from_channels(p_mode, detect_used_channels(p_source), p_lossy_quality, p_astc_format);
}
Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality) {
Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality, ASTCFormat p_astc_format) {
switch (p_mode) {
case COMPRESS_S3TC: {
ERR_FAIL_COND_V(!_image_compress_bc_func, ERR_UNAVAILABLE);
@ -2545,6 +2576,10 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels
ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE);
_image_compress_bptc_func(this, p_lossy_quality, p_channels);
} break;
case COMPRESS_ASTC: {
ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE);
_image_compress_astc_func(this, p_lossy_quality, p_astc_format);
} break;
case COMPRESS_MAX: {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
} break;
@ -2895,10 +2930,12 @@ void (*Image::_image_compress_bc_func)(Image *, float, Image::UsedChannels) = nu
void (*Image::_image_compress_bptc_func)(Image *, float, Image::UsedChannels) = nullptr;
void (*Image::_image_compress_etc1_func)(Image *, float) = nullptr;
void (*Image::_image_compress_etc2_func)(Image *, float, Image::UsedChannels) = nullptr;
void (*Image::_image_compress_astc_func)(Image *, float, Image::ASTCFormat) = nullptr;
void (*Image::_image_decompress_bc)(Image *) = nullptr;
void (*Image::_image_decompress_bptc)(Image *) = nullptr;
void (*Image::_image_decompress_etc1)(Image *) = nullptr;
void (*Image::_image_decompress_etc2)(Image *) = nullptr;
void (*Image::_image_decompress_astc)(Image *) = nullptr;
Vector<uint8_t> (*Image::webp_lossy_packer)(const Ref<Image> &, float) = nullptr;
Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = nullptr;
@ -3314,8 +3351,8 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_invisible"), &Image::is_invisible);
ClassDB::bind_method(D_METHOD("detect_used_channels", "source"), &Image::detect_used_channels, DEFVAL(COMPRESS_SOURCE_GENERIC));
ClassDB::bind_method(D_METHOD("compress", "mode", "source", "lossy_quality"), &Image::compress, DEFVAL(COMPRESS_SOURCE_GENERIC), DEFVAL(0.7));
ClassDB::bind_method(D_METHOD("compress_from_channels", "mode", "channels", "lossy_quality"), &Image::compress_from_channels, DEFVAL(0.7));
ClassDB::bind_method(D_METHOD("compress", "mode", "source", "lossy_quality", "astc_format"), &Image::compress, DEFVAL(COMPRESS_SOURCE_GENERIC), DEFVAL(0.7), DEFVAL(ASTC_FORMAT_4x4));
ClassDB::bind_method(D_METHOD("compress_from_channels", "mode", "channels", "lossy_quality", "astc_format"), &Image::compress_from_channels, DEFVAL(0.7), DEFVAL(ASTC_FORMAT_4x4));
ClassDB::bind_method(D_METHOD("decompress"), &Image::decompress);
ClassDB::bind_method(D_METHOD("is_compressed"), &Image::is_compressed);
@ -3399,6 +3436,10 @@ void Image::_bind_methods() {
BIND_ENUM_CONSTANT(FORMAT_ETC2_RGB8A1);
BIND_ENUM_CONSTANT(FORMAT_ETC2_RA_AS_RG);
BIND_ENUM_CONSTANT(FORMAT_DXT5_RA_AS_RG);
BIND_ENUM_CONSTANT(FORMAT_ASTC_4x4);
BIND_ENUM_CONSTANT(FORMAT_ASTC_4x4_HDR);
BIND_ENUM_CONSTANT(FORMAT_ASTC_8x8);
BIND_ENUM_CONSTANT(FORMAT_ASTC_8x8_HDR);
BIND_ENUM_CONSTANT(FORMAT_MAX);
BIND_ENUM_CONSTANT(INTERPOLATE_NEAREST);
@ -3426,6 +3467,9 @@ void Image::_bind_methods() {
BIND_ENUM_CONSTANT(COMPRESS_SOURCE_GENERIC);
BIND_ENUM_CONSTANT(COMPRESS_SOURCE_SRGB);
BIND_ENUM_CONSTANT(COMPRESS_SOURCE_NORMAL);
BIND_ENUM_CONSTANT(ASTC_FORMAT_4x4);
BIND_ENUM_CONSTANT(ASTC_FORMAT_8x8);
}
void Image::set_compress_bc_func(void (*p_compress_func)(Image *, float, UsedChannels)) {

View file

@ -109,6 +109,10 @@ public:
FORMAT_ETC2_RGB8A1,
FORMAT_ETC2_RA_AS_RG, //used to make basis universal happy
FORMAT_DXT5_RA_AS_RG, //used to make basis universal happy
FORMAT_ASTC_4x4,
FORMAT_ASTC_4x4_HDR,
FORMAT_ASTC_8x8,
FORMAT_ASTC_8x8_HDR,
FORMAT_MAX
};
@ -134,6 +138,11 @@ public:
};
//some functions provided by something else
enum ASTCFormat {
ASTC_FORMAT_4x4,
ASTC_FORMAT_8x8,
};
static ImageMemLoadFunc _png_mem_loader_func;
static ImageMemLoadFunc _jpg_mem_loader_func;
static ImageMemLoadFunc _webp_mem_loader_func;
@ -144,11 +153,13 @@ public:
static void (*_image_compress_bptc_func)(Image *, float p_lossy_quality, UsedChannels p_channels);
static void (*_image_compress_etc1_func)(Image *, float);
static void (*_image_compress_etc2_func)(Image *, float, UsedChannels p_channels);
static void (*_image_compress_astc_func)(Image *, float, ASTCFormat p_format);
static void (*_image_decompress_bc)(Image *);
static void (*_image_decompress_bptc)(Image *);
static void (*_image_decompress_etc1)(Image *);
static void (*_image_decompress_etc2)(Image *);
static void (*_image_decompress_astc)(Image *);
static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
static Vector<uint8_t> (*webp_lossless_packer)(const Ref<Image> &p_image);
@ -351,6 +362,7 @@ public:
COMPRESS_ETC,
COMPRESS_ETC2,
COMPRESS_BPTC,
COMPRESS_ASTC,
COMPRESS_MAX,
};
enum CompressSource {
@ -360,8 +372,8 @@ public:
COMPRESS_SOURCE_MAX,
};
Error compress(CompressMode p_mode, CompressSource p_source = COMPRESS_SOURCE_GENERIC, float p_lossy_quality = 0.7);
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality = 0.7);
Error compress(CompressMode p_mode, CompressSource p_source = COMPRESS_SOURCE_GENERIC, float p_lossy_quality = 0.7, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error compress_from_channels(CompressMode p_mode, UsedChannels p_channels, float p_lossy_quality = 0.7, ASTCFormat p_astc_format = ASTC_FORMAT_4x4);
Error decompress();
bool is_compressed() const;
@ -432,5 +444,6 @@ VARIANT_ENUM_CAST(Image::CompressSource)
VARIANT_ENUM_CAST(Image::UsedChannels)
VARIANT_ENUM_CAST(Image::AlphaMode)
VARIANT_ENUM_CAST(Image::RoughnessChannel)
VARIANT_ENUM_CAST(Image::ASTCFormat)
#endif // IMAGE_H

View file

@ -76,8 +76,12 @@
<param index="0" name="mode" type="int" enum="Image.CompressMode" />
<param index="1" name="source" type="int" enum="Image.CompressSource" default="0" />
<param index="2" name="lossy_quality" type="float" default="0.7" />
<param index="3" name="astc_format" type="int" enum="Image.ASTCFormat" default="0" />
<description>
Compresses the image to use less memory. Can not directly access pixel data while the image is compressed. Returns error if the chosen compression mode is not available. See [enum CompressMode] and [enum CompressSource] constants.
Compresses the image to use less memory. Can not directly access pixel data while the image is compressed. Returns error if the chosen compression mode is not available.
The [param mode] parameter helps to pick the best compression method for DXT and ETC2 formats. It is ignored for ASTC compression.
The [param lossy_quality] parameter is optional for compressors that support it.
For ASTC compression, the [param astc_format] parameter must be supplied.
</description>
</method>
<method name="compress_from_channels">
@ -85,7 +89,12 @@
<param index="0" name="mode" type="int" enum="Image.CompressMode" />
<param index="1" name="channels" type="int" enum="Image.UsedChannels" />
<param index="2" name="lossy_quality" type="float" default="0.7" />
<param index="3" name="astc_format" type="int" enum="Image.ASTCFormat" default="0" />
<description>
Compresses the image to use less memory. Can not directly access pixel data while the image is compressed. Returns error if the chosen compression mode is not available.
This is an alternative to [method compress] that lets the user supply the channels used in order for the compressor to pick the best DXT and ETC2 formats. For other formats (non DXT or ETC2), this argument is ignored.
The [param lossy_quality] parameter is optional for compressors that support it.
For ASTC compression, the [param astc_format] parameter must be supplied.
</description>
</method>
<method name="compute_image_metrics">
@ -646,7 +655,19 @@
</constant>
<constant name="FORMAT_DXT5_RA_AS_RG" value="34" enum="Format">
</constant>
<constant name="FORMAT_MAX" value="35" enum="Format">
<constant name="FORMAT_ASTC_4x4" value="35" enum="Format">
[url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texutre Compression[/url]. This implements the 4x4 (high quality) mode.
</constant>
<constant name="FORMAT_ASTC_4x4_HDR" value="36" enum="Format">
Same format as [constant FORMAT_ASTC_4x4], but with the hint to let the GPU know it is used for HDR.
</constant>
<constant name="FORMAT_ASTC_8x8" value="37" enum="Format">
[url=https://en.wikipedia.org/wiki/Adaptive_scalable_texture_compression]Adaptive Scalable Texutre Compression[/url]. This implements the 8x8 (low quality) mode.
</constant>
<constant name="FORMAT_ASTC_8x8_HDR" value="38" enum="Format">
Same format as [constant FORMAT_ASTC_8x8], but with the hint to let the GPU know it is used for HDR.
</constant>
<constant name="FORMAT_MAX" value="39" enum="Format">
Represents the size of the [enum Format] enum.
</constant>
<constant name="INTERPOLATE_NEAREST" value="0" enum="Interpolation">
@ -710,5 +731,11 @@
<constant name="COMPRESS_SOURCE_NORMAL" value="2" enum="CompressSource">
Source texture (before compression) is a normal texture (e.g. it can be compressed into two channels).
</constant>
<constant name="ASTC_FORMAT_4x4" value="0" enum="ASTCFormat">
Hint to indicate that the high quality 4x4 ASTC compression format should be used.
</constant>
<constant name="ASTC_FORMAT_8x8" value="1" enum="ASTCFormat">
Hint to indicate that the low quality 8x8 ASTC compression format should be used.
</constant>
</constants>
</class>

View file

@ -996,8 +996,11 @@ void RenderingDeviceVulkan::get_compressed_image_format_block_dimensions(DataFor
case DATA_FORMAT_EAC_R11G11_UNORM_BLOCK:
case DATA_FORMAT_EAC_R11G11_SNORM_BLOCK:
case DATA_FORMAT_ASTC_4x4_UNORM_BLOCK: // Again, not sure about astc.
case DATA_FORMAT_ASTC_4x4_SRGB_BLOCK:
case DATA_FORMAT_ASTC_5x4_UNORM_BLOCK:
case DATA_FORMAT_ASTC_4x4_SRGB_BLOCK: {
r_w = 4;
r_h = 4;
} break;
case DATA_FORMAT_ASTC_5x4_UNORM_BLOCK: // Unsupported
case DATA_FORMAT_ASTC_5x4_SRGB_BLOCK:
case DATA_FORMAT_ASTC_5x5_UNORM_BLOCK:
case DATA_FORMAT_ASTC_5x5_SRGB_BLOCK:
@ -1008,10 +1011,16 @@ void RenderingDeviceVulkan::get_compressed_image_format_block_dimensions(DataFor
case DATA_FORMAT_ASTC_8x5_UNORM_BLOCK:
case DATA_FORMAT_ASTC_8x5_SRGB_BLOCK:
case DATA_FORMAT_ASTC_8x6_UNORM_BLOCK:
case DATA_FORMAT_ASTC_8x6_SRGB_BLOCK:
case DATA_FORMAT_ASTC_8x6_SRGB_BLOCK: {
r_w = 4;
r_h = 4;
} break;
case DATA_FORMAT_ASTC_8x8_UNORM_BLOCK:
case DATA_FORMAT_ASTC_8x8_SRGB_BLOCK:
case DATA_FORMAT_ASTC_10x5_UNORM_BLOCK:
case DATA_FORMAT_ASTC_8x8_SRGB_BLOCK: {
r_w = 8;
r_h = 8;
} break;
case DATA_FORMAT_ASTC_10x5_UNORM_BLOCK: // Unsupported
case DATA_FORMAT_ASTC_10x5_SRGB_BLOCK:
case DATA_FORMAT_ASTC_10x6_UNORM_BLOCK:
case DATA_FORMAT_ASTC_10x6_SRGB_BLOCK:
@ -1101,7 +1110,7 @@ uint32_t RenderingDeviceVulkan::get_compressed_image_format_block_byte_size(Data
case DATA_FORMAT_ASTC_12x10_SRGB_BLOCK:
case DATA_FORMAT_ASTC_12x12_UNORM_BLOCK:
case DATA_FORMAT_ASTC_12x12_SRGB_BLOCK:
return 8; // Wrong.
return 16;
default: {
}
}
@ -1123,6 +1132,10 @@ uint32_t RenderingDeviceVulkan::get_compressed_image_format_pixel_rshift(DataFor
case DATA_FORMAT_EAC_R11_UNORM_BLOCK:
case DATA_FORMAT_EAC_R11_SNORM_BLOCK:
return 1;
case DATA_FORMAT_ASTC_8x8_SRGB_BLOCK:
case DATA_FORMAT_ASTC_8x8_UNORM_BLOCK: {
return 2;
}
default: {
}
}

View file

@ -1740,6 +1740,46 @@ Ref<Image> TextureStorage::_validate_texture_format(const Ref<Image> &p_image, T
r_format.swizzle_b = RD::TEXTURE_SWIZZLE_ZERO;
r_format.swizzle_a = RD::TEXTURE_SWIZZLE_ONE;
} break;
case Image::FORMAT_ASTC_4x4:
case Image::FORMAT_ASTC_4x4_HDR: {
if (RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_ASTC_4x4_UNORM_BLOCK, RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT)) {
r_format.format = RD::DATA_FORMAT_ASTC_4x4_UNORM_BLOCK;
if (p_image->get_format() == Image::FORMAT_ASTC_4x4) {
r_format.format_srgb = RD::DATA_FORMAT_ASTC_4x4_SRGB_BLOCK;
}
} 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);
}
r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
} break; // astc 4x4
case Image::FORMAT_ASTC_8x8:
case Image::FORMAT_ASTC_8x8_HDR: {
if (RD::get_singleton()->texture_is_format_supported_for_usage(RD::DATA_FORMAT_ASTC_8x8_UNORM_BLOCK, RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT)) {
r_format.format = RD::DATA_FORMAT_ASTC_8x8_UNORM_BLOCK;
if (p_image->get_format() == Image::FORMAT_ASTC_8x8) {
r_format.format_srgb = RD::DATA_FORMAT_ASTC_8x8_SRGB_BLOCK;
}
} 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);
}
r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
} break; // astc 8x8
default: {
}