2
0
Эх сурвалжийг харах

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

LuoZhihao 2 сар өмнө
parent
commit
237597b01f

+ 1 - 1
core/io/image.cpp

@@ -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_lossy_packer)(const Ref<Image> &, float) = nullptr;
 Vector<uint8_t> (*Image::webp_lossless_packer)(const Ref<Image> &) = 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::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::webp_unpacker)(const Vector<uint8_t> &) = nullptr;
 Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;
 Ref<Image> (*Image::png_unpacker)(const Vector<uint8_t> &) = nullptr;

+ 6 - 1
core/io/image.h

@@ -182,6 +182,11 @@ public:
 		ALPHA_BLEND
 		ALPHA_BLEND
 	};
 	};
 
 
+	struct BasisUniversalPackerParams {
+		int uastc_level = 0;
+		float rdo_quality_loss = 0;
+	};
+
 	// External saver function pointers.
 	// External saver function pointers.
 
 
 	static inline SavePNGFunc save_png_func = nullptr;
 	static inline SavePNGFunc save_png_func = nullptr;
@@ -231,7 +236,7 @@ public:
 	static Vector<uint8_t> (*webp_lossy_packer)(const Ref<Image> &p_image, float p_quality);
 	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> (*webp_lossless_packer)(const Ref<Image> &p_image);
 	static Vector<uint8_t> (*png_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> (*webp_unpacker)(const Vector<uint8_t> &p_buffer);
 	static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);
 	static Ref<Image> (*png_unpacker)(const Vector<uint8_t> &p_buffer);

+ 10 - 0
doc/classes/PortableCompressedTexture2D.xml

@@ -43,6 +43,15 @@
 				Return whether the flag is overridden for all textures of this type.
 				Return whether the flag is overridden for all textures of this type.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_keep_all_compressed_buffers" qualifiers="static">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="keep" type="bool" />
 			<param index="0" name="keep" type="bool" />
@@ -55,6 +64,7 @@
 		<member name="keep_compressed_buffer" type="bool" setter="set_keep_compressed_buffer" getter="is_keeping_compressed_buffer" default="false">
 		<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.
 			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.
 			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>
 		<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" />
 		<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)">
 		<member name="size_override" type="Vector2" setter="set_size_override" getter="get_size_override" default="Vector2(0, 0)">

+ 10 - 0
doc/classes/ProjectSettings.xml

@@ -3257,6 +3257,16 @@
 		<member name="rendering/shading/overrides/force_vertex_shading" type="bool" setter="" getter="" default="false">
 		<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.
 			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>
+		<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">
 		<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].
 			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].
 			[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].

+ 8 - 0
doc/classes/ResourceImporterLayeredTexture.xml

@@ -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.
 			[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.
 			See [url=$DOCS_URL/tutorials/assets_pipeline/importing_images.html#compress-mode]Compress mode[/url] in the manual for more details.
 		</member>
 		</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">
 		<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:
 			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).
 			- Textures will not become grainy in the distance (in 3D), or if scaled down due to [Camera2D] zoom or [CanvasItem] scale (in 2D).

+ 8 - 0
doc/classes/ResourceImporterTexture.xml

@@ -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.
 			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).
 			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>
+		<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">
 		<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.
 			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.
 			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.

+ 2 - 0
editor/editor_property_name_processor.cpp

@@ -261,6 +261,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["pvs"] = "PVS";
 	capitalize_string_remaps["pvs"] = "PVS";
 	capitalize_string_remaps["rcedit"] = "rcedit";
 	capitalize_string_remaps["rcedit"] = "rcedit";
 	capitalize_string_remaps["rcodesign"] = "rcodesign";
 	capitalize_string_remaps["rcodesign"] = "rcodesign";
+	capitalize_string_remaps["rdo"] = "RDO";
 	capitalize_string_remaps["rgb"] = "RGB";
 	capitalize_string_remaps["rgb"] = "RGB";
 	capitalize_string_remaps["rid"] = "RID";
 	capitalize_string_remaps["rid"] = "RID";
 	capitalize_string_remaps["rmb"] = "RMB";
 	capitalize_string_remaps["rmb"] = "RMB";
@@ -289,6 +290,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
 	capitalize_string_remaps["textfile"] = "TextFile";
 	capitalize_string_remaps["textfile"] = "TextFile";
 	capitalize_string_remaps["tls"] = "TLS";
 	capitalize_string_remaps["tls"] = "TLS";
 	capitalize_string_remaps["tv"] = "TV";
 	capitalize_string_remaps["tv"] = "TV";
+	capitalize_string_remaps["uastc"] = "UASTC";
 	capitalize_string_remaps["ui"] = "UI";
 	capitalize_string_remaps["ui"] = "UI";
 	capitalize_string_remaps["uri"] = "URI";
 	capitalize_string_remaps["uri"] = "URI";
 	capitalize_string_remaps["url"] = "URL";
 	capitalize_string_remaps["url"] = "URL";

+ 26 - 7
editor/import/resource_importer_layered_texture.cpp

@@ -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")) {
 	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;
 		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;
 	return true;
 }
 }
 
 
@@ -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::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::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));
 	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/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::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));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate"), true));
@@ -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
 	Vector<Ref<Image>> mipmap_images; //for 3D
 
 
 	if (mode == MODE_3D) {
 	if (mode == MODE_3D) {
@@ -278,11 +287,11 @@ void ResourceImporterLayeredTexture::_save_tex(Vector<Ref<Image>> p_images, cons
 	}
 	}
 
 
 	for (int i = 0; i < p_images.size(); i++) {
 	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++) {
 	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);
 	}
 	}
 }
 }
 
 
@@ -375,6 +384,12 @@ Error ResourceImporterLayeredTexture::import(ResourceUID::ID p_source_id, const
 			slices.push_back(slice);
 			slices.push_back(slice);
 		}
 		}
 	}
 	}
+
+	const Image::BasisUniversalPackerParams basisu_params = {
+		p_options["compress/uastc_level"],
+		p_options["compress/rdo_quality_loss"],
+	};
+
 	Array formats_imported;
 	Array formats_imported;
 	Ref<LayeredTextureImport> texture_import;
 	Ref<LayeredTextureImport> texture_import;
 	texture_import.instantiate();
 	texture_import.instantiate();
@@ -392,6 +407,8 @@ Error ResourceImporterLayeredTexture::import(ResourceUID::ID p_source_id, const
 	texture_import->used_channels = used_channels;
 	texture_import->used_channels = used_channels;
 	texture_import->high_quality = high_quality;
 	texture_import->high_quality = high_quality;
 
 
+	texture_import->basisu_params = basisu_params;
+
 	_check_compress_ctex(p_source_file, texture_import);
 	_check_compress_ctex(p_source_file, texture_import);
 	if (r_metadata) {
 	if (r_metadata) {
 		Dictionary meta;
 		Dictionary meta;
@@ -486,7 +503,8 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
 	ERR_FAIL_NULL(r_texture_import->csource);
 	ERR_FAIL_NULL(r_texture_import->csource);
 	if (r_texture_import->compress_mode != COMPRESS_VRAM_COMPRESSED) {
 	if (r_texture_import->compress_mode != COMPRESS_VRAM_COMPRESSED) {
 		// Import normally.
 		// 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;
 		return;
 	}
 	}
 	// Must import in all formats, in order of priority (so platform chooses the best supported one. IE, etc2 over etc).
 	// Must import in all formats, in order of priority (so platform chooses the best supported one. IE, etc2 over etc).
@@ -541,7 +559,8 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
 	}
 	}
 
 
 	if (use_uncompressed) {
 	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 {
 	} else {
 		if (can_s3tc_bptc) {
 		if (can_s3tc_bptc) {
 			Image::CompressMode image_compress_mode;
 			Image::CompressMode image_compress_mode;
@@ -553,7 +572,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
 				image_compress_mode = Image::COMPRESS_S3TC;
 				image_compress_mode = Image::COMPRESS_S3TC;
 				image_compress_format = "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);
 			r_texture_import->platform_variants->push_back(image_compress_format);
 		}
 		}
 
 
@@ -567,7 +586,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
 				image_compress_mode = Image::COMPRESS_ETC2;
 				image_compress_mode = Image::COMPRESS_ETC2;
 				image_compress_format = "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);
 			r_texture_import->platform_variants->push_back(image_compress_format);
 		}
 		}
 	}
 	}

+ 4 - 1
editor/import/resource_importer_layered_texture.h

@@ -49,6 +49,9 @@ public:
 	Vector<Ref<Image>> *slices = nullptr;
 	Vector<Ref<Image>> *slices = nullptr;
 	int compress_mode = 0;
 	int compress_mode = 0;
 	float lossy = 1.0;
 	float lossy = 1.0;
+
+	Image::BasisUniversalPackerParams basisu_params;
+
 	int hdr_compression = 0;
 	int hdr_compression = 0;
 	bool mipmaps = true;
 	bool mipmaps = true;
 	bool high_quality = false;
 	bool high_quality = false;
@@ -108,7 +111,7 @@ public:
 	virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
 	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;
 	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;
 	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;
 
 

+ 23 - 10
editor/import/resource_importer_texture.cpp

@@ -207,6 +207,9 @@ bool ResourceImporterTexture::get_option_visibility(const String &p_path, const
 
 
 	} else if (p_option == "mipmaps/limit") {
 	} else if (p_option == "mipmaps/limit") {
 		return p_options["mipmaps/generate"];
 		return p_options["mipmaps/generate"];
+
+	} else if (p_option == "compress/uastc_level" || p_option == "compress/rdo_quality_loss") {
+		return int(p_options["compress/mode"]) == COMPRESS_BASIS_UNIVERSAL;
 	}
 	}
 
 
 	return true;
 	return true;
@@ -230,6 +233,11 @@ void ResourceImporterTexture::get_import_options(const String &p_path, List<Impo
 	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), p_preset == PRESET_3D ? 2 : 0));
 	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), p_preset == PRESET_3D ? 2 : 0));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/high_quality"), false));
 	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));
 	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/hdr_compression", PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always"), 1));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/normal_map", PROPERTY_HINT_ENUM, "Detect,Enable,Disabled"), 0));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/normal_map", PROPERTY_HINT_ENUM, "Detect,Enable,Disabled"), 0));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0));
 	r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack", PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized"), 0));
@@ -258,7 +266,7 @@ void ResourceImporterTexture::get_import_options(const String &p_path, List<Impo
 	}
 	}
 }
 }
 
 
-void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality) {
+void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params) {
 	switch (p_compress_mode) {
 	switch (p_compress_mode) {
 		case COMPRESS_LOSSLESS: {
 		case COMPRESS_LOSSLESS: {
 			bool lossless_force_png = GLOBAL_GET("rendering/textures/lossless_compression/force_png") || !Image::_webp_mem_loader_func; // WebP module disabled or png is forced.
 			bool lossless_force_png = GLOBAL_GET("rendering/textures/lossless_compression/force_png") || !Image::_webp_mem_loader_func; // WebP module disabled or png is forced.
@@ -329,7 +337,7 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I
 			f->store_32(p_image->get_mipmap_count());
 			f->store_32(p_image->get_mipmap_count());
 			f->store_32(p_image->get_format());
 			f->store_32(p_image->get_format());
 
 
-			Vector<uint8_t> data = Image::basis_universal_packer(p_image, p_channels);
+			Vector<uint8_t> data = Image::basis_universal_packer(p_image, p_channels, p_basisu_params);
 			const uint64_t data_size = data.size();
 			const uint64_t data_size = data.size();
 
 
 			f->store_32(data_size);
 			f->store_32(data_size);
@@ -338,7 +346,7 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I
 	}
 	}
 }
 }
 
 
-void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) {
+void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) {
 	Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
 	Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
 	ERR_FAIL_COND(f.is_null());
 	ERR_FAIL_COND(f.is_null());
 
 
@@ -418,7 +426,7 @@ void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String
 		used_channels = image->detect_used_channels(comp_source);
 		used_channels = image->detect_used_channels(comp_source);
 	}
 	}
 
 
-	save_to_ctex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality);
+	save_to_ctex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality, p_basisu_params);
 }
 }
 
 
 void ResourceImporterTexture::_save_editor_meta(const Dictionary &p_metadata, const String &p_to_path) {
 void ResourceImporterTexture::_save_editor_meta(const Dictionary &p_metadata, const String &p_to_path) {
@@ -508,6 +516,11 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String
 	const bool hdr_clamp_exposure = p_options["process/hdr_clamp_exposure"];
 	const bool hdr_clamp_exposure = p_options["process/hdr_clamp_exposure"];
 	int size_limit = p_options["process/size_limit"];
 	int size_limit = p_options["process/size_limit"];
 
 
+	const Image::BasisUniversalPackerParams basisu_params = {
+		p_options["compress/uastc_level"],
+		p_options["compress/rdo_quality_loss"],
+	};
+
 	bool using_fallback_size_limit = false;
 	bool using_fallback_size_limit = false;
 	if (size_limit == 0) {
 	if (size_limit == 0) {
 		using_fallback_size_limit = true;
 		using_fallback_size_limit = true;
@@ -685,7 +698,7 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String
 		}
 		}
 
 
 		if (force_uncompressed) {
 		if (force_uncompressed) {
-			_save_ctex(image, p_save_path + ".ctex", COMPRESS_VRAM_UNCOMPRESSED, lossy, Image::COMPRESS_S3TC /* This is ignored. */,
+			_save_ctex(image, p_save_path + ".ctex", COMPRESS_VRAM_UNCOMPRESSED, lossy, basisu_params, Image::COMPRESS_S3TC /* This is ignored. */,
 					mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 					mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 		} else {
 		} else {
 			if (can_s3tc_bptc) {
 			if (can_s3tc_bptc) {
@@ -699,8 +712,8 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String
 					image_compress_format = "s3tc";
 					image_compress_format = "s3tc";
 				}
 				}
 
 
-				_save_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d,
-						detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
+				_save_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, basisu_params, image_compress_mode, mipmaps,
+						stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 				r_platform_variants->push_back(image_compress_format);
 				r_platform_variants->push_back(image_compress_format);
 			}
 			}
 
 
@@ -715,19 +728,19 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String
 					image_compress_format = "etc2";
 					image_compress_format = "etc2";
 				}
 				}
 
 
-				_save_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d,
+				_save_ctex(image, p_save_path + "." + image_compress_format + ".ctex", compress_mode, lossy, basisu_params, image_compress_mode, mipmaps, stream, detect_3d,
 						detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 						detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 				r_platform_variants->push_back(image_compress_format);
 				r_platform_variants->push_back(image_compress_format);
 			}
 			}
 		}
 		}
 	} else {
 	} else {
 		// Import normally.
 		// Import normally.
-		_save_ctex(image, p_save_path + ".ctex", compress_mode, lossy, Image::COMPRESS_S3TC /* This is ignored. */,
+		_save_ctex(image, p_save_path + ".ctex", compress_mode, lossy, basisu_params, Image::COMPRESS_S3TC /* This is ignored. */,
 				mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 				mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 	}
 	}
 
 
 	if (editor_image.is_valid()) {
 	if (editor_image.is_valid()) {
-		_save_ctex(editor_image, p_save_path + ".editor.ctex", compress_mode, lossy, Image::COMPRESS_S3TC /* This is ignored. */,
+		_save_ctex(editor_image, p_save_path + ".editor.ctex", compress_mode, lossy, basisu_params, Image::COMPRESS_S3TC /* This is ignored. */,
 				mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 				mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel);
 
 
 		// Generate and save editor-specific metadata, which we cannot save to the .import file.
 		// Generate and save editor-specific metadata, which we cannot save to the .import file.

+ 2 - 2
editor/import/resource_importer_texture.h

@@ -72,7 +72,7 @@ protected:
 	static ResourceImporterTexture *singleton;
 	static ResourceImporterTexture *singleton;
 	static const char *compression_formats[];
 	static const char *compression_formats[];
 
 
-	void _save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_srgb, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel);
+	void _save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_srgb, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel);
 	void _save_editor_meta(const Dictionary &p_metadata, const String &p_to_path);
 	void _save_editor_meta(const Dictionary &p_metadata, const String &p_to_path);
 	Dictionary _load_editor_meta(const String &p_to_path) const;
 	Dictionary _load_editor_meta(const String &p_to_path) const;
 
 
@@ -80,7 +80,7 @@ protected:
 	static inline void _invert_y_channel(Ref<Image> &r_image);
 	static inline void _invert_y_channel(Ref<Image> &r_image);
 
 
 public:
 public:
-	static void save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality);
+	static void save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality, const Image::BasisUniversalPackerParams &p_basisu_params);
 
 
 	static ResourceImporterTexture *get_singleton() { return singleton; }
 	static ResourceImporterTexture *get_singleton() { return singleton; }
 	virtual String get_importer_name() const override;
 	virtual String get_importer_name() const override;

+ 2 - 7
modules/basis_universal/SCsub

@@ -60,13 +60,8 @@ env_thirdparty.disable_warnings()
 # <https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder>
 # <https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder>
 env_thirdparty.Append(
 env_thirdparty.Append(
     CPPDEFINES=[
     CPPDEFINES=[
-        # Storage formats.
-        # Godot only implements `.basis` support through basis_universal.
-        # Support for `.ktx` files are implemented with a direct libktx implementation.
-        # Building the encoder requires `BASISD_SUPPORT_KTX2` to be enabled,
-        # so we can only disable Zstandard compression for `.ktx` files
-        # (this is not used in `.basis` files).
-        ("BASISD_SUPPORT_KTX2_ZSTD", 0),
+        # Enable ktx2 zstd supercompression.
+        ("BASISD_SUPPORT_KTX2_ZSTD", 1),
         # GPU compression formats.
         # GPU compression formats.
         ("BASISD_SUPPORT_ATC", 0),  # Proprietary Adreno format not supported by Godot.
         ("BASISD_SUPPORT_ATC", 0),  # Proprietary Adreno format not supported by Godot.
         ("BASISD_SUPPORT_FXT1", 0),  # Legacy format not supported by Godot.
         ("BASISD_SUPPORT_FXT1", 0),  # Legacy format not supported by Godot.

+ 71 - 30
modules/basis_universal/image_compress_basisu.cpp

@@ -30,6 +30,7 @@
 
 
 #include "image_compress_basisu.h"
 #include "image_compress_basisu.h"
 
 
+#include "core/config/project_settings.h"
 #include "core/io/image.h"
 #include "core/io/image.h"
 #include "core/os/os.h"
 #include "core/os/os.h"
 #include "core/string/print_string.h"
 #include "core/string/print_string.h"
@@ -78,7 +79,7 @@ inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t>
 	}
 	}
 }
 }
 
 
-Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels) {
+Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels, const Image::BasisUniversalPackerParams &p_basisu_params) {
 	init_mutex.lock();
 	init_mutex.lock();
 	if (!initialized) {
 	if (!initialized) {
 		basisu::basisu_encoder_init();
 		basisu::basisu_encoder_init();
@@ -98,16 +99,23 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
 		is_hdr = true;
 		is_hdr = true;
 	}
 	}
 
 
+	int rdo_dict_size = GLOBAL_GET_CACHED(int, "rendering/textures/basis_universal/rdo_dict_size");
+	bool zstd_supercompression = GLOBAL_GET_CACHED(bool, "rendering/textures/basis_universal/zstd_supercompression");
+	int zstd_supercompression_level = GLOBAL_GET_CACHED(int, "rendering/textures/basis_universal/zstd_supercompression_level");
+
 	basisu::basis_compressor_params params;
 	basisu::basis_compressor_params params;
 
 
 	params.m_uastc = true;
 	params.m_uastc = true;
-	params.m_etc1s_quality_level = basisu::BASISU_QUALITY_MIN;
 	params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;
 	params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;
-	params.m_pack_uastc_ldr_4x4_flags |= basisu::cPackUASTCLevelFastest;
+	params.m_pack_uastc_ldr_4x4_flags |= p_basisu_params.uastc_level;
+
+	params.m_rdo_uastc_ldr_4x4 = p_basisu_params.rdo_quality_loss >= 0.01;
+	params.m_rdo_uastc_ldr_4x4_quality_scalar = p_basisu_params.rdo_quality_loss;
+	params.m_rdo_uastc_ldr_4x4_dict_size = rdo_dict_size;
 
 
-	params.m_rdo_uastc_ldr_4x4 = 0.0f;
-	params.m_rdo_uastc_ldr_4x4_quality_scalar = 0.0f;
-	params.m_rdo_uastc_ldr_4x4_dict_size = 1024;
+	params.m_create_ktx2_file = true;
+	params.m_ktx2_uastc_supercompression = zstd_supercompression ? basist::KTX2_SS_ZSTANDARD : basist::KTX2_SS_NONE;
+	params.m_ktx2_zstd_supercompression_level = zstd_supercompression_level;
 
 
 	params.m_mip_fast = true;
 	params.m_mip_fast = true;
 	params.m_multithreading = true;
 	params.m_multithreading = true;
@@ -241,14 +249,14 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
 	int basisu_err = compressor.process();
 	int basisu_err = compressor.process();
 	ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
 	ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
 
 
-	const basisu::uint8_vec &basisu_encoded = compressor.get_output_basis_file();
+	const basisu::uint8_vec &basisu_encoded = compressor.get_output_ktx2_file();
 
 
 	Vector<uint8_t> basisu_data;
 	Vector<uint8_t> basisu_data;
 	basisu_data.resize(basisu_encoded.size() + 4);
 	basisu_data.resize(basisu_encoded.size() + 4);
 	uint8_t *basisu_data_ptr = basisu_data.ptrw();
 	uint8_t *basisu_data_ptr = basisu_data.ptrw();
 
 
 	// Copy the encoded BasisU data into the output buffer.
 	// Copy the encoded BasisU data into the output buffer.
-	*(uint32_t *)basisu_data_ptr = decompress_format;
+	*(uint32_t *)basisu_data_ptr = decompress_format | BASIS_DECOMPRESS_FLAG_KTX2;
 	memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());
 	memcpy(basisu_data_ptr + 4, basisu_encoded.get_ptr(), basisu_encoded.size());
 
 
 	print_verbose(vformat("BasisU: Encoding a %dx%d image with %d mipmaps took %d ms.", p_image->get_width(), p_image->get_height(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
 	print_verbose(vformat("BasisU: Encoding a %dx%d image with %d mipmaps took %d ms.", p_image->get_width(), p_image->get_height(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
@@ -280,7 +288,9 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
 	bool needs_ra_rg_swap = false;
 	bool needs_ra_rg_swap = false;
 	bool needs_rg_trim = false;
 	bool needs_rg_trim = false;
 
 
-	BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr));
+	uint32_t decompress_format = *(uint32_t *)(src_ptr);
+	bool is_ktx2 = decompress_format & BASIS_DECOMPRESS_FLAG_KTX2;
+	decompress_format &= ~BASIS_DECOMPRESS_FLAG_KTX2;
 
 
 	switch (decompress_format) {
 	switch (decompress_format) {
 		case BASIS_DECOMPRESS_R: {
 		case BASIS_DECOMPRESS_R: {
@@ -398,37 +408,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
 	src_ptr += 4;
 	src_ptr += 4;
 	src_size -= 4;
 	src_size -= 4;
 
 
-	basist::basisu_transcoder transcoder;
-	ERR_FAIL_COND_V(!transcoder.validate_header(src_ptr, src_size), image);
+	if (is_ktx2) {
+		basist::ktx2_transcoder transcoder;
+		ERR_FAIL_COND_V(!transcoder.init(src_ptr, src_size), image);
+
+		transcoder.start_transcoding();
+
+		// Create the buffer for transcoded/decompressed data.
+		Vector<uint8_t> out_data;
+		out_data.resize(Image::get_image_data_size(transcoder.get_width(), transcoder.get_height(), image_format, transcoder.get_levels() > 1));
+
+		uint8_t *dst = out_data.ptrw();
+		memset(dst, 0, out_data.size());
+
+		for (uint32_t i = 0; i < transcoder.get_levels(); i++) {
+			basist::ktx2_image_level_info basisu_level;
+			transcoder.get_image_level_info(basisu_level, i, 0, 0);
+
+			uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;
+			int64_t ofs = Image::get_image_mipmap_offset(transcoder.get_width(), transcoder.get_height(), image_format, i);
+
+			bool result = transcoder.transcode_image_level(i, 0, 0, dst + ofs, mip_block_or_pixel_count, basisu_format);
 
 
-	transcoder.start_transcoding(src_ptr, src_size);
+			if (!result) {
+				print_line(vformat("BasisUniversal cannot unpack level %d.", i));
+				break;
+			}
+		}
+
+		image = Image::create_from_data(transcoder.get_width(), transcoder.get_height(), transcoder.get_levels() > 1, image_format, out_data);
+	} else {
+		basist::basisu_transcoder transcoder;
+		ERR_FAIL_COND_V(!transcoder.validate_header(src_ptr, src_size), image);
 
 
-	basist::basisu_image_info basisu_info;
-	transcoder.get_image_info(src_ptr, src_size, basisu_info, 0);
+		transcoder.start_transcoding(src_ptr, src_size);
 
 
-	// Create the buffer for transcoded/decompressed data.
-	Vector<uint8_t> out_data;
-	out_data.resize(Image::get_image_data_size(basisu_info.m_width, basisu_info.m_height, image_format, basisu_info.m_total_levels > 1));
+		basist::basisu_image_info basisu_info;
+		transcoder.get_image_info(src_ptr, src_size, basisu_info, 0);
 
 
-	uint8_t *dst = out_data.ptrw();
-	memset(dst, 0, out_data.size());
+		// Create the buffer for transcoded/decompressed data.
+		Vector<uint8_t> out_data;
+		out_data.resize(Image::get_image_data_size(basisu_info.m_width, basisu_info.m_height, image_format, basisu_info.m_total_levels > 1));
 
 
-	for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) {
-		basist::basisu_image_level_info basisu_level;
-		transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i);
+		uint8_t *dst = out_data.ptrw();
+		memset(dst, 0, out_data.size());
 
 
-		uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;
-		int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i);
+		for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) {
+			basist::basisu_image_level_info basisu_level;
+			transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i);
 
 
-		bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format);
+			uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height;
+			int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i);
 
 
-		if (!result) {
-			print_line(vformat("BasisUniversal cannot unpack level %d.", i));
-			break;
+			bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format);
+
+			if (!result) {
+				print_line(vformat("BasisUniversal cannot unpack level %d.", i));
+				break;
+			}
 		}
 		}
-	}
 
 
-	image = Image::create_from_data(basisu_info.m_width, basisu_info.m_height, basisu_info.m_total_levels > 1, image_format, out_data);
+		image = Image::create_from_data(basisu_info.m_width, basisu_info.m_height, basisu_info.m_total_levels > 1, image_format, out_data);
+	}
 
 
 	if (needs_ra_rg_swap) {
 	if (needs_ra_rg_swap) {
 		// Swap uncompressed RA-as-RG texture's color channels.
 		// Swap uncompressed RA-as-RG texture's color channels.

+ 2 - 1
modules/basis_universal/image_compress_basisu.h

@@ -41,6 +41,7 @@ enum BasisDecompressFormat {
 	BASIS_DECOMPRESS_HDR_RGB,
 	BASIS_DECOMPRESS_HDR_RGB,
 	BASIS_DECOMPRESS_MAX
 	BASIS_DECOMPRESS_MAX
 };
 };
+constexpr uint32_t BASIS_DECOMPRESS_FLAG_KTX2 = 1 << 31;
 
 
 void basis_universal_init();
 void basis_universal_init();
 
 
@@ -52,7 +53,7 @@ struct BasisRGBAF {
 	uint32_t a;
 	uint32_t a;
 };
 };
 
 
-Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels);
+Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels, const Image::BasisUniversalPackerParams &p_basisu_params);
 #endif
 #endif
 
 
 Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size);
 Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size);

+ 6 - 0
modules/basis_universal/register_types.cpp

@@ -32,6 +32,8 @@
 
 
 #include "image_compress_basisu.h"
 #include "image_compress_basisu.h"
 
 
+#include "core/config/project_settings.h"
+
 void initialize_basis_universal_module(ModuleInitializationLevel p_level) {
 void initialize_basis_universal_module(ModuleInitializationLevel p_level) {
 	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
 	if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
 		return;
 		return;
@@ -40,6 +42,10 @@ void initialize_basis_universal_module(ModuleInitializationLevel p_level) {
 	basis_universal_init();
 	basis_universal_init();
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
+	GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/textures/basis_universal/rdo_dict_size", PROPERTY_HINT_RANGE, "64,65536,1"), 1024);
+	GLOBAL_DEF(PropertyInfo(Variant::BOOL, "rendering/textures/basis_universal/zstd_supercompression"), true);
+	GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/textures/basis_universal/zstd_supercompression_level"), 6);
+
 	Image::basis_universal_packer = basis_universal_packer;
 	Image::basis_universal_packer = basis_universal_packer;
 #endif
 #endif
 
 

+ 8 - 1
scene/resources/portable_compressed_texture.cpp

@@ -193,7 +193,7 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C
 			ERR_FAIL_COND(p_image->is_compressed());
 			ERR_FAIL_COND(p_image->is_compressed());
 			encode_uint16(DATA_FORMAT_BASIS_UNIVERSAL, buffer.ptrw() + 2);
 			encode_uint16(DATA_FORMAT_BASIS_UNIVERSAL, buffer.ptrw() + 2);
 			Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC);
 			Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC);
-			Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc);
+			Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc, basisu_params);
 			buffer.append_array(budata);
 			buffer.append_array(budata);
 #else
 #else
 			ERR_FAIL_MSG("Basis Universal compression can only run in editor build.");
 			ERR_FAIL_MSG("Basis Universal compression can only run in editor build.");
@@ -360,6 +360,11 @@ bool PortableCompressedTexture2D::is_keeping_compressed_buffer() const {
 	return keep_compressed_buffer;
 	return keep_compressed_buffer;
 }
 }
 
 
+void PortableCompressedTexture2D::set_basisu_compressor_params(int p_uastc_level, float p_rdo_quality_loss) {
+	basisu_params.uastc_level = p_uastc_level;
+	basisu_params.rdo_quality_loss = p_rdo_quality_loss;
+}
+
 void PortableCompressedTexture2D::_bind_methods() {
 void PortableCompressedTexture2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8));
 	ClassDB::bind_method(D_METHOD("create_from_image", "image", "compression_mode", "normal_map", "lossy_quality"), &PortableCompressedTexture2D::create_from_image, DEFVAL(false), DEFVAL(0.8));
 	ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format);
 	ClassDB::bind_method(D_METHOD("get_format"), &PortableCompressedTexture2D::get_format);
@@ -371,6 +376,8 @@ void PortableCompressedTexture2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer);
 	ClassDB::bind_method(D_METHOD("set_keep_compressed_buffer", "keep"), &PortableCompressedTexture2D::set_keep_compressed_buffer);
 	ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer);
 	ClassDB::bind_method(D_METHOD("is_keeping_compressed_buffer"), &PortableCompressedTexture2D::is_keeping_compressed_buffer);
 
 
+	ClassDB::bind_method(D_METHOD("set_basisu_compressor_params", "uastc_level", "rdo_quality_loss"), &PortableCompressedTexture2D::set_basisu_compressor_params);
+
 	ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data);
 	ClassDB::bind_method(D_METHOD("_set_data", "data"), &PortableCompressedTexture2D::_set_data);
 	ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data);
 	ClassDB::bind_method(D_METHOD("_get_data"), &PortableCompressedTexture2D::_get_data);
 
 

+ 4 - 0
scene/resources/portable_compressed_texture.h

@@ -71,6 +71,8 @@ private:
 
 
 	bool image_stored = false;
 	bool image_stored = false;
 
 
+	Image::BasisUniversalPackerParams basisu_params;
+
 protected:
 protected:
 	Vector<uint8_t> _get_data() const;
 	Vector<uint8_t> _get_data() const;
 	void _set_data(const Vector<uint8_t> &p_data);
 	void _set_data(const Vector<uint8_t> &p_data);
@@ -106,6 +108,8 @@ public:
 	void set_keep_compressed_buffer(bool p_keep);
 	void set_keep_compressed_buffer(bool p_keep);
 	bool is_keeping_compressed_buffer() const;
 	bool is_keeping_compressed_buffer() const;
 
 
+	void set_basisu_compressor_params(int p_uastc_level, float p_rdo_quality_loss);
+
 	static void set_keep_all_compressed_buffers(bool p_keep);
 	static void set_keep_all_compressed_buffers(bool p_keep);
 	static bool is_keeping_all_compressed_buffers();
 	static bool is_keeping_all_compressed_buffers();