Path: blob/master/modules/betsy/image_compress_betsy.cpp
11352 views
/**************************************************************************/1/* image_compress_betsy.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "image_compress_betsy.h"3132#include "core/config/project_settings.h"3334#include "betsy_bc1.h"3536#include "alpha_stitch.glsl.gen.h"37#include "bc1.glsl.gen.h"38#include "bc4.glsl.gen.h"39#include "bc6h.glsl.gen.h"40#include "servers/display/display_server.h"4142static Mutex betsy_mutex;43static BetsyCompressor *betsy = nullptr;4445static const BetsyShaderType FORMAT_TO_TYPE[BETSY_FORMAT_MAX] = {46BETSY_SHADER_BC1_STANDARD,47BETSY_SHADER_BC1_DITHER,48BETSY_SHADER_BC1_STANDARD,49BETSY_SHADER_BC4_SIGNED,50BETSY_SHADER_BC4_UNSIGNED,51BETSY_SHADER_BC4_SIGNED,52BETSY_SHADER_BC4_UNSIGNED,53BETSY_SHADER_BC6_SIGNED,54BETSY_SHADER_BC6_UNSIGNED,55};5657static const RD::DataFormat BETSY_TO_RD_FORMAT[BETSY_FORMAT_MAX] = {58RD::DATA_FORMAT_R32G32_UINT,59RD::DATA_FORMAT_R32G32_UINT,60RD::DATA_FORMAT_R32G32_UINT,61RD::DATA_FORMAT_R32G32_UINT,62RD::DATA_FORMAT_R32G32_UINT,63RD::DATA_FORMAT_R32G32_UINT,64RD::DATA_FORMAT_R32G32_UINT,65RD::DATA_FORMAT_R32G32B32A32_UINT,66RD::DATA_FORMAT_R32G32B32A32_UINT,67};6869static const Image::Format BETSY_TO_IMAGE_FORMAT[BETSY_FORMAT_MAX] = {70Image::FORMAT_DXT1,71Image::FORMAT_DXT1,72Image::FORMAT_DXT5,73Image::FORMAT_RGTC_R,74Image::FORMAT_RGTC_R,75Image::FORMAT_RGTC_RG,76Image::FORMAT_RGTC_RG,77Image::FORMAT_BPTC_RGBF,78Image::FORMAT_BPTC_RGBFU,79};8081void BetsyCompressor::_init() {82if (!DisplayServer::can_create_rendering_device()) {83return;84}8586// Create local RD.87RenderingContextDriver *rcd = nullptr;88RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();8990if (rd == nullptr) {91#if defined(RD_ENABLED)92#if defined(METAL_ENABLED)93rcd = memnew(RenderingContextDriverMetal);94rd = memnew(RenderingDevice);95#endif96#if defined(VULKAN_ENABLED)97if (rcd == nullptr) {98rcd = memnew(RenderingContextDriverVulkan);99rd = memnew(RenderingDevice);100}101#endif102#endif103if (rcd != nullptr && rd != nullptr) {104Error err = rcd->initialize();105if (err == OK) {106err = rd->initialize(rcd);107}108109if (err != OK) {110memdelete(rd);111memdelete(rcd);112rd = nullptr;113rcd = nullptr;114}115}116}117118ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice.");119120compress_rd = rd;121compress_rcd = rcd;122123// Create the sampler state.124RD::SamplerState src_sampler_state;125{126src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;127src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;128src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;129src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;130src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;131}132133src_sampler = compress_rd->sampler_create(src_sampler_state);134135// Initialize RDShaderFiles.136{137Ref<RDShaderFile> bc1_shader;138bc1_shader.instantiate();139Error err = bc1_shader->parse_versions_from_text(bc1_shader_glsl);140141if (err != OK) {142bc1_shader->print_errors("Betsy BC1 compress shader");143}144145// Standard BC1 compression.146cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("standard"));147ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled.is_null());148149cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_STANDARD].compiled);150ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_STANDARD].pipeline.is_null());151152// Dither BC1 variant. Unused, so comment out for now.153//cached_shaders[BETSY_SHADER_BC1_DITHER].compiled = compress_rd->shader_create_from_spirv(bc1_shader->get_spirv_stages("dithered"));154//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled.is_null());155156//cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC1_DITHER].compiled);157//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC1_DITHER].pipeline.is_null());158}159160{161Ref<RDShaderFile> bc4_shader;162bc4_shader.instantiate();163Error err = bc4_shader->parse_versions_from_text(bc4_shader_glsl);164165if (err != OK) {166bc4_shader->print_errors("Betsy BC4 compress shader");167}168169// Signed BC4 compression. Unused, so comment out for now.170//cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("signed"));171//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled.is_null());172173//cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_SIGNED].compiled);174//ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_SIGNED].pipeline.is_null());175176// Unsigned BC4 compression.177cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc4_shader->get_spirv_stages("unsigned"));178ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled.is_null());179180cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].compiled);181ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC4_UNSIGNED].pipeline.is_null());182}183184{185Ref<RDShaderFile> bc6h_shader;186bc6h_shader.instantiate();187Error err = bc6h_shader->parse_versions_from_text(bc6h_shader_glsl);188189if (err != OK) {190bc6h_shader->print_errors("Betsy BC6 compress shader");191}192193// Signed BC6 compression.194cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("signed"));195ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled.is_null());196197cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_SIGNED].compiled);198ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_SIGNED].pipeline.is_null());199200// Unsigned BC6 compression.201cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled = compress_rd->shader_create_from_spirv(bc6h_shader->get_spirv_stages("unsigned"));202ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled.is_null());203204cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].compiled);205ERR_FAIL_COND(cached_shaders[BETSY_SHADER_BC6_UNSIGNED].pipeline.is_null());206}207208{209Ref<RDShaderFile> alpha_stitch_shader;210alpha_stitch_shader.instantiate();211Error err = alpha_stitch_shader->parse_versions_from_text(alpha_stitch_shader_glsl);212213if (err != OK) {214alpha_stitch_shader->print_errors("Betsy alpha stitch shader");215}216cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled = compress_rd->shader_create_from_spirv(alpha_stitch_shader->get_spirv_stages());217ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled.is_null());218219cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline = compress_rd->compute_pipeline_create(cached_shaders[BETSY_SHADER_ALPHA_STITCH].compiled);220ERR_FAIL_COND(cached_shaders[BETSY_SHADER_ALPHA_STITCH].pipeline.is_null());221}222}223224void BetsyCompressor::init() {225WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true, "Betsy pump task", true);226command_queue.set_pump_task_id(tid);227command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid);228command_queue.push_and_sync(this, &BetsyCompressor::_init);229DEV_ASSERT(task_id == tid);230}231232void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {233task_id = p_pump_task_id;234}235236// Yield thread to WTP so other tasks can be done on it.237// Automatically regains control as soon a task is pushed to the command queue.238void BetsyCompressor::_thread_loop() {239while (!exit) {240WorkerThreadPool::get_singleton()->yield();241command_queue.flush_all();242}243}244245void BetsyCompressor::_thread_exit() {246exit = true;247248if (compress_rd != nullptr) {249if (dxt1_encoding_table_buffer.is_valid()) {250compress_rd->free_rid(dxt1_encoding_table_buffer);251}252253compress_rd->free_rid(src_sampler);254255// Clear the shader cache, pipelines will be unreferenced automatically.256for (int i = 0; i < BETSY_SHADER_MAX; i++) {257if (cached_shaders[i].compiled.is_valid()) {258compress_rd->free_rid(cached_shaders[i].compiled);259}260}261262// Free the RD (and RCD if necessary).263memdelete(compress_rd);264compress_rd = nullptr;265if (compress_rcd != nullptr) {266memdelete(compress_rcd);267compress_rcd = nullptr;268}269}270}271272void BetsyCompressor::finish() {273command_queue.push(this, &BetsyCompressor::_thread_exit);274if (task_id != WorkerThreadPool::INVALID_TASK_ID) {275WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);276task_id = WorkerThreadPool::INVALID_TASK_ID;277}278}279280// Helper functions.281282static int get_next_multiple(int n, int m) {283return n + (m - (n % m));284}285286static Error get_src_texture_format(Image *r_img, RD::DataFormat &r_format) {287switch (r_img->get_format()) {288case Image::FORMAT_L8:289r_img->convert(Image::FORMAT_RGBA8);290r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;291break;292293case Image::FORMAT_LA8:294r_img->convert(Image::FORMAT_RGBA8);295r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;296break;297298case Image::FORMAT_R8:299r_format = RD::DATA_FORMAT_R8_UNORM;300break;301302case Image::FORMAT_RG8:303r_format = RD::DATA_FORMAT_R8G8_UNORM;304break;305306case Image::FORMAT_RGB8:307r_img->convert(Image::FORMAT_RGBA8);308r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;309break;310311case Image::FORMAT_RGBA8:312r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;313break;314315case Image::FORMAT_RH:316r_format = RD::DATA_FORMAT_R16_SFLOAT;317break;318319case Image::FORMAT_RGH:320r_format = RD::DATA_FORMAT_R16G16_SFLOAT;321break;322323case Image::FORMAT_RGBH:324r_img->convert(Image::FORMAT_RGBAH);325r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;326break;327328case Image::FORMAT_RGBAH:329r_format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;330break;331332case Image::FORMAT_RF:333r_format = RD::DATA_FORMAT_R32_SFLOAT;334break;335336case Image::FORMAT_RGF:337r_format = RD::DATA_FORMAT_R32G32_SFLOAT;338break;339340case Image::FORMAT_RGBF:341r_img->convert(Image::FORMAT_RGBAF);342r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;343break;344345case Image::FORMAT_RGBAF:346r_format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT;347break;348349case Image::FORMAT_RGBE9995:350r_format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32;351break;352353case Image::FORMAT_R16:354r_format = RD::DATA_FORMAT_R16_UNORM;355break;356357case Image::FORMAT_RG16:358r_format = RD::DATA_FORMAT_R16G16_UNORM;359break;360361case Image::FORMAT_RGB16:362r_img->convert(Image::FORMAT_RGBA16);363r_format = RD::DATA_FORMAT_R16G16B16A16_UNORM;364break;365366case Image::FORMAT_RGBA16:367r_format = RD::DATA_FORMAT_R16G16B16A16_UNORM;368break;369370case Image::FORMAT_R16I:371r_format = RD::DATA_FORMAT_R16_UINT;372break;373374case Image::FORMAT_RG16I:375r_format = RD::DATA_FORMAT_R16G16_UINT;376break;377378case Image::FORMAT_RGB16I:379r_img->convert(Image::FORMAT_RGBA16I);380r_format = RD::DATA_FORMAT_R16G16B16A16_UINT;381break;382383case Image::FORMAT_RGBA16I:384r_format = RD::DATA_FORMAT_R16G16B16A16_UINT;385break;386387default: {388return ERR_UNAVAILABLE;389}390}391392return OK;393}394395Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {396uint64_t start_time = OS::get_singleton()->get_ticks_msec();397398// Return an error so that the compression can fall back to cpu compression399if (compress_rd == nullptr) {400return ERR_CANT_CREATE;401}402403if (r_img->is_compressed()) {404return ERR_INVALID_DATA;405}406407int img_width = r_img->get_width();408int img_height = r_img->get_height();409if (img_width % 4 != 0 || img_height % 4 != 0) {410img_width = img_width <= 2 ? img_width : (img_width + 3) & ~3;411img_height = img_height <= 2 ? img_height : (img_height + 3) & ~3;412}413414Error err = OK;415416// Destination format.417Image::Format dest_format = BETSY_TO_IMAGE_FORMAT[p_format];418RD::DataFormat dst_rd_format = BETSY_TO_RD_FORMAT[p_format];419420BetsyShaderType shader_type = FORMAT_TO_TYPE[p_format];421BetsyShader shader = cached_shaders[shader_type];422BetsyShader 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).423BetsyShader stitch_shader;424bool needs_alpha_block = false;425426switch (p_format) {427case BETSY_FORMAT_BC3:428case BETSY_FORMAT_BC5_UNSIGNED:429needs_alpha_block = true;430secondary_shader = cached_shaders[BETSY_SHADER_BC4_UNSIGNED];431stitch_shader = cached_shaders[BETSY_SHADER_ALPHA_STITCH];432break;433default:434break;435}436437// src_texture format information.438RD::TextureFormat src_texture_format;439{440src_texture_format.array_layers = 1;441src_texture_format.depth = 1;442src_texture_format.mipmaps = 1;443src_texture_format.texture_type = RD::TEXTURE_TYPE_2D;444src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;445}446447err = get_src_texture_format(r_img, src_texture_format.format);448449if (err != OK) {450return err;451}452453// For the destination format just copy the source format and change the usage bits.454RD::TextureFormat dst_texture_format = src_texture_format;455dst_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;456dst_texture_format.format = dst_rd_format;457458RD::TextureFormat dst_texture_format_alpha;459RD::TextureFormat dst_texture_format_combined;460461if (needs_alpha_block) {462dst_texture_format_combined = dst_texture_format;463dst_texture_format_combined.format = RD::DATA_FORMAT_R32G32B32A32_UINT;464465dst_texture_format.usage_bits |= RD::TEXTURE_USAGE_SAMPLING_BIT;466467dst_texture_format_alpha = dst_texture_format;468dst_texture_format_alpha.format = RD::DATA_FORMAT_R32G32_UINT;469}470471// Encoding table setup.472if ((dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) && dxt1_encoding_table_buffer.is_null()) {473dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, Span(dxt1_encoding_table).reinterpret<uint8_t>());474}475476const int mip_count = r_img->get_mipmap_count() + 1;477478// Container for the compressed data.479Vector<uint8_t> dst_data;480dst_data.resize(Image::get_image_data_size(img_width, img_height, dest_format, r_img->has_mipmaps()));481uint8_t *dst_data_ptr = dst_data.ptrw();482483Vector<Vector<uint8_t>> src_images;484src_images.push_back(Vector<uint8_t>());485Vector<uint8_t> *src_image_ptr = src_images.ptrw();486487// Compress each mipmap.488for (int i = 0; i < mip_count; i++) {489int width, height;490Image::get_image_mipmap_offset_and_dimensions(img_width, img_height, dest_format, i, width, height);491492int64_t src_mip_ofs, src_mip_size;493int src_mip_w, src_mip_h;494r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);495496// Set the source texture width and size.497src_texture_format.height = height;498src_texture_format.width = width;499500// Set the destination texture width and size.501dst_texture_format.height = (height + 3) >> 2;502dst_texture_format.width = (width + 3) >> 2;503504// Pad textures to nearest block by smearing.505if (width != src_mip_w || height != src_mip_h) {506const uint8_t *src_mip_read = r_img->ptr() + src_mip_ofs;507508// Reserve the buffer for padded image data.509int px_size = Image::get_format_pixel_size(r_img->get_format());510src_image_ptr[0].resize(width * height * px_size);511uint8_t *ptrw = src_image_ptr[0].ptrw();512513int x = 0, y = 0;514for (y = 0; y < src_mip_h; y++) {515for (x = 0; x < src_mip_w; x++) {516memcpy(ptrw + (width * y + x) * px_size, src_mip_read + (src_mip_w * y + x) * px_size, px_size);517}518519// First, smear in x.520for (; x < width; x++) {521memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - 1) * px_size, px_size);522}523}524525// Then, smear in y.526for (; y < height; y++) {527for (x = 0; x < width; x++) {528memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - width) * px_size, px_size);529}530}531} else {532// Create a buffer filled with the source mip layer data.533src_image_ptr[0].resize(src_mip_size);534memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + src_mip_ofs, src_mip_size);535}536537// Create the textures on the GPU.538RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);539RID dst_texture_primary = compress_rd->texture_create(dst_texture_format, RD::TextureView());540541{542Vector<RD::Uniform> uniforms;543{544{545RD::Uniform u;546u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;547u.binding = 0;548u.append_id(src_sampler);549u.append_id(src_texture);550uniforms.push_back(u);551}552{553RD::Uniform u;554u.uniform_type = RD::UNIFORM_TYPE_IMAGE;555u.binding = 1;556u.append_id(dst_texture_primary);557uniforms.push_back(u);558}559560if (dest_format == Image::FORMAT_DXT1 || dest_format == Image::FORMAT_DXT5) {561RD::Uniform u;562u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;563u.binding = 2;564u.append_id(dxt1_encoding_table_buffer);565uniforms.push_back(u);566}567}568569RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);570RD::ComputeListID compute_list = compress_rd->compute_list_begin();571572compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);573compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);574575switch (shader_type) {576case BETSY_SHADER_BC6_SIGNED:577case BETSY_SHADER_BC6_UNSIGNED: {578BC6PushConstant push_constant;579push_constant.sizeX = 1.0f / width;580push_constant.sizeY = 1.0f / height;581582compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));583compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);584} break;585586case BETSY_SHADER_BC1_STANDARD: {587BC1PushConstant push_constant;588push_constant.num_refines = 2;589590compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));591compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);592} break;593594case BETSY_SHADER_BC4_UNSIGNED: {595BC4PushConstant push_constant;596push_constant.channel_idx = 0;597598compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));599compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);600} break;601602default: {603} break;604}605606compress_rd->compute_list_end();607608if (!needs_alpha_block) {609compress_rd->submit();610compress_rd->sync();611}612}613614RID dst_texture_rid = dst_texture_primary;615616if (needs_alpha_block) {617// Set the destination texture width and size.618dst_texture_format_alpha.height = (height + 3) >> 2;619dst_texture_format_alpha.width = (width + 3) >> 2;620621RID dst_texture_alpha = compress_rd->texture_create(dst_texture_format_alpha, RD::TextureView());622623{624Vector<RD::Uniform> uniforms;625{626{627RD::Uniform u;628u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;629u.binding = 0;630u.append_id(src_sampler);631u.append_id(src_texture);632uniforms.push_back(u);633}634{635RD::Uniform u;636u.uniform_type = RD::UNIFORM_TYPE_IMAGE;637u.binding = 1;638u.append_id(dst_texture_alpha);639uniforms.push_back(u);640}641}642643RID uniform_set = compress_rd->uniform_set_create(uniforms, secondary_shader.compiled, 0);644RD::ComputeListID compute_list = compress_rd->compute_list_begin();645646compress_rd->compute_list_bind_compute_pipeline(compute_list, secondary_shader.pipeline);647compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);648649BC4PushConstant push_constant;650push_constant.channel_idx = dest_format == Image::FORMAT_DXT5 ? 3 : 1;651652compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC4PushConstant));653compress_rd->compute_list_dispatch(compute_list, 1, get_next_multiple(width, 16) / 16, get_next_multiple(height, 16) / 16);654655compress_rd->compute_list_end();656}657658// Stitching659660// Set the destination texture width and size.661dst_texture_format_combined.height = (height + 3) >> 2;662dst_texture_format_combined.width = (width + 3) >> 2;663664RID dst_texture_combined = compress_rd->texture_create(dst_texture_format_combined, RD::TextureView());665666{667Vector<RD::Uniform> uniforms;668{669{670RD::Uniform u;671u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;672u.binding = 0;673u.append_id(src_sampler);674u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_alpha : dst_texture_primary);675uniforms.push_back(u);676}677{678RD::Uniform u;679u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;680u.binding = 1;681u.append_id(src_sampler);682u.append_id(dest_format == Image::FORMAT_DXT5 ? dst_texture_primary : dst_texture_alpha);683uniforms.push_back(u);684}685{686RD::Uniform u;687u.uniform_type = RD::UNIFORM_TYPE_IMAGE;688u.binding = 2;689u.append_id(dst_texture_combined);690uniforms.push_back(u);691}692}693694RID uniform_set = compress_rd->uniform_set_create(uniforms, stitch_shader.compiled, 0);695RD::ComputeListID compute_list = compress_rd->compute_list_begin();696697compress_rd->compute_list_bind_compute_pipeline(compute_list, stitch_shader.pipeline);698compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);699compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);700701compress_rd->compute_list_end();702703compress_rd->submit();704compress_rd->sync();705}706707dst_texture_rid = dst_texture_combined;708709compress_rd->free_rid(dst_texture_primary);710compress_rd->free_rid(dst_texture_alpha);711}712713// Copy data from the GPU to the buffer.714const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture_rid, 0);715int64_t dst_ofs = Image::get_image_mipmap_offset(img_width, img_height, dest_format, i);716717memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());718719// Free the source and dest texture.720compress_rd->free_rid(src_texture);721compress_rd->free_rid(dst_texture_rid);722}723724src_images.clear();725726// Set the compressed data to the image.727r_img->set_data(img_width, img_height, r_img->has_mipmaps(), dest_format, dst_data);728729print_verbose(730vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",731img_width,732img_height,733r_img->get_mipmap_count(),734Image::get_format_name(dest_format),735OS::get_singleton()->get_ticks_msec() - start_time));736737return OK;738}739740void ensure_betsy_exists() {741betsy_mutex.lock();742if (betsy == nullptr) {743betsy = memnew(BetsyCompressor);744betsy->init();745}746betsy_mutex.unlock();747}748749Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) {750ensure_betsy_exists();751Image::Format format = r_img->get_format();752Error result = ERR_UNAVAILABLE;753754if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) {755if (r_img->detect_signed()) {756result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img);757} else {758result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img);759}760}761762if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {763free_device();764}765766return result;767}768769Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {770ensure_betsy_exists();771Error result = ERR_UNAVAILABLE;772773switch (p_channels) {774case Image::USED_CHANNELS_RGB:775case Image::USED_CHANNELS_L:776result = betsy->compress(BETSY_FORMAT_BC1, r_img);777break;778779case Image::USED_CHANNELS_RGBA:780case Image::USED_CHANNELS_LA:781result = betsy->compress(BETSY_FORMAT_BC3, r_img);782break;783784case Image::USED_CHANNELS_R:785result = betsy->compress(BETSY_FORMAT_BC4_UNSIGNED, r_img);786break;787788case Image::USED_CHANNELS_RG:789result = betsy->compress(BETSY_FORMAT_BC5_UNSIGNED, r_img);790break;791792default:793break;794}795796if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {797free_device();798}799800return result;801}802803void free_device() {804if (betsy != nullptr) {805betsy->finish();806memdelete(betsy);807}808}809810811