Parcourir la source

Merge pull request #99537 from BlueCube3310/betsy-bc1-bc5

Betsy: Add BC3 and BC5 support
Rémi Verschelde il y a 9 mois
Parent
commit
f4f6f5fb55

+ 1 - 2
core/io/image.cpp

@@ -2788,8 +2788,7 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels
 			} break;
 
 			case COMPRESS_S3TC: {
-				// BC3 is unsupported currently.
-				if ((p_channels == USED_CHANNELS_R || p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) {
+				if (_image_compress_bc_rd_func) {
 					Error result = _image_compress_bc_rd_func(this, p_channels);
 
 					// If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme.

+ 1 - 0
modules/betsy/SCsub

@@ -8,6 +8,7 @@ env_betsy = env_modules.Clone()
 env_betsy.GLSL_HEADER("bc6h.glsl")
 env_betsy.GLSL_HEADER("bc1.glsl")
 env_betsy.GLSL_HEADER("bc4.glsl")
+env_betsy.GLSL_HEADER("alpha_stitch.glsl")
 env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"])
 
 # Thirdparty source files

+ 24 - 0
modules/betsy/alpha_stitch.glsl

@@ -0,0 +1,24 @@
+// RGB and Alpha components of ETC2 RGBA are computed separately.
+// This compute shader merely stitches them together to form the final result
+// It's also used by RG11 driver to stitch two R11 into one RG11
+
+#[compute]
+#version 450
+
+#include "CrossPlatformSettings_piece_all.glsl"
+#include "UavCrossPlatform_piece_all.glsl"
+
+layout(local_size_x = 8, //
+		local_size_y = 8, //
+		local_size_z = 1) in;
+
+layout(binding = 0) uniform usampler2D srcRGB;
+layout(binding = 1) uniform usampler2D srcAlpha;
+layout(binding = 2, rgba32ui) uniform restrict writeonly uimage2D dstTexture;
+
+void main() {
+	uint2 rgbBlock = OGRE_Load2D(srcRGB, int2(gl_GlobalInvocationID.xy), 0).xy;
+	uint2 alphaBlock = OGRE_Load2D(srcAlpha, int2(gl_GlobalInvocationID.xy), 0).xy;
+
+	imageStore(dstTexture, int2(gl_GlobalInvocationID.xy), uint4(rgbBlock.xy, alphaBlock.xy));
+}

+ 384 - 225
modules/betsy/image_compress_betsy.cpp

@@ -34,6 +34,7 @@
 
 #include "betsy_bc1.h"
 
+#include "alpha_stitch.glsl.gen.h"
 #include "bc1.glsl.gen.h"
 #include "bc4.glsl.gen.h"
 #include "bc6h.glsl.gen.h"
@@ -42,6 +43,42 @@
 static Mutex betsy_mutex;
 static BetsyCompressor *betsy = nullptr;
 
+static const BetsyShaderType FORMAT_TO_TYPE[BETSY_FORMAT_MAX] = {
+	BETSY_SHADER_BC1_STANDARD,
+	BETSY_SHADER_BC1_DITHER,
+	BETSY_SHADER_BC1_STANDARD,
+	BETSY_SHADER_BC4_SIGNED,
+	BETSY_SHADER_BC4_UNSIGNED,
+	BETSY_SHADER_BC4_SIGNED,
+	BETSY_SHADER_BC4_UNSIGNED,
+	BETSY_SHADER_BC6_SIGNED,
+	BETSY_SHADER_BC6_UNSIGNED,
+};
+
+static const RD::DataFormat BETSY_TO_RD_FORMAT[BETSY_FORMAT_MAX] = {
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32_UINT,
+	RD::DATA_FORMAT_R32G32B32A32_UINT,
+	RD::DATA_FORMAT_R32G32B32A32_UINT,
+};
+
+static const Image::Format BETSY_TO_IMAGE_FORMAT[BETSY_FORMAT_MAX] = {
+	Image::FORMAT_DXT1,
+	Image::FORMAT_DXT1,
+	Image::FORMAT_DXT5,
+	Image::FORMAT_RGTC_R,
+	Image::FORMAT_RGTC_R,
+	Image::FORMAT_RGTC_RG,
+	Image::FORMAT_RGTC_RG,
+	Image::FORMAT_BPTC_RGBF,
+	Image::FORMAT_BPTC_RGBFU,
+};
+
 void BetsyCompressor::_init() {
 	if (!DisplayServer::can_create_rendering_device()) {
 		return;
@@ -95,6 +132,94 @@ void BetsyCompressor::_init() {
 	}
 
 	src_sampler = compress_rd->sampler_create(src_sampler_state);
+
+	// Initialize RDShaderFiles.
+	{
+		Ref<RDShaderFile> bc1_shader;
+		bc1_shader.instantiate();
+		Error err = bc1_shader->parse_versions_from_text(bc1_shader_glsl);
+
+		if (err != OK) {
+			bc1_shader->print_errors("Betsy BC1 compress shader");
+		}
+
+		// Standard BC1 compression.
+		cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("standard"));
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled.is_null());
+
+		cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled);
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline.is_null());
+
+		// Dither BC1 variant. Unused, so comment out for now.
+		//cached_shaders[BETSY_SHADER_BC1_DITHER].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("dithered"));
+		//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled.is_null());
+
+		//cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled);
+		//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline.is_null());
+	}
+
+	{
+		Ref<RDShaderFile> bc4_shader;
+		bc4_shader.instantiate();
+		Error err = bc4_shader->parse_versions_from_text(bc4_shader_glsl);
+
+		if (err != OK) {
+			bc4_shader->print_errors("Betsy BC4 compress shader");
+		}
+
+		// Signed BC4 compression. Unused, so comment out for now.
+		//cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("signed"));
+		//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled.is_null());
+
+		//cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled);
+		//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline.is_null());
+
+		// Unsigned BC4 compression.
+		cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("unsigned"));
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled.is_null());
+
+		cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled);
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline.is_null());
+	}
+
+	{
+		Ref<RDShaderFile> bc6h_shader;
+		bc6h_shader.instantiate();
+		Error err = bc6h_shader->parse_versions_from_text(bc6h_shader_glsl);
+
+		if (err != OK) {
+			bc6h_shader->print_errors("Betsy BC6 compress shader");
+		}
+
+		// Signed BC6 compression.
+		cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("signed"));
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled.is_null());
+
+		cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled);
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline.is_null());
+
+		// Unsigned BC6 compression.
+		cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("unsigned"));
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled.is_null());
+
+		cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled);
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline.is_null());
+	}
+
+	{
+		Ref<RDShaderFile> alpha_stitch_shader;
+		alpha_stitch_shader.instantiate();
+		Error err = alpha_stitch_shader->parse_versions_from_text(alpha_stitch_shader_glsl);
+
+		if (err != OK) {
+			alpha_stitch_shader->print_errors("Betsy alpha stitch shader");
+		}
+		cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled = compress_rd->shader_create_from_spirv(alpha_stitch_shader->get_spirv_stages());
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled.is_null());
+
+		cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled);
+		ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline.is_null());
+	}
 }
 
 void BetsyCompressor::init() {
@@ -129,12 +254,11 @@ void BetsyCompressor::_thread_exit() {
 		compress_rd->free(src_sampler);
 
 		// Clear the shader cache, pipelines will be unreferenced automatically.
-		for (KeyValue<String, BetsyShader> &E : cached_shaders) {
-			if (E.value.compiled.is_valid()) {
-				compress_rd->free(E.value.compiled);
+		for (int i = 0; i < BETSY_SHADER_MAX; i++) {
+			if (cached_shaders[i].compiled.is_valid()) {
+				compress_rd->free(cached_shaders[i].compiled);
 			}
 		}
-		cached_shaders.clear();
 	}
 }
 
@@ -162,227 +286,152 @@ static int get_next_multiple(int n, int m) {
 	return n + (m - (n % m));
 }
 
-static String get_shader_name(BetsyFormat p_format) {
-	switch (p_format) {
-		case BETSY_FORMAT_BC1:
-		case BETSY_FORMAT_BC1_DITHER:
-			return "BC1";
-
-		case BETSY_FORMAT_BC3:
-			return "BC3";
-
-		case BETSY_FORMAT_BC4_SIGNED:
-		case BETSY_FORMAT_BC4_UNSIGNED:
-			return "BC4";
-
-		case BETSY_FORMAT_BC6_SIGNED:
-		case BETSY_FORMAT_BC6_UNSIGNED:
-			return "BC6";
-
-		default:
-			return "";
-	}
-}
-
-Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
-	uint64_t start_time = OS::get_singleton()->get_ticks_msec();
-
-	// Return an error so that the compression can fall back to cpu compression
-	if (compress_rd == nullptr) {
-		return ERR_CANT_CREATE;
-	}
-
-	if (r_img->is_compressed()) {
-		return ERR_INVALID_DATA;
-	}
-
-	Error err = OK;
-
-	// Destination format.
-	Image::Format dest_format = Image::FORMAT_MAX;
-	RD::DataFormat dst_rd_format = RD::DATA_FORMAT_MAX;
-
-	String version = "";
-
-	switch (p_format) {
-		case BETSY_FORMAT_BC1:
-			version = "standard";
-			dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
-			dest_format = Image::FORMAT_DXT1;
-			break;
-
-		case BETSY_FORMAT_BC1_DITHER:
-			version = "dithered";
-			dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
-			dest_format = Image::FORMAT_DXT1;
-			break;
-
-		case BETSY_FORMAT_BC4_UNSIGNED:
-			version = "unsigned";
-			dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
-			dest_format = Image::FORMAT_RGTC_R;
-			break;
-
-		case BETSY_FORMAT_BC6_SIGNED:
-			version = "signed";
-			dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
-			dest_format = Image::FORMAT_BPTC_RGBF;
-			break;
-
-		case BETSY_FORMAT_BC6_UNSIGNED:
-			version = "unsigned";
-			dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
-			dest_format = Image::FORMAT_BPTC_RGBFU;
-			break;
-
-		default:
-			err = ERR_INVALID_PARAMETER;
-			break;
-	}
-
-	const String shader_name = get_shader_name(p_format) + "-" + version;
-	BetsyShader shader;
-
-	if (cached_shaders.has(shader_name)) {
-		shader = cached_shaders[shader_name];
-
-	} else {
-		Ref<RDShaderFile> source;
-		source.instantiate();
-
-		switch (p_format) {
-			case BETSY_FORMAT_BC1:
-			case BETSY_FORMAT_BC1_DITHER:
-				err = source->parse_versions_from_text(bc1_shader_glsl);
-				break;
-
-			case BETSY_FORMAT_BC4_SIGNED:
-			case BETSY_FORMAT_BC4_UNSIGNED:
-				err = source->parse_versions_from_text(bc4_shader_glsl);
-				break;
-
-			case BETSY_FORMAT_BC6_SIGNED:
-			case BETSY_FORMAT_BC6_UNSIGNED:
-				err = source->parse_versions_from_text(bc6h_shader_glsl);
-				break;
-
-			default:
-				err = ERR_INVALID_PARAMETER;
-				break;
-		}
-
-		if (err != OK) {
-			source->print_errors("Betsy compress shader");
-			return err;
-		}
-
-		// Compile the shader, return early if invalid.
-		shader.compiled = compress_rd->shader_create_from_spirv(source->get_spirv_stages(version));
-		if (shader.compiled.is_null()) {
-			return ERR_CANT_CREATE;
-		}
-
-		// Compile the pipeline, return early if invalid.
-		shader.pipeline = compress_rd->compute_pipeline_create(shader.compiled);
-		if (shader.pipeline.is_null()) {
-			return ERR_CANT_CREATE;
-		}
-
-		cached_shaders[shader_name] = shader;
-	}
-
-	if (shader.compiled.is_null() || shader.pipeline.is_null()) {
-		return ERR_INVALID_DATA;
-	}
-
-	// src_texture format information.
-	RD::TextureFormat src_texture_format;
-	{
-		src_texture_format.array_layers = 1;
-		src_texture_format.depth = 1;
-		src_texture_format.mipmaps = 1;
-		src_texture_format.texture_type = RD::TEXTURE_TYPE_2D;
-		src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
-	}
-
+static Error get_src_texture_format(Image *r_img, RD::DataFormat &r_format) {
 	switch (r_img->get_format()) {
 		case Image::FORMAT_L8:
 			r_img->convert(Image::FORMAT_RGBA8);
-			src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+			r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
 			break;
 
 		case Image::FORMAT_LA8:
 			r_img->convert(Image::FORMAT_RGBA8);
-			src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+			r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
 			break;
 
 		case Image::FORMAT_R8:
-			src_texture_format.format = RD::DATA_FORMAT_R8_UNORM;
+			r_format = RD::DATA_FORMAT_R8_UNORM;
 			break;
 
 		case Image::FORMAT_RG8:
-			src_texture_format.format = RD::DATA_FORMAT_R8G8_UNORM;
+			r_format = RD::DATA_FORMAT_R8G8_UNORM;
 			break;
 
 		case Image::FORMAT_RGB8:
 			r_img->convert(Image::FORMAT_RGBA8);
-			src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+			r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
 			break;
 
 		case Image::FORMAT_RGBA8:
-			src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+			r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
 			break;
 
 		case Image::FORMAT_RH:
-			src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT;
+			r_format = RD::DATA_FORMAT_R16_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGH:
-			src_texture_format.format = RD::DATA_FORMAT_R16G16_SFLOAT;
+			r_format = RD::DATA_FORMAT_R16G16_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGBH:
 			r_img->convert(Image::FORMAT_RGBAH);
-			src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+			r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGBAH:
-			src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+			r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
 			break;
 
 		case Image::FORMAT_RF:
-			src_texture_format.format = RD::DATA_FORMAT_R32_SFLOAT;
+			r_format = RD::DATA_FORMAT_R32_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGF:
-			src_texture_format.format = RD::DATA_FORMAT_R32G32_SFLOAT;
+			r_format = RD::DATA_FORMAT_R32G32_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGBF:
 			r_img->convert(Image::FORMAT_RGBAF);
-			src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
+			r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGBAF:
-			src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
+			r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
 			break;
 
 		case Image::FORMAT_RGBE9995:
-			src_texture_format.format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
+			r_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
 			break;
 
 		default: {
-			return err;
+			return ERR_UNAVAILABLE;
 		}
 	}
 
+	return OK;
+}
+
+Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
+	uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
+	// Return an error so that the compression can fall back to cpu compression
+	if (compress_rd == nullptr) {
+		return ERR_CANT_CREATE;
+	}
+
+	if (r_img->is_compressed()) {
+		return ERR_INVALID_DATA;
+	}
+
+	Error err = OK;
+
+	// Destination format.
+	Image::Format dest_format = BETSY_TO_IMAGE_FORMAT[p_format];
+	RD::DataFormat dst_rd_format = BETSY_TO_RD_FORMAT[p_format];
+
+	BetsyShaderType shader_type = FORMAT_TO_TYPE[p_format];
+	BetsyShader shader = cached_shaders[shader_type];
+	BetsyShader secondary_shader; // The secondary shader is used for alpha blocks. For BC it's BC4U and for ETC it's ETC2_RU (8-bit variant).
+	BetsyShader stitch_shader;
+	bool needs_alpha_block = false;
+
+	switch (p_format) {
+		case BETSY_FORMAT_BC3:
+		case BETSY_FORMAT_BC5_UNSIGNED:
+			needs_alpha_block = true;
+			secondary_shader = cached_shaders[BETSY_SHADER_BC4_UNSIGNED];
+			stitch_shader = cached_shaders[BETSY_SHADER_ALPHA_STITCH];
+			break;
+		default:
+			break;
+	}
+
+	// src_texture format information.
+	RD::TextureFormat src_texture_format;
+	{
+		src_texture_format.array_layers = 1;
+		src_texture_format.depth = 1;
+		src_texture_format.mipmaps = 1;
+		src_texture_format.texture_type = RD::TEXTURE_TYPE_2D;
+		src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
+	}
+
+	err = get_src_texture_format(r_img, src_texture_format.format);
+
+	if (err != OK) {
+		return err;
+	}
+
 	// For the destination format just copy the source format and change the usage bits.
 	RD::TextureFormat dst_texture_format = src_texture_format;
 	dst_texture_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
 	dst_texture_format.format = dst_rd_format;
 
+	RD::TextureFormat dst_texture_format_alpha;
+	RD::TextureFormat dst_texture_format_combined;
+
+	if (needs_alpha_block) {
+		dst_texture_format_combined = dst_texture_format;
+		dst_texture_format_combined.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
+
+		dst_texture_format.usage_bits |= RD::TEXTURE_USAGE_SAMPLING_BIT;
+
+		dst_texture_format_alpha = dst_texture_format;
+		dst_texture_format_alpha.format = RD::DATA_FORMAT_R32G32_UINT;
+	}
+
 	// Encoding table setup.
-	if (dest_format == Image::FORMAT_DXT1 && dxt1_encoding_table_buffer.is_null()) {
+	if ((dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) && dxt1_encoding_table_buffer.is_null()) {
 		Vector<uint8_t> data;
 		data.resize(1024 * 4);
 		memcpy(data.ptrw(), dxt1_encoding_table, 1024 * 4);
@@ -421,94 +470,189 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
 
 		// Create the textures on the GPU.
 		RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);
-		RID dst_texture = compress_rd->texture_create(dst_texture_format, RD::TextureView());
+		RID dst_texture_primary = compress_rd->texture_create(dst_texture_format, RD::TextureView());
 
-		Vector<RD::Uniform> uniforms;
 		{
+			Vector<RD::Uniform> uniforms;
 			{
-				RD::Uniform u;
-				u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
-				u.binding = 0;
-				u.append_id(src_sampler);
-				u.append_id(src_texture);
-				uniforms.push_back(u);
-			}
-			{
-				RD::Uniform u;
-				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
-				u.binding = 1;
-				u.append_id(dst_texture);
-				uniforms.push_back(u);
+				{
+					RD::Uniform u;
+					u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+					u.binding = 0;
+					u.append_id(src_sampler);
+					u.append_id(src_texture);
+					uniforms.push_back(u);
+				}
+				{
+					RD::Uniform u;
+					u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+					u.binding = 1;
+					u.append_id(dst_texture_primary);
+					uniforms.push_back(u);
+				}
+
+				if (dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) {
+					RD::Uniform u;
+					u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+					u.binding = 2;
+					u.append_id(dxt1_encoding_table_buffer);
+					uniforms.push_back(u);
+				}
 			}
 
-			if (dest_format == Image::FORMAT_DXT1) {
-				RD::Uniform u;
-				u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
-				u.binding = 2;
-				u.append_id(dxt1_encoding_table_buffer);
-				uniforms.push_back(u);
+			RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
+			RD::ComputeListID compute_list = compress_rd->compute_list_begin();
+
+			compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
+			compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
+
+			switch (shader_type) {
+				case BETSY_SHADER_BC6_SIGNED:
+				case BETSY_SHADER_BC6_UNSIGNED: {
+					BC6PushConstant push_constant;
+					push_constant.sizeX = 1.0f / width;
+					push_constant.sizeY = 1.0f / height;
+
+					compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
+					compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+				} break;
+
+				case BETSY_SHADER_BC1_STANDARD: {
+					BC1PushConstant push_constant;
+					push_constant.num_refines = 2;
+
+					compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
+					compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+				} break;
+
+				case BETSY_SHADER_BC4_UNSIGNED: {
+					BC4PushConstant push_constant;
+					push_constant.channel_idx = 0;
+
+					compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
+					compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
+				} break;
+
+				default: {
+				} break;
 			}
-		}
 
-		RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
-		RD::ComputeListID compute_list = compress_rd->compute_list_begin();
+			compress_rd->compute_list_end();
 
-		compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
-		compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
+			if (!needs_alpha_block) {
+				compress_rd->submit();
+				compress_rd->sync();
+			}
+		}
 
-		switch (dest_format) {
-			case Image::FORMAT_BPTC_RGBFU:
-			case Image::FORMAT_BPTC_RGBF: {
-				BC6PushConstant push_constant;
-				push_constant.sizeX = 1.0f / width;
-				push_constant.sizeY = 1.0f / height;
-				push_constant.padding[0] = 0;
-				push_constant.padding[1] = 0;
+		RID *dst_texture_rid = &dst_texture_primary;
 
-				compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
-				compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
-			} break;
+		if (needs_alpha_block) {
+			// Set the destination texture width and size.
+			dst_texture_format_alpha.height = (height + 3) >> 2;
+			dst_texture_format_alpha.width = (width + 3) >> 2;
 
-			case Image::FORMAT_DXT1: {
-				BC1PushConstant push_constant;
-				push_constant.num_refines = 2;
-				push_constant.padding[0] = 0;
-				push_constant.padding[1] = 0;
-				push_constant.padding[2] = 0;
+			RID dst_texture_alpha = compress_rd->texture_create(dst_texture_format_alpha, RD::TextureView());
 
-				compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
-				compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
-			} break;
+			{
+				Vector<RD::Uniform> uniforms;
+				{
+					{
+						RD::Uniform u;
+						u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+						u.binding = 0;
+						u.append_id(src_sampler);
+						u.append_id(src_texture);
+						uniforms.push_back(u);
+					}
+					{
+						RD::Uniform u;
+						u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+						u.binding = 1;
+						u.append_id(dst_texture_alpha);
+						uniforms.push_back(u);
+					}
+				}
+
+				RID uniform_set = compress_rd->uniform_set_create(uniforms, secondary_shader.compiled, 0);
+				RD::ComputeListID compute_list = compress_rd->compute_list_begin();
+
+				compress_rd->compute_list_bind_compute_pipeline(compute_list, secondary_shader.pipeline);
+				compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
 
-			case Image::FORMAT_RGTC_R: {
 				BC4PushConstant push_constant;
-				push_constant.channel_idx = 0;
-				push_constant.padding[0] = 0;
-				push_constant.padding[1] = 0;
-				push_constant.padding[2] = 0;
+				push_constant.channel_idx = dest_format == Image::FORMAT_DXT5 ? 3 : 1;
 
 				compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));
 				compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);
-			} break;
 
-			default: {
-			} break;
-		}
+				compress_rd->compute_list_end();
+			}
 
-		compress_rd->compute_list_end();
+			// Stitching
 
-		compress_rd->submit();
-		compress_rd->sync();
+			// Set the destination texture width and size.
+			dst_texture_format_combined.height = (height + 3) >> 2;
+			dst_texture_format_combined.width = (width + 3) >> 2;
+
+			RID dst_texture_combined = compress_rd->texture_create(dst_texture_format_combined, RD::TextureView());
+
+			{
+				Vector<RD::Uniform> uniforms;
+				{
+					{
+						RD::Uniform u;
+						u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+						u.binding = 0;
+						u.append_id(src_sampler);
+						u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_alpha : dst_texture_primary);
+						uniforms.push_back(u);
+					}
+					{
+						RD::Uniform u;
+						u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+						u.binding = 1;
+						u.append_id(src_sampler);
+						u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_primary : dst_texture_alpha);
+						uniforms.push_back(u);
+					}
+					{
+						RD::Uniform u;
+						u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+						u.binding = 2;
+						u.append_id(dst_texture_combined);
+						uniforms.push_back(u);
+					}
+				}
+
+				RID uniform_set = compress_rd->uniform_set_create(uniforms, stitch_shader.compiled, 0);
+				RD::ComputeListID compute_list = compress_rd->compute_list_begin();
+
+				compress_rd->compute_list_bind_compute_pipeline(compute_list, stitch_shader.pipeline);
+				compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
+				compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+
+				compress_rd->compute_list_end();
+
+				compress_rd->submit();
+				compress_rd->sync();
+			}
+
+			dst_texture_rid = &dst_texture_combined;
+
+			compress_rd->free(dst_texture_primary);
+			compress_rd->free(dst_texture_alpha);
+		}
 
 		// Copy data from the GPU to the buffer.
-		const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture, 0);
+		const Vector<uint8_t> texture_data = compress_rd->texture_get_data(*dst_texture_rid, 0);
 		int64_t dst_ofs = Image::get_image_mipmap_offset(r_img->get_width(), r_img->get_height(), dest_format, i);
 
 		memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());
 
 		// Free the source and dest texture.
-		compress_rd->free(dst_texture);
 		compress_rd->free(src_texture);
+		compress_rd->free(*dst_texture_rid);
 	}
 
 	src_images.clear();
@@ -516,7 +660,13 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
 	// Set the compressed data to the image.
 	r_img->set_data(r_img->get_width(), r_img->get_height(), r_img->has_mipmaps(), dest_format, dst_data);
 
-	print_verbose(vformat("Betsy: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
+	print_verbose(
+			vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",
+					r_img->get_width(),
+					r_img->get_height(),
+					r_img->get_mipmap_count(),
+					Image::get_format_name(dest_format),
+					OS::get_singleton()->get_ticks_msec() - start_time));
 
 	return OK;
 }
@@ -560,10 +710,19 @@ Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
 			result = betsy->compress(BETSY_FORMAT_BC1, r_img);
 			break;
 
+		case Image::USED_CHANNELS_RGBA:
+		case Image::USED_CHANNELS_LA:
+			result = betsy->compress(BETSY_FORMAT_BC3, r_img);
+			break;
+
 		case Image::USED_CHANNELS_R:
 			result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);
 			break;
 
+		case Image::USED_CHANNELS_RG:
+			result = betsy->compress(BETSY_FORMAT_BC5_UNSIGNED, r_img);
+			break;
+
 		default:
 			break;
 	}

+ 19 - 4
modules/betsy/image_compress_betsy.h

@@ -52,24 +52,38 @@ enum BetsyFormat {
 	BETSY_FORMAT_BC3,
 	BETSY_FORMAT_BC4_SIGNED,
 	BETSY_FORMAT_BC4_UNSIGNED,
+	BETSY_FORMAT_BC5_SIGNED,
+	BETSY_FORMAT_BC5_UNSIGNED,
 	BETSY_FORMAT_BC6_SIGNED,
 	BETSY_FORMAT_BC6_UNSIGNED,
+	BETSY_FORMAT_MAX,
+};
+
+enum BetsyShaderType {
+	BETSY_SHADER_BC1_STANDARD,
+	BETSY_SHADER_BC1_DITHER,
+	BETSY_SHADER_BC4_SIGNED,
+	BETSY_SHADER_BC4_UNSIGNED,
+	BETSY_SHADER_BC6_SIGNED,
+	BETSY_SHADER_BC6_UNSIGNED,
+	BETSY_SHADER_ALPHA_STITCH,
+	BETSY_SHADER_MAX,
 };
 
 struct BC6PushConstant {
 	float sizeX;
 	float sizeY;
-	uint32_t padding[2];
+	uint32_t padding[2] = { 0 };
 };
 
 struct BC1PushConstant {
 	uint32_t num_refines;
-	uint32_t padding[3];
+	uint32_t padding[3] = { 0 };
 };
 
 struct BC4PushConstant {
 	uint32_t channel_idx;
-	uint32_t padding[3];
+	uint32_t padding[3] = { 0 };
 };
 
 void free_device();
@@ -90,7 +104,7 @@ class BetsyCompressor : public Object {
 	// Resources shared by all compression formats.
 	RenderingDevice *compress_rd = nullptr;
 	RenderingContextDriver *compress_rcd = nullptr;
-	HashMap<String, BetsyShader> cached_shaders;
+	BetsyShader cached_shaders[BETSY_SHADER_MAX];
 	RID src_sampler;
 
 	// Format-specific resources.
@@ -101,6 +115,7 @@ class BetsyCompressor : public Object {
 	void _thread_loop();
 	void _thread_exit();
 
+	Error _get_shader(BetsyFormat p_format, const String &p_version, BetsyShader &r_shader);
 	Error _compress(BetsyFormat p_format, Image *r_img);
 
 public: