2
0
mirror of https://github.com/godotengine/godot.git synced 2025-04-25 01:48:08 +08:00

Merge pull request from BlueCube3310/betsy-bc1-bc5

Betsy: Add BC3 and BC5 support
This commit is contained in:
Rémi Verschelde 2024-11-29 22:02:06 +01:00
commit f4f6f5fb55
No known key found for this signature in database
GPG Key ID: C3336907360768E1
5 changed files with 441 additions and 243 deletions

@ -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.

@ -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

@ -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));
}

@ -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,26 +286,79 @@ 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";
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);
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case BETSY_FORMAT_BC3:
return "BC3";
case Image::FORMAT_LA8:
r_img->convert(Image::FORMAT_RGBA8);
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case BETSY_FORMAT_BC4_SIGNED:
case BETSY_FORMAT_BC4_UNSIGNED:
return "BC4";
case Image::FORMAT_R8:
r_format = RD::DATA_FORMAT_R8_UNORM;
break;
case BETSY_FORMAT_BC6_SIGNED:
case BETSY_FORMAT_BC6_UNSIGNED:
return "BC6";
case Image::FORMAT_RG8:
r_format = RD::DATA_FORMAT_R8G8_UNORM;
break;
default:
return "";
case Image::FORMAT_RGB8:
r_img->convert(Image::FORMAT_RGBA8);
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case Image::FORMAT_RGBA8:
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case Image::FORMAT_RH:
r_format = RD::DATA_FORMAT_R16_SFLOAT;
break;
case Image::FORMAT_RGH:
r_format = RD::DATA_FORMAT_R16G16_SFLOAT;
break;
case Image::FORMAT_RGBH:
r_img->convert(Image::FORMAT_RGBAH);
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
break;
case Image::FORMAT_RGBAH:
r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
break;
case Image::FORMAT_RF:
r_format = RD::DATA_FORMAT_R32_SFLOAT;
break;
case Image::FORMAT_RGF:
r_format = RD::DATA_FORMAT_R32G32_SFLOAT;
break;
case Image::FORMAT_RGBF:
r_img->convert(Image::FORMAT_RGBAF);
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
break;
case Image::FORMAT_RGBAF:
r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
break;
case Image::FORMAT_RGBE9995:
r_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
break;
default: {
return ERR_UNAVAILABLE;
}
}
return OK;
}
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
@ -199,102 +376,26 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
Error err = OK;
// Destination format.
Image::Format dest_format = Image::FORMAT_MAX;
RD::DataFormat dst_rd_format = RD::DATA_FORMAT_MAX;
Image::Format dest_format = BETSY_TO_IMAGE_FORMAT[p_format];
RD::DataFormat dst_rd_format = BETSY_TO_RD_FORMAT[p_format];
String version = "";
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_BC1:
version = "standard";
dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
dest_format = Image::FORMAT_DXT1;
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;
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;
{
@ -305,75 +406,10 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
}
switch (r_img->get_format()) {
case Image::FORMAT_L8:
r_img->convert(Image::FORMAT_RGBA8);
src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
err = get_src_texture_format(r_img, src_texture_format.format);
case Image::FORMAT_LA8:
r_img->convert(Image::FORMAT_RGBA8);
src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case Image::FORMAT_R8:
src_texture_format.format = RD::DATA_FORMAT_R8_UNORM;
break;
case Image::FORMAT_RG8:
src_texture_format.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;
break;
case Image::FORMAT_RGBA8:
src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case Image::FORMAT_RH:
src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT;
break;
case Image::FORMAT_RGH:
src_texture_format.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;
break;
case Image::FORMAT_RGBAH:
src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
break;
case Image::FORMAT_RF:
src_texture_format.format = RD::DATA_FORMAT_R32_SFLOAT;
break;
case Image::FORMAT_RGF:
src_texture_format.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;
break;
case Image::FORMAT_RGBAF:
src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;
break;
case Image::FORMAT_RGBE9995:
src_texture_format.format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;
break;
default: {
return err;
}
if (err != OK) {
return err;
}
// For the destination format just copy the source format and change the usage bits.
@ -381,8 +417,21 @@ Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
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;
}
compress_rd->compute_list_end();
if (!needs_alpha_block) {
compress_rd->submit();
compress_rd->sync();
}
}
RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
RD::ComputeListID compute_list = compress_rd->compute_list_begin();
RID *dst_texture_rid = &dst_texture_primary;
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) {
// Set the destination texture width and size.
dst_texture_format_alpha.height = (height + 3) >> 2;
dst_texture_format_alpha.width = (width + 3) >> 2;
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_alpha = compress_rd->texture_create(dst_texture_format_alpha, RD::TextureView());
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;
{
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);
}
}
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 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_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;
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();
}
// Stitching
// 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);
}
compress_rd->compute_list_end();
compress_rd->submit();
compress_rd->sync();
// 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;
}

@ -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: