Skip to content

BasisU: Use KTX2 format and add import options to configure encoder #105080

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

Merged
merged 1 commit into from
May 5, 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
2 changes: 1 addition & 1 deletion core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ 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;
Vector<uint8_t> (*Image::png_packer)(const Ref<Image> &) = nullptr;
Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels) = nullptr;
Vector<uint8_t> (*Image::basis_universal_packer)(const Ref<Image> &, Image::UsedChannels, const BasisUniversalPackerParams &) = nullptr;

Ref<Image> (*Image::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
Expand Down
7 changes: 6 additions & 1 deletion core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ class Image : public Resource {
ALPHA_BLEND
};

struct BasisUniversalPackerParams {
int uastc_level = 0;
float rdo_quality_loss = 0;
};

// External saver function pointers.

static inline SavePNGFunc save_png_func = nullptr;
Expand Down Expand Up @@ -231,7 +236,7 @@ class Image : public Resource {
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);
static Vector<uint8_t> (*png_packer)(const Ref<Image> &p_image);
static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels);
static Vector<uint8_t> (*basis_universal_packer)(const Ref<Image> &p_image, UsedChannels p_channels, const BasisUniversalPackerParams &p_basisu_params);

static Ref<Image> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
Expand Down
10 changes: 10 additions & 0 deletions doc/classes/PortableCompressedTexture2D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@
Return whether the flag is overridden for all textures of this type.
</description>
</method>
<method name="set_basisu_compressor_params">
<return type="void" />
<param index="0" name="uastc_level" type="int" />
<param index="1" name="rdo_quality_loss" type="float" />
<description>
Sets the compressor parameters for Basis Universal compression. See also the settings in [ResourceImporterTexture].
[b]Note:[/b] This must be set before [method create_from_image] to take effect.
</description>
</method>
<method name="set_keep_all_compressed_buffers" qualifiers="static">
<return type="void" />
<param index="0" name="keep" type="bool" />
Expand All @@ -55,6 +64,7 @@
<member name="keep_compressed_buffer" type="bool" setter="set_keep_compressed_buffer" getter="is_keeping_compressed_buffer" default="false">
When running on the editor, this class will keep the source compressed data in memory. Otherwise, the source compressed data is lost after loading and the resource can't be re saved.
This flag allows to keep the compressed data in memory if you intend it to persist after loading.
[b]Note:[/b] This must be set before [method create_from_image] to take effect.
</member>
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" />
<member name="size_override" type="Vector2" setter="set_size_override" getter="get_size_override" default="Vector2(0, 0)">
Expand Down
10 changes: 10 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3257,6 +3257,16 @@
<member name="rendering/shading/overrides/force_vertex_shading" type="bool" setter="" getter="" default="false">
If [code]true[/code], forces vertex shading for all rendering. This can increase performance a lot, but also reduces quality immensely. Can be used to optimize performance on low-end mobile devices.
</member>
<member name="rendering/textures/basis_universal/rdo_dict_size" type="int" setter="" getter="" default="1024">
The dictionary size for Rate-Distortion Optimization (RDO) when importing textures as Basis Universal and when RDO is enabled, ranging from [code]64[/code] to [code]65536[/code]. Higher values reduce the file sizes further, but make encoding times significantly longer.
</member>
<member name="rendering/textures/basis_universal/zstd_supercompression" type="bool" setter="" getter="" default="true">
If [code]true[/code], enables Zstandard supercompression to reduce file size when importing textures as Basis Universal.
[b]Note:[/b] Basis Universal textures need to be compressed to gain the benefit of smaller file sizes, otherwise they are as large as VRAM-compressed textures.
</member>
<member name="rendering/textures/basis_universal/zstd_supercompression_level" type="int" setter="" getter="" default="6">
Specify the compression level for Basis Universal Zstandard supercompression, ranging from [code]1[/code] to [code]22[/code].
</member>
<member name="rendering/textures/canvas_textures/default_texture_filter" type="int" setter="" getter="" default="1">
The default texture filtering mode to use for [CanvasItem]s built-in texture. In shaders, this texture is accessed as [code]TEXTURE[/code].
[b]Note:[/b] For pixel art aesthetics, see also [member rendering/2d/snap/snap_2d_vertices_to_pixel] and [member rendering/2d/snap/snap_2d_transforms_to_pixel].
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/ResourceImporterLayeredTexture.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
[b]Basis Universal:[/b] Reduced quality, low memory usage, lowest size on disk, slow import. Only use for textures in 3D scenes, not for 2D elements.
See [url=$DOCS_URL/tutorials/assets_pipeline/importing_images.html#compress-mode]Compress mode[/url] in the manual for more details.
</member>
<member name="compress/rdo_quality_loss" type="float" setter="" getter="" default="0.0">
If greater than or equal to [code]0.01[/code], enables Rate-Distortion Optimization (RDO) to reduce file size. Higher values result in smaller file sizes but lower quality.
[b]Note:[/b] Enabling RDO makes encoding times significantly longer, especially when the image is large.
See also [member ProjectSettings.rendering/textures/basis_universal/rdo_dict_size] and [member ProjectSettings.rendering/textures/basis_universal/zstd_supercompression_level] if you want to reduce the file size further.
</member>
<member name="compress/uastc_level" type="int" setter="" getter="" default="0">
The UASTC encoding level. Higher values result in better quality but make encoding times longer.
</member>
<member name="mipmaps/generate" type="bool" setter="" getter="" default="true">
If [code]true[/code], smaller versions of the texture are generated on import. For example, a 64×64 texture will generate 6 mipmaps (32×32, 16×16, 8×8, 4×4, 2×2, 1×1). This has several benefits:
- Textures will not become grainy in the distance (in 3D), or if scaled down due to [Camera2D] zoom or [CanvasItem] scale (in 2D).
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/ResourceImporterTexture.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
When using a texture as normal map, only the red and green channels are required. Given regular texture compression algorithms produce artifacts that don't look that nice in normal maps, the RGTC compression format is the best fit for this data. Forcing this option to Enable will make Godot import the image as RGTC compressed. By default, it's set to Detect. This means that if the texture is ever detected to be used as a normal map, it will be changed to Enable and reimported automatically.
Note that RGTC compression affects the resulting normal map image. You will have to adjust custom shaders that use the normal map's blue channel to take this into account. Built-in material shaders already ignore the blue channel in a normal map (regardless of the actual normal map's contents).
</member>
<member name="compress/rdo_quality_loss" type="float" setter="" getter="" default="0.0">
If greater than or equal to [code]0.01[/code], enables Rate-Distortion Optimization (RDO) to reduce file size. Higher values result in smaller file sizes but lower quality.
[b]Note:[/b] Enabling RDO makes encoding times significantly longer, especially when the image is large.
See also [member ProjectSettings.rendering/textures/basis_universal/rdo_dict_size] and [member ProjectSettings.rendering/textures/basis_universal/zstd_supercompression_level] if you want to reduce the file size further.
</member>
<member name="compress/uastc_level" type="int" setter="" getter="" default="0">
The UASTC encoding level. Higher values result in better quality but make encoding times longer.
</member>
<member name="detect_3d/compress_to" type="int" setter="" getter="" default="1">
This changes the [member compress/mode] option that is used when a texture is detected as being used in 3D.
Changing this import option only has an effect if a texture is detected as being used in 3D. Changing this to [b]Disabled[/b] then reimporting will not change the existing compress mode on a texture (if it's detected to be used in 3D), but choosing [b]VRAM Compressed[/b] or [b]Basis Universal[/b] will.
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_property_name_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["pvs"] = "PVS";
capitalize_string_remaps["rcedit"] = "rcedit";
capitalize_string_remaps["rcodesign"] = "rcodesign";
capitalize_string_remaps["rdo"] = "RDO";
capitalize_string_remaps["rgb"] = "RGB";
capitalize_string_remaps["rid"] = "RID";
capitalize_string_remaps["rmb"] = "RMB";
Expand Down Expand Up @@ -289,6 +290,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["textfile"] = "TextFile";
capitalize_string_remaps["tls"] = "TLS";
capitalize_string_remaps["tv"] = "TV";
capitalize_string_remaps["uastc"] = "UASTC";
capitalize_string_remaps["ui"] = "UI";
capitalize_string_remaps["uri"] = "URI";
capitalize_string_remaps["url"] = "URL";
Expand Down
33 changes: 26 additions & 7 deletions editor/import/resource_importer_layered_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ bool ResourceImporterLayeredTexture::get_option_visibility(const String &p_path,
if ((p_option == "compress/high_quality" || p_option == "compress/hdr_compression") && p_options.has("compress/mode")) {
return int(p_options["compress/mode"]) == COMPRESS_VRAM_COMPRESSED;
}
if (p_option == "compress/uastc_level" || p_option == "compress/rdo_quality_loss") {
return int(p_options["compress/mode"]) == COMPRESS_BASIS_UNIVERSAL;
}

return true;
}

Expand All @@ -140,6 +144,11 @@ void ResourceImporterLayeredTexture::get_import_options(const String &p_path, Li
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Lossless,Lossy,VRAM Compressed,VRAM Uncompressed,Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/high_quality"), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.7));

Image::BasisUniversalPackerParams basisu_params;
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/uastc_level", PROPERTY_HINT_ENUM, "Fastest,Faster,Medium,Slower,Slowest"), basisu_params.uastc_level));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/rdo_quality_loss", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater"), basisu_params.rdo_quality_loss));

r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized,Normal Map (RG Channels)"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), true));
Expand All @@ -158,7 +167,7 @@ void ResourceImporterLayeredTexture::get_import_options(const String &p_path, Li
}
}

void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2) {
Vector<Ref<Image>> mipmap_images; //for 3D

if (mode == MODE_3D) {
Expand Down Expand Up @@ -278,11 +287,11 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
}

for (int i = 0; i < p_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
ResourceImporterTexture::save_to_ctex_format(f, p_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_basisu_params);
}

for (int i = 0; i < mipmap_images.size(); i++) {
ResourceImporterTexture::save_to_ctex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy);
ResourceImporterTexture::save_to_ctex_format(f, mipmap_images[i], ResourceImporterTexture::CompressMode(p_compress_mode), used_channels, p_vram_compression, p_lossy, p_basisu_params);
}
}

Expand Down Expand Up @@ -375,6 +384,12 @@ Error ResourceImporterLayeredTexture::import(ResourceUID::ID p_source_id, const
slices.push_back(slice);
}
}

const Image::BasisUniversalPackerParams basisu_params = {
p_options["compress/uastc_level"],
p_options["compress/rdo_quality_loss"],
};

Array formats_imported;
Ref<LayeredTextureImport> texture_import;
texture_import.instantiate();
Expand All @@ -392,6 +407,8 @@ Error ResourceImporterLayeredTexture::import(ResourceUID::ID p_source_id, const
texture_import->used_channels = used_channels;
texture_import->high_quality = high_quality;

texture_import->basisu_params = basisu_params;

_check_compress_ctex(p_source_file, texture_import);
if (r_metadata) {
Dictionary meta;
Expand Down Expand Up @@ -486,7 +503,8 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
ERR_FAIL_NULL(r_texture_import->csource);
if (r_texture_import->compress_mode != COMPRESS_VRAM_COMPRESSED) {
// Import normally.
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params,
Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
return;
}
// Must import in all formats, in order of priority (so platform chooses the best supported one. IE, etc2 over etc).
Expand Down Expand Up @@ -541,7 +559,8 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
}

if (use_uncompressed) {
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, COMPRESS_VRAM_UNCOMPRESSED, r_texture_import->lossy, Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, COMPRESS_VRAM_UNCOMPRESSED, r_texture_import->lossy, r_texture_import->basisu_params,
Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false);
} else {
if (can_s3tc_bptc) {
Image::CompressMode image_compress_mode;
Expand All @@ -553,7 +572,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
image_compress_mode = Image::COMPRESS_S3TC;
image_compress_format = "s3tc";
}
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
r_texture_import->platform_variants->push_back(image_compress_format);
}

Expand All @@ -567,7 +586,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
image_compress_mode = Image::COMPRESS_ETC2;
image_compress_format = "etc2";
}
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
_save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + image_compress_format + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, r_texture_import->basisu_params, image_compress_mode, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, true);
r_texture_import->platform_variants->push_back(image_compress_format);
}
}
Expand Down
5 changes: 4 additions & 1 deletion editor/import/resource_importer_layered_texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class LayeredTextureImport : public RefCounted {
Vector<Ref<Image>> *slices = nullptr;
int compress_mode = 0;
float lossy = 1.0;

Image::BasisUniversalPackerParams basisu_params;

int hdr_compression = 0;
bool mipmaps = true;
bool high_quality = false;
Expand Down Expand Up @@ -108,7 +111,7 @@ class ResourceImporterLayeredTexture : public ResourceImporter {
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;

void _save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);
void _save_tex(Vector<Ref<Image>> p_images, const String &p_to_path, int p_compress_mode, float p_lossy, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, Image::CompressSource p_csource, Image::UsedChannels used_channels, bool p_mipmaps, bool p_force_po2);

virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;

Expand Down
Loading