initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

63
modules/SCsub Normal file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
import os
import modules_builders
Import("env")
env_modules = env.Clone()
# Allow modules to detect if they are being built as a module.
env_modules.Append(CPPDEFINES=["GODOT_MODULE"])
Export("env_modules")
# Header with MODULE_*_ENABLED defines.
modules_enabled = env.CommandNoCache(
"modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_builders.modules_enabled_builder)
)
register_module_types = env.CommandNoCache(
"register_module_types.gen.cpp",
[env.Value(env.modules_detected), modules_enabled],
env.Run(modules_builders.register_module_types_builder),
)
test_headers = []
# libmodule_<name>.a for each active module.
for name, path in env.module_list.items():
env.modules_sources = []
# Name for built-in modules, (absolute) path for custom ones.
base_path = path if os.path.isabs(path) else name
SConscript(base_path + "/SCsub")
if env.modules_sources:
lib = env_modules.add_library("module_%s" % name, env.modules_sources)
env.Prepend(LIBS=[lib])
if env["tests"]:
# Lookup potential headers in `tests` subfolder.
import glob
module_tests = sorted(glob.glob(os.path.join(base_path, "tests", "*.h")))
if module_tests != []:
test_headers += module_tests
# Generate header to be included in `tests/test_main.cpp` to run module-specific tests.
if env["tests"]:
env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_builders.modules_tests_builder))
# libmodules.a with only register_module_types.
# Must be last so that all libmodule_<name>.a libraries are on the right side
# in the linker command.
env.modules_sources = []
env_modules.add_source_files(env.modules_sources, register_module_types)
lib = env_modules.add_library("modules", env.modules_sources)
env.Prepend(LIBS=[lib])

62
modules/astcenc/SCsub Normal file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_astcenc = env_modules.Clone()
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/astcenc/"
thirdparty_sources = [
"astcenc_averages_and_directions.cpp",
"astcenc_block_sizes.cpp",
"astcenc_color_quantize.cpp",
"astcenc_color_unquantize.cpp",
"astcenc_compress_symbolic.cpp",
"astcenc_compute_variance.cpp",
"astcenc_decompress_symbolic.cpp",
"astcenc_diagnostic_trace.cpp",
"astcenc_entry.cpp",
"astcenc_find_best_partitioning.cpp",
"astcenc_ideal_endpoints_and_weights.cpp",
"astcenc_image.cpp",
"astcenc_integer_sequence.cpp",
"astcenc_mathlib.cpp",
"astcenc_mathlib_softfloat.cpp",
"astcenc_partition_tables.cpp",
"astcenc_percentile_tables.cpp",
"astcenc_pick_best_endpoint_format.cpp",
"astcenc_quantization.cpp",
"astcenc_symbolic_physical.cpp",
"astcenc_weight_align.cpp",
"astcenc_weight_quant_xfer_tables.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_astcenc.Prepend(CPPEXTPATH=[thirdparty_dir])
env_thirdparty = env_astcenc.Clone()
env_thirdparty.disable_warnings()
# Build the encoder only for editor builds
astc_encoder = env.editor_build
if not astc_encoder:
env_thirdparty.Append(CPPDEFINES=[("ASTCENC_DECOMPRESS_ONLY")])
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_astcenc.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return True
def configure(env):
pass

View File

@@ -0,0 +1,290 @@
/**************************************************************************/
/* image_compress_astcenc.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_compress_astcenc.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include <astcenc.h>
#ifdef TOOLS_ENABLED
void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
const uint64_t start_time = OS::get_singleton()->get_ticks_msec();
if (r_img->is_compressed()) {
return; // Do not compress, already compressed.
}
const Image::Format src_format = r_img->get_format();
const bool is_hdr = src_format >= Image::FORMAT_RF && src_format <= Image::FORMAT_RGBE9995;
if (src_format >= Image::FORMAT_RH && src_format <= Image::FORMAT_RGBAH) {
r_img->convert(Image::FORMAT_RGBAH);
} else if (src_format >= Image::FORMAT_RF && src_format <= Image::FORMAT_RGBE9995) {
r_img->convert(Image::FORMAT_RGBAF);
} else {
r_img->convert(Image::FORMAT_RGBA8);
}
// Determine encoder output format from our enum.
const astcenc_profile profile = is_hdr ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR;
Image::Format target_format = Image::FORMAT_MAX;
unsigned int block_x = 4;
unsigned int block_y = 4;
if (p_format == Image::ASTCFormat::ASTC_FORMAT_4x4) {
if (is_hdr) {
target_format = Image::FORMAT_ASTC_4x4_HDR;
} else {
target_format = Image::FORMAT_ASTC_4x4;
}
} else if (p_format == Image::ASTCFormat::ASTC_FORMAT_8x8) {
if (is_hdr) {
target_format = Image::FORMAT_ASTC_8x8_HDR;
} else {
target_format = Image::FORMAT_ASTC_8x8;
}
block_x = 8;
block_y = 8;
}
// Compress image data and (if required) mipmaps.
const bool has_mipmaps = r_img->has_mipmaps();
int width = r_img->get_width();
int height = r_img->get_height();
int required_width = (width % block_x) != 0 ? width + (block_x - (width % block_x)) : width;
int required_height = (height % block_y) != 0 ? height + (block_y - (height % block_y)) : height;
if (width != required_width || height != required_height) {
// Resize texture to fit block size.
r_img->resize(required_width, required_height);
width = required_width;
height = required_height;
}
print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), has_mipmaps ? ", with mipmaps" : ""));
// Initialize astcenc.
const int64_t dest_size = Image::get_image_data_size(width, height, target_format, has_mipmaps);
Vector<uint8_t> dest_data;
dest_data.resize(dest_size);
uint8_t *dest_write = dest_data.ptrw();
astcenc_config config;
config.block_x = block_x;
config.block_y = block_y;
config.profile = profile;
const float quality = ASTCENC_PRE_MEDIUM;
astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, 0, &config);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
// Context allocation.
astcenc_context *context;
const unsigned int thread_count = 1; // Godot compresses multiple images each on a thread, which is more efficient for large amount of images imported.
status = astcenc_context_alloc(&config, thread_count, &context);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const uint8_t *src_data = r_img->ptr();
for (int i = 0; i < mip_count + 1; i++) {
int src_mip_w, src_mip_h;
const int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h);
const uint8_t *mip_data = &src_data[src_ofs];
const int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
uint8_t *dest_mip_write = &dest_write[dst_ofs];
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
if (unlikely(dst_ofs % 8 != 0)) {
astcenc_context_free(context);
ERR_FAIL_MSG("astcenc: Mip offset is not a multiple of 8.");
}
// Compress image.
astcenc_image image;
image.dim_x = src_mip_w;
image.dim_y = src_mip_h;
image.dim_z = 1;
if (r_img->get_format() == Image::FORMAT_RGBA8) {
image.data_type = ASTCENC_TYPE_U8;
} else if (r_img->get_format() == Image::FORMAT_RGBAH) {
image.data_type = ASTCENC_TYPE_F16;
} else {
image.data_type = ASTCENC_TYPE_F32;
}
image.data = (void **)(&mip_data);
// Compute the number of ASTC blocks in each dimension.
unsigned int block_count_x = (src_mip_w + block_x - 1) / block_x;
unsigned int block_count_y = (src_mip_h + block_y - 1) / block_y;
size_t comp_len = block_count_x * block_count_y * 16;
const astcenc_swizzle swizzle = {
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
};
status = astcenc_compress_image(context, &image, &swizzle, dest_mip_write, comp_len, 0);
ERR_BREAK_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status)));
astcenc_compress_reset(context);
}
astcenc_context_free(context);
// Replace original image with compressed one.
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);
print_verbose(vformat("astcenc: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}
#endif // TOOLS_ENABLED
void _decompress_astc(Image *r_img) {
const uint64_t start_time = OS::get_singleton()->get_ticks_msec();
// Determine decompression parameters from image format.
const Image::Format src_format = r_img->get_format();
bool is_hdr = false;
unsigned int block_x = 0;
unsigned int block_y = 0;
switch (src_format) {
case Image::FORMAT_ASTC_4x4: {
block_x = 4;
block_y = 4;
is_hdr = false;
} break;
case Image::FORMAT_ASTC_4x4_HDR: {
block_x = 4;
block_y = 4;
is_hdr = true;
} break;
case Image::FORMAT_ASTC_8x8: {
block_x = 8;
block_y = 8;
is_hdr = false;
} break;
case Image::FORMAT_ASTC_8x8_HDR: {
block_x = 8;
block_y = 8;
is_hdr = true;
} break;
default: {
ERR_FAIL_MSG(vformat("astcenc: Cannot decompress Image with a non-ASTC format: %s.", Image::get_format_name(src_format)));
} break;
}
// Initialize astcenc.
const astcenc_profile profile = is_hdr ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR;
astcenc_config config;
const float quality = ASTCENC_PRE_MEDIUM;
const uint32_t flags = ASTCENC_FLG_DECOMPRESS_ONLY;
astcenc_error status = astcenc_config_init(profile, block_x, block_y, 1, quality, flags, &config);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
// Context allocation.
astcenc_context *context = nullptr;
const unsigned int thread_count = 1;
status = astcenc_context_alloc(&config, thread_count, &context);
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
const Image::Format target_format = is_hdr ? Image::FORMAT_RGBAH : Image::FORMAT_RGBA8;
const bool has_mipmaps = r_img->has_mipmaps();
int width = r_img->get_width();
int height = r_img->get_height();
const int64_t dest_size = Image::get_image_data_size(width, height, target_format, has_mipmaps);
Vector<uint8_t> dest_data;
dest_data.resize(dest_size);
uint8_t *dest_write = dest_data.ptrw();
// Decompress image.
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
const uint8_t *src_data = r_img->ptr();
for (int i = 0; i < mip_count + 1; i++) {
const int64_t src_ofs = Image::get_image_mipmap_offset(width, height, src_format, i);
const uint8_t *mip_data = &src_data[src_ofs];
int64_t src_size;
if (i == mip_count) {
src_size = r_img->get_data_size() - src_ofs;
} else {
src_size = Image::get_image_mipmap_offset(width, height, src_format, i + 1) - src_ofs;
}
int dst_mip_w, dst_mip_h;
const int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h);
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
ERR_FAIL_COND(dst_ofs % 8 != 0);
uint8_t *dest_mip_write = &dest_write[dst_ofs];
astcenc_image image;
image.dim_x = dst_mip_w;
image.dim_y = dst_mip_h;
image.dim_z = 1;
image.data_type = is_hdr ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8;
image.data = (void **)(&dest_mip_write);
const astcenc_swizzle swizzle = {
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
};
status = astcenc_decompress_image(context, mip_data, src_size, &image, &swizzle, 0);
ERR_BREAK_MSG(status != ASTCENC_SUCCESS, vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status)));
ERR_BREAK_MSG(image.dim_z > 1, "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported.");
astcenc_compress_reset(context);
}
astcenc_context_free(context);
// Replace original image with compressed one.
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);
print_verbose(vformat("astcenc: Decompression took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}

View File

@@ -0,0 +1,39 @@
/**************************************************************************/
/* image_compress_astcenc.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#ifdef TOOLS_ENABLED
void _compress_astc(Image *r_img, Image::ASTCFormat p_format);
#endif
void _decompress_astc(Image *r_img);

View File

@@ -0,0 +1,51 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_compress_astcenc.h"
void initialize_astcenc_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
#ifdef TOOLS_ENABLED
Image::_image_compress_astc_func = _compress_astc;
#endif
Image::_image_decompress_astc = _decompress_astc;
}
void uninitialize_astcenc_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_astcenc_module(ModuleInitializationLevel p_level);
void uninitialize_astcenc_module(ModuleInitializationLevel p_level);

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_basisu = env_modules.Clone()
# Thirdparty source files
thirdparty_obj = []
# Not unbundled so far since not widespread as shared library
thirdparty_dir = "#thirdparty/basis_universal/"
# Only build the encoder for editor builds
basisu_encoder = env.editor_build
# Sync list with upstream CMakeLists.txt
if basisu_encoder:
encoder_sources = [
"3rdparty/android_astc_decomp.cpp",
"basisu_astc_hdr_6x6_enc.cpp",
"basisu_astc_hdr_common.cpp",
"basisu_backend.cpp",
"basisu_basis_file.cpp",
"basisu_bc7enc.cpp",
"basisu_comp.cpp",
"basisu_enc.cpp",
"basisu_etc.cpp",
"basisu_frontend.cpp",
"basisu_gpu_texture.cpp",
"basisu_kernels_sse.cpp",
"basisu_opencl.cpp",
"basisu_pvrtc1_4.cpp",
"basisu_resample_filters.cpp",
"basisu_resampler.cpp",
"basisu_ssim.cpp",
"basisu_uastc_enc.cpp",
"basisu_uastc_hdr_4x4_enc.cpp",
"jpgd.cpp",
"pvpngreader.cpp",
]
encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources]
transcoder_sources = [thirdparty_dir + "transcoder/basisu_transcoder.cpp"]
env_basisu.Prepend(CPPEXTPATH=[thirdparty_dir])
if basisu_encoder:
env_basisu.Prepend(CPPEXTPATH=["#thirdparty/tinyexr"])
if env["builtin_zstd"]:
env_basisu.Prepend(CPPEXTPATH=["#thirdparty/zstd"])
env_thirdparty = env_basisu.Clone()
env_thirdparty.disable_warnings()
# Disable unneeded features to reduce binary size.
# <https://github.com/BinomialLLC/basis_universal/wiki/How-to-Use-and-Configure-the-Transcoder>
env_thirdparty.Append(
CPPDEFINES=[
# Enable ktx2 zstd supercompression.
("BASISD_SUPPORT_KTX2_ZSTD", 1),
# GPU compression formats.
("BASISD_SUPPORT_ATC", 0), # Proprietary Adreno format not supported by Godot.
("BASISD_SUPPORT_FXT1", 0), # Legacy format not supported by Godot.
("BASISD_SUPPORT_PVRTC1", 0), # Legacy format not supported by Godot.
("BASISD_SUPPORT_PVRTC2", 0), # Legacy format not supported by Godot.
]
)
if basisu_encoder:
env_thirdparty.add_source_files(thirdparty_obj, encoder_sources)
env_thirdparty.add_source_files(thirdparty_obj, transcoder_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_basisu.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

View File

@@ -0,0 +1,8 @@
def can_build(env, platform):
if env.editor_build: # Encoder dependencies
env.module_add_dependencies("basis_universal", ["tinyexr"])
return True
def configure(env):
pass

View File

@@ -0,0 +1,496 @@
/**************************************************************************/
/* image_compress_basisu.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_compress_basisu.h"
#include "core/config/project_settings.h"
#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "servers/rendering_server.h"
#include <transcoder/basisu_transcoder.h>
#ifdef TOOLS_ENABLED
#include <encoder/basisu_comp.h>
static Mutex init_mutex;
static bool initialized = false;
#endif
void basis_universal_init() {
basist::basisu_transcoder_init();
}
#ifdef TOOLS_ENABLED
template <typename T>
inline void _basisu_pad_mipmap(const uint8_t *p_image_mip_data, Vector<uint8_t> &r_mip_data_padded, int p_next_width, int p_next_height, int p_width, int p_height, int64_t p_size) {
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
const T *mip_src_data = reinterpret_cast<const T *>(p_image_mip_data);
// Reserve space in the padded buffer.
r_mip_data_padded.resize(p_next_width * p_next_height * sizeof(T));
T *data_padded_ptr = reinterpret_cast<T *>(r_mip_data_padded.ptrw());
// Pad mipmap to the nearest block by smearing.
int x = 0, y = 0;
for (y = 0; y < p_height; y++) {
for (x = 0; x < p_width; x++) {
data_padded_ptr[p_next_width * y + x] = mip_src_data[p_width * y + x];
}
// First, smear in x.
for (; x < p_next_width; x++) {
data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - 1];
}
}
// Then, smear in y.
for (; y < p_next_height; y++) {
for (x = 0; x < p_next_width; x++) {
data_padded_ptr[p_next_width * y + x] = data_padded_ptr[p_next_width * y + x - p_next_width];
}
}
}
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels, const Image::BasisUniversalPackerParams &p_basisu_params) {
init_mutex.lock();
if (!initialized) {
basisu::basisu_encoder_init();
initialized = true;
}
init_mutex.unlock();
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
Ref<Image> image = p_image->duplicate();
bool is_hdr = false;
if (image->get_format() <= Image::FORMAT_RGB565) {
image->convert(Image::FORMAT_RGBA8);
} else if (image->get_format() <= Image::FORMAT_RGBE9995) {
image->convert(Image::FORMAT_RGBAF);
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;
params.m_uastc = true;
params.m_pack_uastc_ldr_4x4_flags &= ~basisu::cPackUASTCLevelMask;
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_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_multithreading = true;
params.m_check_for_alpha = false;
if (!OS::get_singleton()->is_stdout_verbose()) {
params.m_print_stats = false;
params.m_compute_stats = false;
params.m_status_output = false;
}
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
params.m_pJob_pool = &job_pool;
BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;
if (is_hdr) {
decompress_format = BASIS_DECOMPRESS_HDR_RGB;
params.m_hdr = true;
params.m_uastc_hdr_4x4_options.set_quality_level(p_basisu_params.uastc_level);
} else {
switch (p_channels) {
case Image::USED_CHANNELS_L: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_LA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
} break;
case Image::USED_CHANNELS_RGBA: {
params.m_force_alpha = true;
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
}
}
ERR_FAIL_COND_V(decompress_format == BASIS_DECOMPRESS_MAX, Vector<uint8_t>());
// Copy the source image data with mipmaps into BasisU.
{
const int orig_width = image->get_width();
const int orig_height = image->get_height();
bool is_res_div_4 = (orig_width % 4 == 0) && (orig_height % 4 == 0);
// Image's resolution rounded up to the nearest values divisible by 4.
int next_width = orig_width <= 2 ? orig_width : (orig_width + 3) & ~3;
int next_height = orig_height <= 2 ? orig_height : (orig_height + 3) & ~3;
Vector<uint8_t> image_data = image->get_data();
basisu::vector<basisu::image> basisu_mipmaps;
basisu::vector<basisu::imagef> basisu_mipmaps_hdr;
// Buffer for storing padded mipmap data.
Vector<uint8_t> mip_data_padded;
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int64_t ofs, size;
int width, height;
image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);
const uint8_t *image_mip_data = image_data.ptr() + ofs;
// Pad the mipmap's data if its resolution isn't divisible by 4.
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
if (is_hdr) {
_basisu_pad_mipmap<BasisRGBAF>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
} else {
_basisu_pad_mipmap<uint32_t>(image_mip_data, mip_data_padded, next_width, next_height, width, height, size);
}
// Override the image_mip_data pointer with our temporary Vector.
image_mip_data = reinterpret_cast<const uint8_t *>(mip_data_padded.ptr());
// Override the mipmap's properties.
width = next_width;
height = next_height;
size = mip_data_padded.size();
}
// Get the next mipmap's resolution.
next_width /= 2;
next_height /= 2;
// Copy the source mipmap's data to a BasisU image.
if (is_hdr) {
basisu::imagef basisu_image(width, height);
memcpy(reinterpret_cast<uint8_t *>(basisu_image.get_ptr()), image_mip_data, size);
if (i == 0) {
params.m_source_images_hdr.push_back(basisu_image);
} else {
basisu_mipmaps_hdr.push_back(basisu_image);
}
} else {
basisu::image basisu_image(width, height);
memcpy(basisu_image.get_ptr(), image_mip_data, size);
if (i == 0) {
params.m_source_images.push_back(basisu_image);
} else {
basisu_mipmaps.push_back(basisu_image);
}
}
}
if (is_hdr) {
params.m_source_mipmap_images_hdr.push_back(basisu_mipmaps_hdr);
} else {
params.m_source_mipmap_images.push_back(basisu_mipmaps);
}
}
// Encode the image data.
basisu::basis_compressor compressor;
compressor.init(params);
int basisu_err = compressor.process();
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
const basisu::uint8_vec &basisu_encoded = compressor.get_output_ktx2_file();
Vector<uint8_t> basisu_data;
basisu_data.resize(basisu_encoded.size() + 4);
uint8_t *basisu_data_ptr = basisu_data.ptrw();
// Copy the encoded BasisU data into the output buffer.
*(uint32_t *)basisu_data_ptr = decompress_format | BASIS_DECOMPRESS_FLAG_KTX2;
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));
return basisu_data;
}
#endif // TOOLS_ENABLED
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
Ref<Image> image;
ERR_FAIL_NULL_V_MSG(p_data, image, "Cannot unpack invalid BasisUniversal data.");
const uint8_t *src_ptr = p_data;
int src_size = p_size;
basist::transcoder_texture_format basisu_format = basist::transcoder_texture_format::cTFTotalTextureFormats;
Image::Format image_format = Image::FORMAT_MAX;
// Get supported compression formats.
bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");
bool astc_supported = RS::get_singleton()->has_os_feature("astc");
bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc");
bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");
bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");
bool astc_hdr_supported = RS::get_singleton()->has_os_feature("astc_hdr");
bool needs_ra_rg_swap = false;
bool needs_rg_trim = false;
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) {
case BASIS_DECOMPRESS_R: {
if (rgtc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC4_R;
image_format = Image::FORMAT_RGTC_R;
} else if (s3tc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC1;
image_format = Image::FORMAT_DXT1;
} else if (etc2_supported) {
basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11;
image_format = Image::FORMAT_ETC2_R11;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
needs_rg_trim = true;
}
} break;
case BASIS_DECOMPRESS_RG: {
if (rgtc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC5_RG;
image_format = Image::FORMAT_RGTC_RG;
} else if (s3tc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC3;
image_format = Image::FORMAT_DXT5_RA_AS_RG;
} else if (etc2_supported) {
basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11;
image_format = Image::FORMAT_ETC2_RG11;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
needs_ra_rg_swap = true;
needs_rg_trim = true;
}
} break;
case BASIS_DECOMPRESS_RG_AS_RA: {
if (s3tc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC3;
image_format = Image::FORMAT_DXT5_RA_AS_RG;
} else if (etc2_supported) {
basisu_format = basist::transcoder_texture_format::cTFETC2;
image_format = Image::FORMAT_ETC2_RA_AS_RG;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
needs_ra_rg_swap = true;
needs_rg_trim = true;
}
} break;
case BASIS_DECOMPRESS_RGB: {
if (bptc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC7_M6_OPAQUE_ONLY;
image_format = Image::FORMAT_BPTC_RGBA;
} else if (astc_supported) {
basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
image_format = Image::FORMAT_ASTC_4x4;
} else if (s3tc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC1;
image_format = Image::FORMAT_DXT1;
} else if (etc2_supported) {
basisu_format = basist::transcoder_texture_format::cTFETC1;
image_format = Image::FORMAT_ETC2_RGB8;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
}
} break;
case BASIS_DECOMPRESS_RGBA: {
if (bptc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC7_M5;
image_format = Image::FORMAT_BPTC_RGBA;
} else if (astc_supported) {
basisu_format = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
image_format = Image::FORMAT_ASTC_4x4;
} else if (s3tc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC3;
image_format = Image::FORMAT_DXT5;
} else if (etc2_supported) {
basisu_format = basist::transcoder_texture_format::cTFETC2;
image_format = Image::FORMAT_ETC2_RGBA8;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
}
} break;
case BASIS_DECOMPRESS_HDR_RGB: {
if (bptc_supported) {
basisu_format = basist::transcoder_texture_format::cTFBC6H;
image_format = Image::FORMAT_BPTC_RGBFU;
} else if (astc_hdr_supported) {
basisu_format = basist::transcoder_texture_format::cTFASTC_HDR_4x4_RGBA;
image_format = Image::FORMAT_ASTC_4x4_HDR;
} else {
// No supported VRAM compression formats, decompress.
basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;
image_format = Image::FORMAT_RGBE9995;
}
} break;
default: {
ERR_FAIL_V(image);
} break;
}
src_ptr += 4;
src_size -= 4;
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);
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);
transcoder.start_transcoding(src_ptr, src_size);
basist::basisu_image_info basisu_info;
transcoder.get_image_info(src_ptr, src_size, basisu_info, 0);
// 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));
uint8_t *dst = out_data.ptrw();
memset(dst, 0, out_data.size());
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);
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);
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);
}
if (needs_ra_rg_swap) {
// Swap uncompressed RA-as-RG texture's color channels.
image->convert_ra_rgba8_to_rg();
}
if (needs_rg_trim) {
// Remove unnecessary color channels from uncompressed textures.
if (decompress_format == BASIS_DECOMPRESS_R) {
image->convert(Image::FORMAT_R8);
} else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) {
image->convert(Image::FORMAT_RG8);
}
}
print_verbose(vformat("BasisU: Transcoding a %dx%d image with %d mipmaps into %s took %d ms.",
image->get_width(), image->get_height(), image->get_mipmap_count(), Image::get_format_name(image_format), OS::get_singleton()->get_ticks_msec() - start_time));
return image;
}
Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer) {
return basis_universal_unpacker_ptr(p_buffer.ptr(), p_buffer.size());
}

View File

@@ -0,0 +1,60 @@
/**************************************************************************/
/* image_compress_basisu.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
enum BasisDecompressFormat {
BASIS_DECOMPRESS_RG,
BASIS_DECOMPRESS_RGB,
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
BASIS_DECOMPRESS_R,
BASIS_DECOMPRESS_HDR_RGB,
BASIS_DECOMPRESS_MAX
};
constexpr uint32_t BASIS_DECOMPRESS_FLAG_KTX2 = 1 << 31;
void basis_universal_init();
#ifdef TOOLS_ENABLED
struct BasisRGBAF {
uint32_t r;
uint32_t g;
uint32_t b;
uint32_t a;
};
Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedChannels p_channels, const Image::BasisUniversalPackerParams &p_basisu_params);
#endif
Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size);
Ref<Image> basis_universal_unpacker(const Vector<uint8_t> &p_buffer);

View File

@@ -0,0 +1,67 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_compress_basisu.h"
#include "core/config/project_settings.h"
void initialize_basis_universal_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
basis_universal_init();
#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;
#endif
Image::basis_universal_unpacker = basis_universal_unpacker;
Image::basis_universal_unpacker_ptr = basis_universal_unpacker_ptr;
}
void uninitialize_basis_universal_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
#ifdef TOOLS_ENABLED
Image::basis_universal_packer = nullptr;
#endif
Image::basis_universal_unpacker = nullptr;
Image::basis_universal_unpacker_ptr = nullptr;
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_basis_universal_module(ModuleInitializationLevel p_level);
void uninitialize_basis_universal_module(ModuleInitializationLevel p_level);

10
modules/bcdec/SCsub Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_bcdec = env_modules.Clone()
# Godot source files
env_bcdec.add_source_files(env.modules_sources, "*.cpp")

6
modules/bcdec/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return True
def configure(env):
pass

View File

@@ -0,0 +1,303 @@
/**************************************************************************/
/* image_decompress_bcdec.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_decompress_bcdec.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#define BCDEC_IMPLEMENTATION
#include "thirdparty/misc/bcdec.h"
inline void bcdec_bc6h_half_s(const void *compressedBlock, void *decompressedBlock, int destinationPitch) {
bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, true);
}
inline void bcdec_bc6h_half_u(const void *compressedBlock, void *decompressedBlock, int destinationPitch) {
bcdec_bc6h_half(compressedBlock, decompressedBlock, destinationPitch, false);
}
template <void (*decompress_func)(const void *, void *, int), int block_size, int pixel_size, int component_size>
static inline void _safe_decompress_mipmap(int width, int height, const uint8_t *src, uint8_t *dst) {
// A stack-allocated output buffer large enough to contain an entire uncompressed block.
uint8_t temp_buf[4 * 4 * pixel_size];
// The amount of misaligned pixels on each axis.
const int width_diff = width - (width & ~0x03);
const int height_diff = height - (height & ~0x03);
// The amount of uncompressed blocks on each axis.
const int width_blocks = (width & ~0x03) / 4;
const int height_blocks = (height & ~0x03) / 4;
// The pitch of the image in bytes.
const int image_pitch = width * pixel_size;
// The pitch of a block in bytes.
const int block_pitch = 4 * pixel_size;
// The pitch of the last block in bytes.
const int odd_pitch = width_diff * pixel_size;
size_t src_pos = 0;
size_t dst_pos = 0;
// Decompress the blocks, starting from the top.
for (int y = 0; y < height_blocks; y += 1) {
// Decompress the blocks, starting from the left.
for (int x = 0; x < width_blocks; x += 1) {
decompress_func(&src[src_pos], &dst[dst_pos], image_pitch / component_size);
src_pos += block_size;
dst_pos += block_pitch;
}
// Decompress the block on the right.
if (width_diff > 0) {
decompress_func(&src[src_pos], temp_buf, block_pitch / component_size);
// Copy the data from the temporary buffer to the output.
for (int i = 0; i < 4; i++) {
memcpy(&dst[dst_pos + i * image_pitch], &temp_buf[i * block_pitch], odd_pitch);
}
src_pos += block_size;
dst_pos += odd_pitch;
}
// Skip to the next row of blocks, the current one has already been filled.
dst_pos += 3 * image_pitch;
}
// Decompress the blocks at the bottom of the image.
if (height_diff > 0) {
// Decompress the blocks at the bottom.
for (int x = 0; x < width_blocks; x += 1) {
decompress_func(&src[src_pos], temp_buf, block_pitch / component_size);
// Copy the data from the temporary buffer to the output.
for (int i = 0; i < height_diff; i++) {
memcpy(&dst[dst_pos + i * image_pitch], &temp_buf[i * block_pitch], block_pitch);
}
src_pos += block_size;
dst_pos += block_pitch;
}
// Decompress the block in the lower-right corner.
if (width_diff > 0) {
decompress_func(&src[src_pos], temp_buf, block_pitch / component_size);
// Copy the data from the temporary buffer to the output.
for (int i = 0; i < height_diff; i++) {
memcpy(&dst[dst_pos + i * image_pitch], &temp_buf[i * block_pitch], odd_pitch);
}
src_pos += block_size;
dst_pos += odd_pitch;
}
}
}
template <void (*decompress_func)(const void *, void *, int), int block_size, int pixel_size, int component_size>
static inline void _decompress_mipmap(int width, int height, const uint8_t *src, uint8_t *dst) {
size_t src_pos = 0;
size_t dst_pos = 0;
// The size of a single block in bytes.
const int block_pitch = 4 * pixel_size;
// The pitch of the image in bytes.
const int image_pitch = width * pixel_size;
for (int y = 0; y < height; y += 4) {
for (int x = 0; x < width; x += 4) {
decompress_func(&src[src_pos], &dst[dst_pos], image_pitch / component_size);
src_pos += block_size;
dst_pos += block_pitch;
}
// Skip to the next row of blocks, the current one has already been filled.
dst_pos += 3 * image_pitch;
}
}
static void decompress_image(BCdecFormat format, const void *src, void *dst, const uint64_t width, const uint64_t height) {
const uint8_t *src_blocks = reinterpret_cast<const uint8_t *>(src);
uint8_t *dec_blocks = reinterpret_cast<uint8_t *>(dst);
const uint64_t aligned_width = (width + 3) & ~0x03;
const uint64_t aligned_height = (height + 3) & ~0x03;
if (width != aligned_width || height != aligned_height) {
// Decompress the mipmap in a 'safe' way, which involves starting from the top left.
// For each block row, decompress all of the 'full' blocks, then the misaligned one (on the x axis).
// Then, decompress the final misaligned block row at the bottom.
// Finally, decompress the misaligned block at the bottom right.
switch (format) {
case BCdec_BC1: {
_safe_decompress_mipmap<bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC2: {
_safe_decompress_mipmap<bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC3: {
_safe_decompress_mipmap<bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC4: {
_safe_decompress_mipmap<bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC5: {
_safe_decompress_mipmap<bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC6U: {
_safe_decompress_mipmap<bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 2>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC6S: {
_safe_decompress_mipmap<bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 2>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC7: {
_safe_decompress_mipmap<bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
}
} else {
// Just decompress as usual, as fast as possible.
switch (format) {
case BCdec_BC1: {
_decompress_mipmap<bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC2: {
_decompress_mipmap<bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC3: {
_decompress_mipmap<bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC4: {
_decompress_mipmap<bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC5: {
_decompress_mipmap<bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 1>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC6U: {
_decompress_mipmap<bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 2>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC6S: {
_decompress_mipmap<bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 2>(width, height, src_blocks, dec_blocks);
} break;
case BCdec_BC7: {
_decompress_mipmap<bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 1>(width, height, src_blocks, dec_blocks);
} break;
}
}
}
void image_decompress_bcdec(Image *p_image) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
int width = p_image->get_width();
int height = p_image->get_height();
Image::Format source_format = p_image->get_format();
Image::Format target_format = Image::FORMAT_MAX;
BCdecFormat bcdec_format = BCdec_BC1;
switch (source_format) {
case Image::FORMAT_DXT1:
bcdec_format = BCdec_BC1;
target_format = Image::FORMAT_RGBA8;
break;
case Image::FORMAT_DXT3:
bcdec_format = BCdec_BC2;
target_format = Image::FORMAT_RGBA8;
break;
case Image::FORMAT_DXT5:
case Image::FORMAT_DXT5_RA_AS_RG:
bcdec_format = BCdec_BC3;
target_format = Image::FORMAT_RGBA8;
break;
case Image::FORMAT_RGTC_R:
bcdec_format = BCdec_BC4;
target_format = Image::FORMAT_R8;
break;
case Image::FORMAT_RGTC_RG:
bcdec_format = BCdec_BC5;
target_format = Image::FORMAT_RG8;
break;
case Image::FORMAT_BPTC_RGBFU:
bcdec_format = BCdec_BC6U;
target_format = Image::FORMAT_RGBH;
break;
case Image::FORMAT_BPTC_RGBF:
bcdec_format = BCdec_BC6S;
target_format = Image::FORMAT_RGBH;
break;
case Image::FORMAT_BPTC_RGBA:
bcdec_format = BCdec_BC7;
target_format = Image::FORMAT_RGBA8;
break;
default:
ERR_FAIL_MSG("bcdec: Can't decompress unknown format: " + Image::get_format_name(source_format) + ".");
break;
}
int mm_count = p_image->get_mipmap_count();
int64_t target_size = Image::get_image_data_size(width, height, target_format, p_image->has_mipmaps());
// Decompressed data.
Vector<uint8_t> data;
data.resize(target_size);
uint8_t *wb = data.ptrw();
// Source data.
const uint8_t *rb = p_image->get_data().ptr();
// Decompress mipmaps.
for (int i = 0; i <= mm_count; i++) {
int mipmap_w = 0, mipmap_h = 0;
int64_t src_ofs = Image::get_image_mipmap_offset(width, height, source_format, i);
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, mipmap_w, mipmap_h);
decompress_image(bcdec_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h);
}
p_image->set_data(width, height, p_image->has_mipmaps(), target_format, data);
// Swap channels if the format is using a channel swizzle.
if (source_format == Image::FORMAT_DXT5_RA_AS_RG) {
p_image->convert_ra_rgba8_to_rg();
}
print_verbose(vformat("bcdec: Decompression of a %dx%d %s image with %d mipmaps took %d ms.",
p_image->get_width(), p_image->get_height(), Image::get_format_name(source_format), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
}

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* image_decompress_bcdec.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
enum BCdecFormat {
BCdec_BC1,
BCdec_BC2,
BCdec_BC3,
BCdec_BC4,
BCdec_BC5,
BCdec_BC6S,
BCdec_BC6U,
BCdec_BC7,
};
void image_decompress_bcdec(Image *p_image);

View File

@@ -0,0 +1,48 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_decompress_bcdec.h"
void initialize_bcdec_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
Image::_image_decompress_bc = image_decompress_bcdec;
Image::_image_decompress_bptc = image_decompress_bcdec;
}
void uninitialize_bcdec_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_bcdec_module(ModuleInitializationLevel p_level);
void uninitialize_bcdec_module(ModuleInitializationLevel p_level);

View File

@@ -0,0 +1,18 @@
Copyright 2020-2022 Matias N. Goldberg
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This software uses code from:
* [GPURealTimeBC6H](https://github.com/knarkowicz/GPURealTimeBC6H), under public domain. Modifications by Matias N. Goldberg
* [rg-etc1](https://github.com/richgel999/rg-etc1/), Copyright (c) 2012 Rich Geldreich, zlib license. Extensive modifications by Matias N. Goldberg to adapt it as a compute shader
* [stb_dxt](https://github.com/nothings/stb/blob/master/stb_dxt.h), under dual-license: A. MIT License
Copyright (c) 2017 Sean Barrett, B. Public Domain (www.unlicense.org). Original by fabian "ryg" giesen - ported to C by stb. Modifications by Matias N. Goldberg to adapt it as a compute shader
* EAC loosely inspired on [etc2_encoder](https://github.com/titilambert/packaging-efl/blob/master/src/static_libs/rg_etc/etc2_encoder.c), Copyright (C) 2014 Jean-Philippe ANDRE, 2-clause BSD license
* ETC2 T & H modes based on [etc2_encoder](https://github.com/titilambert/packaging-efl/blob/master/src/static_libs/rg_etc/etc2_encoder.c), Copyright (C) 2014 Jean-Philippe ANDRE, 2-clause BSD license. A couple minor bugfixes applied by Matias N. Goldberg. Modifications made by Matias N. Goldberg to adapt it as a compute shader
* ETC2 P very loosely based on [etc2_encoder](https://github.com/titilambert/packaging-efl/blob/master/src/static_libs/rg_etc/etc2_encoder.c), Copyright (C) 2014 Jean-Philippe ANDRE, 2-clause BSD license. Considerable rewrite by Matias N. Goldberg to enhance its quality.

18
modules/betsy/SCsub Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_betsy = env_modules.Clone()
# Betsy shaders, originally from https://github.com/darksylinc/betsy
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"])
# Godot source files
env_betsy.add_source_files(env.modules_sources, "*.cpp")

View File

@@ -0,0 +1,21 @@
// RGB and Alpha components of ETC2 RGBA/DXT5 are computed separately.
// This compute shader merely stitches them together to form the final result
// It's also used by RG11/BC4 driver to stitch two R11/BC4 into one RG11/BC5
#[compute]
#version 450
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() {
uvec2 rgbBlock = texelFetch(srcRGB, ivec2(gl_GlobalInvocationID.xy), 0).xy;
uvec2 alphaBlock = texelFetch(srcAlpha, ivec2(gl_GlobalInvocationID.xy), 0).xy;
imageStore(dstTexture, ivec2(gl_GlobalInvocationID.xy), uvec4(rgbBlock.xy, alphaBlock.xy));
}

491
modules/betsy/bc1.glsl Normal file
View File

@@ -0,0 +1,491 @@
#[versions]
standard = "";
dithered = "#define BC1_DITHER";
#[compute]
#version 450
#VERSION_DEFINES
#define FLT_MAX 340282346638528859811704183484516925440.0f
layout(binding = 0) uniform sampler2D srcTex;
layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture;
layout(std430, binding = 2) readonly restrict buffer globalBuffer {
vec2 c_oMatch5[256];
vec2 c_oMatch6[256];
};
layout(push_constant, std430) uniform Params {
uint p_numRefinements;
uint p_padding[3];
}
params;
layout(local_size_x = 8, //
local_size_y = 8, //
local_size_z = 1) in;
vec3 rgb565to888(float rgb565) {
vec3 retVal;
retVal.x = floor(rgb565 / 2048.0f);
retVal.y = floor(mod(rgb565, 2048.0f) / 32.0f);
retVal.z = floor(mod(rgb565, 32.0f));
// This is the correct 565 to 888 conversion:
// rgb = floor( rgb * ( 255.0f / vec3( 31.0f, 63.0f, 31.0f ) ) + 0.5f )
//
// However stb_dxt follows a different one:
// rb = floor( rb * ( 256 / 32 + 8 / 32 ) );
// g = floor( g * ( 256 / 64 + 4 / 64 ) );
//
// I'm not sure exactly why but it's possible this is how the S3TC specifies it should be decoded
// It's quite possible this is the reason:
// http://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/
//
// Or maybe it's just because it's cheap to do with integer shifts.
// Anyway, we follow stb_dxt's conversion just in case
// (gives almost the same result, with 1 or -1 of difference for a very few values)
//
// Perhaps when we make 888 -> 565 -> 888 it doesn't matter
// because they end up mapping to the original number
return floor(retVal * vec3(8.25f, 4.0625f, 8.25f));
}
float rgb888to565(vec3 rgbValue) {
rgbValue.rb = floor(rgbValue.rb * 31.0f / 255.0f + 0.5f);
rgbValue.g = floor(rgbValue.g * 63.0f / 255.0f + 0.5f);
return rgbValue.r * 2048.0f + rgbValue.g * 32.0f + rgbValue.b;
}
// linear interpolation at 1/3 point between a and b, using desired rounding type
vec3 lerp13(vec3 a, vec3 b) {
#ifdef STB_DXT_USE_ROUNDING_BIAS
// with rounding bias
return a + floor((b - a) * (1.0f / 3.0f) + 0.5f);
#else
// without rounding bias
return floor((2.0f * a + b) / 3.0f);
#endif
}
/// Unpacks a block of 4 colors from two 16-bit endpoints
void EvalColors(out vec3 colors[4], float c0, float c1) {
colors[0] = rgb565to888(c0);
colors[1] = rgb565to888(c1);
colors[2] = lerp13(colors[0], colors[1]);
colors[3] = lerp13(colors[1], colors[0]);
}
/** The color optimization function. (Clever code, part 1)
@param outMinEndp16 [out]
Minimum endpoint, in RGB565
@param outMaxEndp16 [out]
Maximum endpoint, in RGB565
*/
void OptimizeColorsBlock(const uint srcPixelsBlock[16], out float outMinEndp16, out float outMaxEndp16) {
// determine color distribution
vec3 avgColor;
vec3 minColor;
vec3 maxColor;
avgColor = minColor = maxColor = unpackUnorm4x8(srcPixelsBlock[0]).xyz;
for (int i = 1; i < 16; ++i) {
const vec3 currColorUnorm = unpackUnorm4x8(srcPixelsBlock[i]).xyz;
avgColor += currColorUnorm;
minColor = min(minColor, currColorUnorm);
maxColor = max(maxColor, currColorUnorm);
}
avgColor = round(avgColor * 255.0f / 16.0f);
maxColor *= 255.0f;
minColor *= 255.0f;
// determine covariance matrix
float cov[6];
for (int i = 0; i < 6; ++i) {
cov[i] = 0;
}
for (int i = 0; i < 16; ++i) {
const vec3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
vec3 rgbDiff = currColor - avgColor;
cov[0] += rgbDiff.r * rgbDiff.r;
cov[1] += rgbDiff.r * rgbDiff.g;
cov[2] += rgbDiff.r * rgbDiff.b;
cov[3] += rgbDiff.g * rgbDiff.g;
cov[4] += rgbDiff.g * rgbDiff.b;
cov[5] += rgbDiff.b * rgbDiff.b;
}
// convert covariance matrix to float, find principal axis via power iter
for (int i = 0; i < 6; ++i) {
cov[i] /= 255.0f;
}
vec3 vF = maxColor - minColor;
const int nIterPower = 4;
for (int iter = 0; iter < nIterPower; ++iter) {
const float r = vF.r * cov[0] + vF.g * cov[1] + vF.b * cov[2];
const float g = vF.r * cov[1] + vF.g * cov[3] + vF.b * cov[4];
const float b = vF.r * cov[2] + vF.g * cov[4] + vF.b * cov[5];
vF.r = r;
vF.g = g;
vF.b = b;
}
float magn = max(abs(vF.r), max(abs(vF.g), abs(vF.b)));
vec3 v;
if (magn < 4.0f) { // too small, default to luminance
v.r = 299.0f; // JPEG YCbCr luma coefs, scaled by 1000.
v.g = 587.0f;
v.b = 114.0f;
} else {
v = trunc(vF * (512.0f / magn));
}
// Pick colors at extreme points
vec3 minEndpoint, maxEndpoint;
float minDot = FLT_MAX;
float maxDot = -FLT_MAX;
for (int i = 0; i < 16; ++i) {
const vec3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
const float dotValue = dot(currColor, v);
if (dotValue < minDot) {
minDot = dotValue;
minEndpoint = currColor;
}
if (dotValue > maxDot) {
maxDot = dotValue;
maxEndpoint = currColor;
}
}
outMinEndp16 = rgb888to565(minEndpoint);
outMaxEndp16 = rgb888to565(maxEndpoint);
}
// The color matching function
uint MatchColorsBlock(const uint srcPixelsBlock[16], vec3 color[4]) {
uint mask = 0u;
vec3 dir = color[0] - color[1];
float stops[4];
for (int i = 0; i < 4; ++i) {
stops[i] = dot(color[i], dir);
}
// think of the colors as arranged on a line; project point onto that line, then choose
// next color out of available ones. we compute the crossover points for "best color in top
// half"/"best in bottom half" and then the same inside that subinterval.
//
// relying on this 1d approximation isn't always optimal in terms of euclidean distance,
// but it's very close and a lot faster.
// http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html
float c0Point = trunc((stops[1] + stops[3]) * 0.5f);
float halfPoint = trunc((stops[3] + stops[2]) * 0.5f);
float c3Point = trunc((stops[2] + stops[0]) * 0.5f);
#ifndef BC1_DITHER
// the version without dithering is straightforward
for (uint i = 16u; i-- > 0u;) {
const vec3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
const float dotValue = dot(currColor, dir);
mask <<= 2u;
if (dotValue < halfPoint) {
mask |= ((dotValue < c0Point) ? 1u : 3u);
} else {
mask |= ((dotValue < c3Point) ? 2u : 0u);
}
}
#else
// with floyd-steinberg dithering
vec4 ep1 = vec4(0, 0, 0, 0);
vec4 ep2 = vec4(0, 0, 0, 0);
c0Point *= 16.0f;
halfPoint *= 16.0f;
c3Point *= 16.0f;
for (uint y = 0u; y < 4u; ++y) {
float ditherDot;
uint lmask, step;
vec3 currColor;
float dotValue;
currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 0]).xyz * 255.0f;
dotValue = dot(currColor, dir);
ditherDot = (dotValue * 16.0f) + (3 * ep2[1] + 5 * ep2[0]);
if (ditherDot < halfPoint) {
step = (ditherDot < c0Point) ? 1u : 3u;
} else {
step = (ditherDot < c3Point) ? 2u : 0u;
}
ep1[0] = dotValue - stops[step];
lmask = step;
currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 1]).xyz * 255.0f;
dotValue = dot(currColor, dir);
ditherDot = (dotValue * 16.0f) + (7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]);
if (ditherDot < halfPoint) {
step = (ditherDot < c0Point) ? 1u : 3u;
} else {
step = (ditherDot < c3Point) ? 2u : 0u;
}
ep1[1] = dotValue - stops[step];
lmask |= step << 2u;
currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f;
dotValue = dot(currColor, dir);
ditherDot = (dotValue * 16.0f) + (7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]);
if (ditherDot < halfPoint) {
step = (ditherDot < c0Point) ? 1u : 3u;
} else {
step = (ditherDot < c3Point) ? 2u : 0u;
}
ep1[2] = dotValue - stops[step];
lmask |= step << 4u;
currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f;
dotValue = dot(currColor, dir);
ditherDot = (dotValue * 16.0f) + (7 * ep1[2] + 5 * ep2[3] + ep2[2]);
if (ditherDot < halfPoint) {
step = (ditherDot < c0Point) ? 1u : 3u;
} else {
step = (ditherDot < c3Point) ? 2u : 0u;
}
ep1[3] = dotValue - stops[step];
lmask |= step << 6u;
mask |= lmask << (y * 8u);
{
vec4 tmp = ep1;
ep1 = ep2;
ep2 = tmp;
} // swap
}
#endif
return mask;
}
// The refinement function. (Clever code, part 2)
// Tries to optimize colors to suit block contents better.
// (By solving a least squares system via normal equations+Cramer's rule)
bool RefineBlock(const uint srcPixelsBlock[16], uint mask, inout float inOutMinEndp16,
inout float inOutMaxEndp16) {
float newMin16, newMax16;
const float oldMin = inOutMinEndp16;
const float oldMax = inOutMaxEndp16;
if ((mask ^ (mask << 2u)) < 4u) // all pixels have the same index?
{
// yes, linear system would be singular; solve using optimal
// single-color match on average color
vec3 rgbVal = vec3(8.0f / 255.0f, 8.0f / 255.0f, 8.0f / 255.0f);
for (int i = 0; i < 16; ++i) {
rgbVal += unpackUnorm4x8(srcPixelsBlock[i]).xyz;
}
rgbVal = floor(rgbVal * (255.0f / 16.0f));
newMax16 = c_oMatch5[uint(rgbVal.r)][0] * 2048.0f + //
c_oMatch6[uint(rgbVal.g)][0] * 32.0f + //
c_oMatch5[uint(rgbVal.b)][0];
newMin16 = c_oMatch5[uint(rgbVal.r)][1] * 2048.0f + //
c_oMatch6[uint(rgbVal.g)][1] * 32.0f + //
c_oMatch5[uint(rgbVal.b)][1];
} else {
const float w1Tab[4] = { 3, 0, 2, 1 };
const float prods[4] = { 589824.0f, 2304.0f, 262402.0f, 66562.0f };
// ^some magic to save a lot of multiplies in the accumulating loop...
// (precomputed products of weights for least squares system, accumulated inside one 32-bit
// register)
float akku = 0.0f;
uint cm = mask;
vec3 at1 = vec3(0, 0, 0);
vec3 at2 = vec3(0, 0, 0);
for (int i = 0; i < 16; ++i, cm >>= 2u) {
const vec3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
const uint step = cm & 3u;
const float w1 = w1Tab[step];
akku += prods[step];
at1 += currColor * w1;
at2 += currColor;
}
at2 = 3.0f * at2 - at1;
// extract solutions and decide solvability
const float xx = floor(akku / 65535.0f);
const float yy = floor(mod(akku, 65535.0f) / 256.0f);
const float xy = mod(akku, 256.0f);
vec2 f_rb_g;
f_rb_g.x = 3.0f * 31.0f / 255.0f / (xx * yy - xy * xy);
f_rb_g.y = f_rb_g.x * 63.0f / 31.0f;
// solve.
const vec3 newMaxVal = clamp(floor((at1 * yy - at2 * xy) * f_rb_g.xyx + 0.5f),
vec3(0.0f, 0.0f, 0.0f), vec3(31, 63, 31));
newMax16 = newMaxVal.x * 2048.0f + newMaxVal.y * 32.0f + newMaxVal.z;
const vec3 newMinVal = clamp(floor((at2 * xx - at1 * xy) * f_rb_g.xyx + 0.5f),
vec3(0.0f, 0.0f, 0.0f), vec3(31, 63, 31));
newMin16 = newMinVal.x * 2048.0f + newMinVal.y * 32.0f + newMinVal.z;
}
inOutMinEndp16 = newMin16;
inOutMaxEndp16 = newMax16;
return oldMin != newMin16 || oldMax != newMax16;
}
#ifdef BC1_DITHER
/// Quantizes 'srcValue' which is originally in 888 (full range),
/// converting it to 565 and then back to 888 (quantized)
vec3 quant(vec3 srcValue) {
srcValue = clamp(srcValue, 0.0f, 255.0f);
// Convert 888 -> 565
srcValue = floor(srcValue * vec3(31.0f / 255.0f, 63.0f / 255.0f, 31.0f / 255.0f) + 0.5f);
// Convert 565 -> 888 back
srcValue = floor(srcValue * vec3(8.25f, 4.0625f, 8.25f));
return srcValue;
}
void DitherBlock(const uint srcPixBlck[16], out uint dthPixBlck[16]) {
vec3 ep1[4] = { vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0) };
vec3 ep2[4] = { vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0), vec3(0, 0, 0) };
for (uint y = 0u; y < 16u; y += 4u) {
vec3 srcPixel, dithPixel;
srcPixel = unpackUnorm4x8(srcPixBlck[y + 0u]).xyz * 255.0f;
dithPixel = quant(srcPixel + trunc((3 * ep2[1] + 5 * ep2[0]) * (1.0f / 16.0f)));
ep1[0] = srcPixel - dithPixel;
dthPixBlck[y + 0u] = packUnorm4x8(vec4(dithPixel * (1.0f / 255.0f), 1.0f));
srcPixel = unpackUnorm4x8(srcPixBlck[y + 1u]).xyz * 255.0f;
dithPixel = quant(
srcPixel + trunc((7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]) * (1.0f / 16.0f)));
ep1[1] = srcPixel - dithPixel;
dthPixBlck[y + 1u] = packUnorm4x8(vec4(dithPixel * (1.0f / 255.0f), 1.0f));
srcPixel = unpackUnorm4x8(srcPixBlck[y + 2u]).xyz * 255.0f;
dithPixel = quant(
srcPixel + trunc((7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]) * (1.0f / 16.0f)));
ep1[2] = srcPixel - dithPixel;
dthPixBlck[y + 2u] = packUnorm4x8(vec4(dithPixel * (1.0f / 255.0f), 1.0f));
srcPixel = unpackUnorm4x8(srcPixBlck[y + 3u]).xyz * 255.0f;
dithPixel = quant(srcPixel + trunc((7 * ep1[2] + 5 * ep2[3] + ep2[2]) * (1.0f / 16.0f)));
ep1[3] = srcPixel - dithPixel;
dthPixBlck[y + 3u] = packUnorm4x8(vec4(dithPixel * (1.0f / 255.0f), 1.0f));
// swap( ep1, ep2 )
for (uint i = 0u; i < 4u; ++i) {
vec3 tmp = ep1[i];
ep1[i] = ep2[i];
ep2[i] = tmp;
}
}
}
#endif
void main() {
uint srcPixelsBlock[16];
bool bAllColorsEqual = true;
// Load the whole 4x4 block
const uvec2 pixelsToLoadBase = gl_GlobalInvocationID.xy << 2u;
for (uint i = 0u; i < 16u; ++i) {
const uvec2 pixelsToLoad = pixelsToLoadBase + uvec2(i & 0x03u, i >> 2u);
const vec3 srcPixels0 = texelFetch(srcTex, ivec2(pixelsToLoad), 0).xyz;
srcPixelsBlock[i] = packUnorm4x8(vec4(srcPixels0, 1.0f));
bAllColorsEqual = bAllColorsEqual && srcPixelsBlock[0] == srcPixelsBlock[i];
}
float maxEndp16, minEndp16;
uint mask = 0u;
if (bAllColorsEqual) {
const uvec3 rgbVal = uvec3(unpackUnorm4x8(srcPixelsBlock[0]).xyz * 255.0f);
mask = 0xAAAAAAAAu;
maxEndp16 =
c_oMatch5[rgbVal.r][0] * 2048.0f + c_oMatch6[rgbVal.g][0] * 32.0f + c_oMatch5[rgbVal.b][0];
minEndp16 =
c_oMatch5[rgbVal.r][1] * 2048.0f + c_oMatch6[rgbVal.g][1] * 32.0f + c_oMatch5[rgbVal.b][1];
} else {
#ifdef BC1_DITHER
uint ditherPixelsBlock[16];
// first step: compute dithered version for PCA if desired
DitherBlock(srcPixelsBlock, ditherPixelsBlock);
#else
#define ditherPixelsBlock srcPixelsBlock
#endif
// second step: pca+map along principal axis
OptimizeColorsBlock(ditherPixelsBlock, minEndp16, maxEndp16);
if (minEndp16 != maxEndp16) {
vec3 colors[4];
EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted
mask = MatchColorsBlock(srcPixelsBlock, colors);
}
// third step: refine (multiple times if requested)
bool bStopRefinement = false;
for (uint i = 0u; i < params.p_numRefinements && !bStopRefinement; ++i) {
const uint lastMask = mask;
if (RefineBlock(ditherPixelsBlock, mask, minEndp16, maxEndp16)) {
if (minEndp16 != maxEndp16) {
vec3 colors[4];
EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted
mask = MatchColorsBlock(srcPixelsBlock, colors);
} else {
mask = 0u;
bStopRefinement = true;
}
}
bStopRefinement = mask == lastMask || bStopRefinement;
}
}
// write the color block
if (maxEndp16 < minEndp16) {
const float tmpValue = minEndp16;
minEndp16 = maxEndp16;
maxEndp16 = tmpValue;
mask ^= 0x55555555u;
}
uvec2 outputBytes;
outputBytes.x = uint(maxEndp16) | (uint(minEndp16) << 16u);
outputBytes.y = mask;
uvec2 dstUV = gl_GlobalInvocationID.xy;
imageStore(dstTexture, ivec2(dstUV), uvec4(outputBytes.xy, 0u, 0u));
}

151
modules/betsy/bc4.glsl Normal file
View File

@@ -0,0 +1,151 @@
#[versions]
unsigned = "";
signed = "#define SNORM";
#[compute]
#version 450
#VERSION_DEFINES
shared vec2 g_minMaxValues[4u * 4u * 4u];
shared uvec2 g_mask[4u * 4u];
layout(binding = 0) uniform sampler2D srcTex;
layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture;
layout(push_constant, std430) uniform Params {
uint p_channelIdx;
uint p_padding[3];
}
params;
layout(local_size_x = 4, //
local_size_y = 4, //
local_size_z = 4) in;
/// Each block is 16 pixels
/// Each thread works on 4 pixels
/// Therefore each block needs 4 threads, generating 8 masks
/// At the end these 8 masks get merged into 2 and results written to output
///
/// **Q: Why 4 pixels per thread? Why not 1 pixel per thread? Why not 2? Why not 16?**
///
/// A: It's a sweetspot.
/// - Very short threads cannot fill expensive GPUs with enough work (dispatch bound)
/// - Lots of threads means lots of synchronization (e.g. evaluating min/max, merging masks)
/// overhead, and also more LDS usage which reduces occupancy.
/// - Long threads (e.g. 1 thread per block) misses parallelism opportunities
void main() {
float minVal, maxVal;
vec4 srcPixel;
const uint blockThreadId = gl_LocalInvocationID.x;
const uvec2 pixelsToLoadBase = gl_GlobalInvocationID.yz << 2u;
for (uint i = 0u; i < 4u; ++i) {
const uvec2 pixelsToLoad = pixelsToLoadBase + uvec2(i, blockThreadId);
const vec4 value = texelFetch(srcTex, ivec2(pixelsToLoad), 0).xyzw;
srcPixel[i] = params.p_channelIdx == 0 ? value.x : (params.p_channelIdx == 1 ? value.y : value.w);
srcPixel[i] *= 255.0f;
}
minVal = min(srcPixel.x, min(srcPixel.y, srcPixel.z));
maxVal = max(srcPixel.x, max(srcPixel.y, srcPixel.z));
minVal = min(minVal, srcPixel.w);
maxVal = max(maxVal, srcPixel.w);
const uint minMaxIdxBase = (gl_LocalInvocationID.z << 4u) + (gl_LocalInvocationID.y << 2u);
const uint maskIdxBase = (gl_LocalInvocationID.z << 2u) + gl_LocalInvocationID.y;
g_minMaxValues[minMaxIdxBase + blockThreadId] = vec2(minVal, maxVal);
g_mask[maskIdxBase] = uvec2(0u, 0u);
memoryBarrierShared();
barrier();
// Have all 4 threads in the block grab the min/max value by comparing what all 4 threads uploaded
for (uint i = 0u; i < 4u; ++i) {
minVal = min(g_minMaxValues[minMaxIdxBase + i].x, minVal);
maxVal = max(g_minMaxValues[minMaxIdxBase + i].y, maxVal);
}
// determine bias and emit color indices
// given the choice of maxVal/minVal, these indices are optimal:
// http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/
float dist = maxVal - minVal;
float dist4 = dist * 4.0f;
float dist2 = dist * 2.0f;
float bias = (dist < 8) ? (dist - 1) : (trunc(dist * 0.5f) + 2);
bias -= minVal * 7;
uint mask0 = 0u, mask1 = 0u;
for (uint i = 0u; i < 4u; ++i) {
float a = srcPixel[i] * 7.0f + bias;
int ind = 0;
// select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max).
if (a >= dist4) {
ind = 4;
a -= dist4;
}
if (a >= dist2) {
ind += 2;
a -= dist2;
}
if (a >= dist) {
ind += 1;
}
// turn linear scale into DXT index (0/1 are extremal pts)
ind = -ind & 7;
ind ^= (2 > ind) ? 1 : 0;
// write index
const uint bits = 16u + ((blockThreadId << 2u) + i) * 3u;
if (bits < 32u) {
mask0 |= uint(ind) << bits;
if (bits + 3u > 32u) {
mask1 |= uint(ind) >> (32u - bits);
}
} else {
mask1 |= uint(ind) << (bits - 32u);
}
}
if (mask0 != 0u) {
atomicOr(g_mask[maskIdxBase].x, mask0);
}
if (mask1 != 0u) {
atomicOr(g_mask[maskIdxBase].y, mask1);
}
memoryBarrierShared();
barrier();
if (blockThreadId == 0u) {
// Save data
uvec2 outputBytes;
#ifdef SNORM
outputBytes.x =
packSnorm4x8(vec4(maxVal * (1.0f / 255.0f) * 2.0f - 1.0f,
minVal * (1.0f / 255.0f) * 2.0f - 1.0f, 0.0f, 0.0f));
#else
outputBytes.x = packUnorm4x8(
vec4(maxVal * (1.0f / 255.0f), minVal * (1.0f / 255.0f), 0.0f, 0.0f));
#endif
outputBytes.x |= g_mask[maskIdxBase].x;
outputBytes.y = g_mask[maskIdxBase].y;
uvec2 dstUV = gl_GlobalInvocationID.yz;
imageStore(dstTexture, ivec2(dstUV), uvec4(outputBytes.xy, 0u, 0u));
}
}

741
modules/betsy/bc6h.glsl Normal file
View File

@@ -0,0 +1,741 @@
#[versions]
signed = "#define SIGNED";
unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned.
#[compute]
#version 450
#VERSION_DEFINES
vec3 f32tof16(vec3 value) {
return vec3(packHalf2x16(vec2(value.x, 0.0)),
packHalf2x16(vec2(value.y, 0.0)),
packHalf2x16(vec2(value.z, 0.0)));
}
vec3 f16tof32(uvec3 value) {
return vec3(unpackHalf2x16(value.x).x,
unpackHalf2x16(value.y).x,
unpackHalf2x16(value.z).x);
}
float f32tof16(float value) {
return packHalf2x16(vec2(value.x, 0.0));
}
float f16tof32(uint value) {
return unpackHalf2x16(value.x).x;
}
layout(binding = 0) uniform sampler2D srcTexture;
layout(binding = 1, rgba32ui) uniform restrict writeonly uimage2D dstTexture;
layout(push_constant, std430) uniform Params {
vec2 p_textureSizeRcp;
uint padding0;
uint padding1;
}
params;
const float HALF_MAX = 65504.0f;
const uint PATTERN_NUM = 32u;
#ifdef SIGNED
const float HALF_MIN = -65504.0f;
#else
const float HALF_MIN = 0.0f;
#endif
#ifdef SIGNED
// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254
// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950
bool isNegative(float a) {
return a < 0.0f;
}
float CalcSignlessMSLE(float a, float b) {
float err = log2((b + 1.0f) / (a + 1.0f));
err = err * err;
return err;
}
float CrossCalcMSLE(float a, float b) {
float result = 0.0f;
result += CalcSignlessMSLE(0.0f, abs(a));
result += CalcSignlessMSLE(0.0f, abs(b));
return result;
}
float CalcMSLE(vec3 a, vec3 b) {
float result = 0.0f;
if (isNegative(a.x) != isNegative(b.x)) {
result += CrossCalcMSLE(a.x, b.x);
} else {
result += CalcSignlessMSLE(abs(a.x), abs(b.x));
}
if (isNegative(a.y) != isNegative(b.y)) {
result += CrossCalcMSLE(a.y, b.y);
} else {
result += CalcSignlessMSLE(abs(a.y), abs(b.y));
}
if (isNegative(a.z) != isNegative(b.z)) {
result += CrossCalcMSLE(a.z, b.z);
} else {
result += CalcSignlessMSLE(abs(a.z), abs(b.z));
}
return result;
}
// Adapt the log function to make sense when a < 0
vec3 customLog2(vec3 a) {
return vec3(
a.x >= 0 ? log2(a.x + 1.0f) : -log2(-a.x + 1.0f),
a.y >= 0 ? log2(a.y + 1.0f) : -log2(-a.y + 1.0f),
a.z >= 0 ? log2(a.z + 1.0f) : -log2(-a.z + 1.0f));
}
// Inverse of customLog2()
vec3 customExp2(vec3 a) {
return vec3(
a.x >= 0 ? exp2(a.x) - 1.0f : -(exp2(-a.x) - 1.0f),
a.y >= 0 ? exp2(a.y) - 1.0f : -(exp2(-a.y) - 1.0f),
a.z >= 0 ? exp2(a.z) - 1.0f : -(exp2(-a.z) - 1.0f));
}
#else
float CalcMSLE(vec3 a, vec3 b) {
vec3 err = log2((b + 1.0f) / (a + 1.0f));
err = err * err;
return err.x + err.y + err.z;
}
vec3 customLog2(vec3 a) {
return log2(a + 1.0f);
}
vec3 customExp2(vec3 a) {
return exp2(a) - 1.0f;
}
#endif
uint PatternFixupID(uint i) {
uint ret = 15u;
ret = ((3441033216u >> i) & 0x1u) != 0 ? 2u : ret;
ret = ((845414400u >> i) & 0x1u) != 0 ? 8u : ret;
return ret;
}
uint Pattern(uint p, uint i) {
uint p2 = p / 2u;
uint p3 = p - p2 * 2u;
uint enc = 0u;
enc = p2 == 0u ? 2290666700u : enc;
enc = p2 == 1u ? 3972591342u : enc;
enc = p2 == 2u ? 4276930688u : enc;
enc = p2 == 3u ? 3967876808u : enc;
enc = p2 == 4u ? 4293707776u : enc;
enc = p2 == 5u ? 3892379264u : enc;
enc = p2 == 6u ? 4278255592u : enc;
enc = p2 == 7u ? 4026597360u : enc;
enc = p2 == 8u ? 9369360u : enc;
enc = p2 == 9u ? 147747072u : enc;
enc = p2 == 10u ? 1930428556u : enc;
enc = p2 == 11u ? 2362323200u : enc;
enc = p2 == 12u ? 823134348u : enc;
enc = p2 == 13u ? 913073766u : enc;
enc = p2 == 14u ? 267393000u : enc;
enc = p2 == 15u ? 966553998u : enc;
enc = p3 != 0u ? enc >> 16u : enc;
uint ret = (enc >> i) & 0x1u;
return ret;
}
#ifndef SIGNED
//UF
vec3 Quantize7(vec3 x) {
return (f32tof16(x) * 128.0f) / (0x7bff + 1.0f);
}
vec3 Quantize9(vec3 x) {
return (f32tof16(x) * 512.0f) / (0x7bff + 1.0f);
}
vec3 Quantize10(vec3 x) {
return (f32tof16(x) * 1024.0f) / (0x7bff + 1.0f);
}
vec3 Unquantize7(vec3 x) {
return (x * 65536.0f + 0x8000) / 128.0f;
}
vec3 Unquantize9(vec3 x) {
return (x * 65536.0f + 0x8000) / 512.0f;
}
vec3 Unquantize10(vec3 x) {
return (x * 65536.0f + 0x8000) / 1024.0f;
}
vec3 FinishUnquantize(vec3 endpoint0Unq, vec3 endpoint1Unq, float weight) {
vec3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 4096.0f);
return f16tof32(uvec3(comp));
}
#else
//SF
vec3 cmpSign(vec3 value) {
vec3 signVal;
signVal.x = value.x >= 0.0f ? 1.0f : -1.0f;
signVal.y = value.y >= 0.0f ? 1.0f : -1.0f;
signVal.z = value.z >= 0.0f ? 1.0f : -1.0f;
return signVal;
}
vec3 Quantize7(vec3 x) {
vec3 signVal = cmpSign(x);
return signVal * (f32tof16(abs(x)) * 64.0f) / (0x7bff + 1.0f);
}
vec3 Quantize9(vec3 x) {
vec3 signVal = cmpSign(x);
return signVal * (f32tof16(abs(x)) * 256.0f) / (0x7bff + 1.0f);
}
vec3 Quantize10(vec3 x) {
vec3 signVal = cmpSign(x);
return signVal * (f32tof16(abs(x)) * 512.0f) / (0x7bff + 1.0f);
}
vec3 Unquantize7(vec3 x) {
vec3 signVal = sign(x);
x = abs(x);
vec3 finalVal = signVal * (x * 32768.0f + 0x4000) / 64.0f;
finalVal.x = x.x >= 64.0f ? 32767.0 : finalVal.x;
finalVal.y = x.y >= 64.0f ? 32767.0 : finalVal.y;
finalVal.z = x.z >= 64.0f ? 32767.0 : finalVal.z;
return finalVal;
}
vec3 Unquantize9(vec3 x) {
vec3 signVal = sign(x);
x = abs(x);
vec3 finalVal = signVal * (x * 32768.0f + 0x4000) / 256.0f;
finalVal.x = x.x >= 256.0f ? 32767.0 : finalVal.x;
finalVal.y = x.y >= 256.0f ? 32767.0 : finalVal.y;
finalVal.z = x.z >= 256.0f ? 32767.0 : finalVal.z;
return finalVal;
}
vec3 Unquantize10(vec3 x) {
vec3 signVal = sign(x);
x = abs(x);
vec3 finalVal = signVal * (x * 32768.0f + 0x4000) / 512.0f;
finalVal.x = x.x >= 512.0f ? 32767.0 : finalVal.x;
finalVal.y = x.y >= 512.0f ? 32767.0 : finalVal.y;
finalVal.z = x.z >= 512.0f ? 32767.0 : finalVal.z;
return finalVal;
}
vec3 FinishUnquantize(vec3 endpoint0Unq, vec3 endpoint1Unq, float weight) {
vec3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f);
return f16tof32(uvec3(comp));
}
#endif
void Swap(inout vec3 a, inout vec3 b) {
vec3 tmp = a;
a = b;
b = tmp;
}
void Swap(inout float a, inout float b) {
float tmp = a;
a = b;
b = tmp;
}
uint ComputeIndex3(float texelPos, float endPoint0Pos, float endPoint1Pos) {
float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos);
return uint(clamp(r * 6.98182f + 0.00909f + 0.5f, 0.0f, 7.0f));
}
uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) {
float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos);
return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f));
}
// This adds a bitflag to quantized values that signifies whether they are negative.
void SignExtend(inout vec3 v1, uint mask, uint signFlag) {
ivec3 v = ivec3(v1);
v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0);
v.y = (v.y & int(mask)) | (v.y < 0 ? int(signFlag) : 0);
v.z = (v.z & int(mask)) | (v.z < 0 ? int(signFlag) : 0);
v1 = v;
}
// Encodes a block with mode 11 (2x 10-bit endpoints).
void EncodeP1(inout uvec4 block, inout float blockMSLE, vec3 texels[16]) {
// compute endpoints (min/max RGB bbox)
vec3 blockMin = texels[0];
vec3 blockMax = texels[0];
for (uint i = 1u; i < 16u; ++i) {
blockMin = min(blockMin, texels[i]);
blockMax = max(blockMax, texels[i]);
}
// refine endpoints in log2 RGB space
vec3 refinedBlockMin = blockMax;
vec3 refinedBlockMax = blockMin;
for (uint i = 0u; i < 16u; ++i) {
refinedBlockMin = min(refinedBlockMin, texels[i] == blockMin ? refinedBlockMin : texels[i]);
refinedBlockMax = max(refinedBlockMax, texels[i] == blockMax ? refinedBlockMax : texels[i]);
}
vec3 logBlockMax = customLog2(blockMax);
vec3 logBlockMin = customLog2(blockMin);
vec3 logRefinedBlockMax = customLog2(refinedBlockMax);
vec3 logRefinedBlockMin = customLog2(refinedBlockMin);
vec3 logBlockMaxExt = (logBlockMax - logBlockMin) * (1.0f / 32.0f);
logBlockMin += min(logRefinedBlockMin - logBlockMin, logBlockMaxExt);
logBlockMax -= min(logBlockMax - logRefinedBlockMax, logBlockMaxExt);
blockMin = customExp2(logBlockMin);
blockMax = customExp2(logBlockMax);
vec3 blockDir = blockMax - blockMin;
blockDir = blockDir / (blockDir.x + blockDir.y + blockDir.z);
vec3 endpoint0 = Quantize10(blockMin);
vec3 endpoint1 = Quantize10(blockMax);
float endPoint0Pos = f32tof16(dot(blockMin, blockDir));
float endPoint1Pos = f32tof16(dot(blockMax, blockDir));
#ifdef SIGNED
int maxVal10 = 0x1FF;
endpoint0 = clamp(endpoint0, -maxVal10, maxVal10);
endpoint1 = clamp(endpoint1, -maxVal10, maxVal10);
#endif
// check if endpoint swap is required
float fixupTexelPos = f32tof16(dot(texels[0], blockDir));
uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos);
if (fixupIndex > 7) {
Swap(endPoint0Pos, endPoint1Pos);
Swap(endpoint0, endpoint1);
}
// compute indices
uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u };
for (uint i = 0u; i < 16u; ++i) {
float texelPos = f32tof16(dot(texels[i], blockDir));
indices[i] = ComputeIndex4(texelPos, endPoint0Pos, endPoint1Pos);
}
// compute compression error (MSLE)
vec3 endpoint0Unq = Unquantize10(endpoint0);
vec3 endpoint1Unq = Unquantize10(endpoint1);
float msle = 0.0f;
for (uint i = 0u; i < 16u; ++i) {
float weight = floor((indices[i] * 64.0f) / 15.0f + 0.5f);
vec3 texelUnc = FinishUnquantize(endpoint0Unq, endpoint1Unq, weight);
msle += CalcMSLE(texels[i], texelUnc);
}
#ifdef SIGNED
SignExtend(endpoint0, 0x1FF, 0x200);
SignExtend(endpoint1, 0x1FF, 0x200);
#endif
// encode block for mode 11
blockMSLE = msle;
block.x = 0x03;
// endpoints
block.x |= uint(endpoint0.x) << 5u;
block.x |= uint(endpoint0.y) << 15u;
block.x |= uint(endpoint0.z) << 25u;
block.y |= uint(endpoint0.z) >> 7u;
block.y |= uint(endpoint1.x) << 3u;
block.y |= uint(endpoint1.y) << 13u;
block.y |= uint(endpoint1.z) << 23u;
block.z |= uint(endpoint1.z) >> 9u;
// indices
block.z |= indices[0] << 1u;
block.z |= indices[1] << 4u;
block.z |= indices[2] << 8u;
block.z |= indices[3] << 12u;
block.z |= indices[4] << 16u;
block.z |= indices[5] << 20u;
block.z |= indices[6] << 24u;
block.z |= indices[7] << 28u;
block.w |= indices[8] << 0u;
block.w |= indices[9] << 4u;
block.w |= indices[10] << 8u;
block.w |= indices[11] << 12u;
block.w |= indices[12] << 16u;
block.w |= indices[13] << 20u;
block.w |= indices[14] << 24u;
block.w |= indices[15] << 28u;
}
float DistToLineSq(vec3 PointOnLine, vec3 LineDirection, vec3 Point) {
vec3 w = Point - PointOnLine;
vec3 x = w - dot(w, LineDirection) * LineDirection;
return dot(x, x);
}
// Gets the deviation from the source data of a particular pattern (smaller is better).
float EvaluateP2Pattern(uint pattern, vec3 texels[16]) {
vec3 p0BlockMin = vec3(HALF_MAX, HALF_MAX, HALF_MAX);
vec3 p0BlockMax = vec3(HALF_MIN, HALF_MIN, HALF_MIN);
vec3 p1BlockMin = vec3(HALF_MAX, HALF_MAX, HALF_MAX);
vec3 p1BlockMax = vec3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0; i < 16; ++i) {
uint paletteID = Pattern(pattern, i);
if (paletteID == 0) {
p0BlockMin = min(p0BlockMin, texels[i]);
p0BlockMax = max(p0BlockMax, texels[i]);
} else {
p1BlockMin = min(p1BlockMin, texels[i]);
p1BlockMax = max(p1BlockMax, texels[i]);
}
}
vec3 p0BlockDir = normalize(p0BlockMax - p0BlockMin);
vec3 p1BlockDir = normalize(p1BlockMax - p1BlockMin);
float sqDistanceFromLine = 0.0f;
for (uint i = 0; i < 16; ++i) {
uint paletteID = Pattern(pattern, i);
if (paletteID == 0) {
sqDistanceFromLine += DistToLineSq(p0BlockMin, p0BlockDir, texels[i]);
} else {
sqDistanceFromLine += DistToLineSq(p1BlockMin, p1BlockDir, texels[i]);
}
}
return sqDistanceFromLine;
}
// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding.
void EncodeP2Pattern(inout uvec4 block, inout float blockMSLE, uint pattern, vec3 texels[16]) {
vec3 p0BlockMin = vec3(HALF_MAX, HALF_MAX, HALF_MAX);
vec3 p0BlockMax = vec3(HALF_MIN, HALF_MIN, HALF_MIN);
vec3 p1BlockMin = vec3(HALF_MAX, HALF_MAX, HALF_MAX);
vec3 p1BlockMax = vec3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0u; i < 16u; ++i) {
uint paletteID = Pattern(pattern, i);
if (paletteID == 0) {
p0BlockMin = min(p0BlockMin, texels[i]);
p0BlockMax = max(p0BlockMax, texels[i]);
} else {
p1BlockMin = min(p1BlockMin, texels[i]);
p1BlockMax = max(p1BlockMax, texels[i]);
}
}
vec3 p0BlockDir = p0BlockMax - p0BlockMin;
vec3 p1BlockDir = p1BlockMax - p1BlockMin;
p0BlockDir = p0BlockDir / (p0BlockDir.x + p0BlockDir.y + p0BlockDir.z);
p1BlockDir = p1BlockDir / (p1BlockDir.x + p1BlockDir.y + p1BlockDir.z);
float p0Endpoint0Pos = f32tof16(dot(p0BlockMin, p0BlockDir));
float p0Endpoint1Pos = f32tof16(dot(p0BlockMax, p0BlockDir));
float p1Endpoint0Pos = f32tof16(dot(p1BlockMin, p1BlockDir));
float p1Endpoint1Pos = f32tof16(dot(p1BlockMax, p1BlockDir));
uint fixupID = PatternFixupID(pattern);
float p0FixupTexelPos = f32tof16(dot(texels[0], p0BlockDir));
float p1FixupTexelPos = f32tof16(dot(texels[fixupID], p1BlockDir));
uint p0FixupIndex = ComputeIndex3(p0FixupTexelPos, p0Endpoint0Pos, p0Endpoint1Pos);
uint p1FixupIndex = ComputeIndex3(p1FixupTexelPos, p1Endpoint0Pos, p1Endpoint1Pos);
if (p0FixupIndex > 3u) {
Swap(p0Endpoint0Pos, p0Endpoint1Pos);
Swap(p0BlockMin, p0BlockMax);
}
if (p1FixupIndex > 3u) {
Swap(p1Endpoint0Pos, p1Endpoint1Pos);
Swap(p1BlockMin, p1BlockMax);
}
uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u };
for (uint i = 0u; i < 16u; ++i) {
float p0TexelPos = f32tof16(dot(texels[i], p0BlockDir));
float p1TexelPos = f32tof16(dot(texels[i], p1BlockDir));
uint p0Index = ComputeIndex3(p0TexelPos, p0Endpoint0Pos, p0Endpoint1Pos);
uint p1Index = ComputeIndex3(p1TexelPos, p1Endpoint0Pos, p1Endpoint1Pos);
uint paletteID = Pattern(pattern, i);
indices[i] = paletteID == 0u ? p0Index : p1Index;
}
vec3 endpoint760 = floor(Quantize7(p0BlockMin));
vec3 endpoint761 = floor(Quantize7(p0BlockMax));
vec3 endpoint762 = floor(Quantize7(p1BlockMin));
vec3 endpoint763 = floor(Quantize7(p1BlockMax));
vec3 endpoint950 = floor(Quantize9(p0BlockMin));
vec3 endpoint951 = floor(Quantize9(p0BlockMax));
vec3 endpoint952 = floor(Quantize9(p1BlockMin));
vec3 endpoint953 = floor(Quantize9(p1BlockMax));
endpoint761 = endpoint761 - endpoint760;
endpoint762 = endpoint762 - endpoint760;
endpoint763 = endpoint763 - endpoint760;
endpoint951 = endpoint951 - endpoint950;
endpoint952 = endpoint952 - endpoint950;
endpoint953 = endpoint953 - endpoint950;
int maxVal76 = 0x1F;
endpoint761 = clamp(endpoint761, -maxVal76, maxVal76);
endpoint762 = clamp(endpoint762, -maxVal76, maxVal76);
endpoint763 = clamp(endpoint763, -maxVal76, maxVal76);
int maxVal95 = 0xF;
endpoint951 = clamp(endpoint951, -maxVal95, maxVal95);
endpoint952 = clamp(endpoint952, -maxVal95, maxVal95);
endpoint953 = clamp(endpoint953, -maxVal95, maxVal95);
#ifdef SIGNED
int maxVal7 = 0x3F;
int maxVal9 = 0xFF;
endpoint760 = clamp(endpoint760, -maxVal7, maxVal7);
endpoint950 = clamp(endpoint950, -maxVal9, maxVal9);
#endif
vec3 endpoint760Unq = Unquantize7(endpoint760);
vec3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761);
vec3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762);
vec3 endpoint763Unq = Unquantize7(endpoint760 + endpoint763);
vec3 endpoint950Unq = Unquantize9(endpoint950);
vec3 endpoint951Unq = Unquantize9(endpoint950 + endpoint951);
vec3 endpoint952Unq = Unquantize9(endpoint950 + endpoint952);
vec3 endpoint953Unq = Unquantize9(endpoint950 + endpoint953);
float msle76 = 0.0f;
float msle95 = 0.0f;
for (uint i = 0u; i < 16u; ++i) {
uint paletteID = Pattern(pattern, i);
vec3 tmp760Unq = paletteID == 0u ? endpoint760Unq : endpoint762Unq;
vec3 tmp761Unq = paletteID == 0u ? endpoint761Unq : endpoint763Unq;
vec3 tmp950Unq = paletteID == 0u ? endpoint950Unq : endpoint952Unq;
vec3 tmp951Unq = paletteID == 0u ? endpoint951Unq : endpoint953Unq;
float weight = floor((indices[i] * 64.0f) / 7.0f + 0.5f);
vec3 texelUnc76 = FinishUnquantize(tmp760Unq, tmp761Unq, weight);
vec3 texelUnc95 = FinishUnquantize(tmp950Unq, tmp951Unq, weight);
msle76 += CalcMSLE(texels[i], texelUnc76);
msle95 += CalcMSLE(texels[i], texelUnc95);
}
SignExtend(endpoint761, 0x1F, 0x20);
SignExtend(endpoint762, 0x1F, 0x20);
SignExtend(endpoint763, 0x1F, 0x20);
SignExtend(endpoint951, 0xF, 0x10);
SignExtend(endpoint952, 0xF, 0x10);
SignExtend(endpoint953, 0xF, 0x10);
#ifdef SIGNED
SignExtend(endpoint760, 0x3F, 0x40);
SignExtend(endpoint950, 0xFF, 0x100);
#endif
// encode block
float p2MSLE = min(msle76, msle95);
if (p2MSLE < blockMSLE) {
blockMSLE = p2MSLE;
block = uvec4(0u, 0u, 0u, 0u);
if (p2MSLE == msle76) {
// 7.6
block.x = 0x1u;
block.x |= (uint(endpoint762.y) & 0x20u) >> 3u;
block.x |= (uint(endpoint763.y) & 0x10u) >> 1u;
block.x |= (uint(endpoint763.y) & 0x20u) >> 1u;
block.x |= uint(endpoint760.x) << 5u;
block.x |= (uint(endpoint763.z) & 0x01u) << 12u;
block.x |= (uint(endpoint763.z) & 0x02u) << 12u;
block.x |= (uint(endpoint762.z) & 0x10u) << 10u;
block.x |= uint(endpoint760.y) << 15u;
block.x |= (uint(endpoint762.z) & 0x20u) << 17u;
block.x |= (uint(endpoint763.z) & 0x04u) << 21u;
block.x |= (uint(endpoint762.y) & 0x10u) << 20u;
block.x |= uint(endpoint760.z) << 25u;
block.y |= (uint(endpoint763.z) & 0x08u) >> 3u;
block.y |= (uint(endpoint763.z) & 0x20u) >> 4u;
block.y |= (uint(endpoint763.z) & 0x10u) >> 2u;
block.y |= uint(endpoint761.x) << 3u;
block.y |= (uint(endpoint762.y) & 0x0Fu) << 9u;
block.y |= uint(endpoint761.y) << 13u;
block.y |= (uint(endpoint763.y) & 0x0Fu) << 19u;
block.y |= uint(endpoint761.z) << 23u;
block.y |= (uint(endpoint762.z) & 0x07u) << 29u;
block.z |= (uint(endpoint762.z) & 0x08u) >> 3u;
block.z |= uint(endpoint762.x) << 1u;
block.z |= uint(endpoint763.x) << 7u;
} else {
// 9.5
block.x = 0xEu;
block.x |= uint(endpoint950.x) << 5u;
block.x |= (uint(endpoint952.z) & 0x10u) << 10u;
block.x |= uint(endpoint950.y) << 15u;
block.x |= (uint(endpoint952.y) & 0x10u) << 20u;
block.x |= uint(endpoint950.z) << 25u;
block.y |= uint(endpoint950.z) >> 7u;
block.y |= (uint(endpoint953.z) & 0x10u) >> 2u;
block.y |= uint(endpoint951.x) << 3u;
block.y |= (uint(endpoint953.y) & 0x10u) << 4u;
block.y |= (uint(endpoint952.y) & 0x0Fu) << 9u;
block.y |= uint(endpoint951.y) << 13u;
block.y |= (uint(endpoint953.z) & 0x01u) << 18u;
block.y |= (uint(endpoint953.y) & 0x0Fu) << 19u;
block.y |= uint(endpoint951.z) << 23u;
block.y |= (uint(endpoint953.z) & 0x02u) << 27u;
block.y |= uint(endpoint952.z) << 29u;
block.z |= (uint(endpoint952.z) & 0x08u) >> 3u;
block.z |= uint(endpoint952.x) << 1u;
block.z |= (uint(endpoint953.z) & 0x04u) << 4u;
block.z |= uint(endpoint953.x) << 7u;
block.z |= (uint(endpoint953.z) & 0x08u) << 9u;
}
block.z |= pattern << 13u;
uint blockFixupID = PatternFixupID(pattern);
if (blockFixupID == 15u) {
block.z |= indices[0] << 18u;
block.z |= indices[1] << 20u;
block.z |= indices[2] << 23u;
block.z |= indices[3] << 26u;
block.z |= indices[4] << 29u;
block.w |= indices[5] << 0u;
block.w |= indices[6] << 3u;
block.w |= indices[7] << 6u;
block.w |= indices[8] << 9u;
block.w |= indices[9] << 12u;
block.w |= indices[10] << 15u;
block.w |= indices[11] << 18u;
block.w |= indices[12] << 21u;
block.w |= indices[13] << 24u;
block.w |= indices[14] << 27u;
block.w |= indices[15] << 30u;
} else if (blockFixupID == 2u) {
block.z |= indices[0] << 18u;
block.z |= indices[1] << 20u;
block.z |= indices[2] << 23u;
block.z |= indices[3] << 25u;
block.z |= indices[4] << 28u;
block.z |= indices[5] << 31u;
block.w |= indices[5] >> 1u;
block.w |= indices[6] << 2u;
block.w |= indices[7] << 5u;
block.w |= indices[8] << 8u;
block.w |= indices[9] << 11u;
block.w |= indices[10] << 14u;
block.w |= indices[11] << 17u;
block.w |= indices[12] << 20u;
block.w |= indices[13] << 23u;
block.w |= indices[14] << 26u;
block.w |= indices[15] << 29u;
} else {
block.z |= indices[0] << 18u;
block.z |= indices[1] << 20u;
block.z |= indices[2] << 23u;
block.z |= indices[3] << 26u;
block.z |= indices[4] << 29u;
block.w |= indices[5] << 0u;
block.w |= indices[6] << 3u;
block.w |= indices[7] << 6u;
block.w |= indices[8] << 9u;
block.w |= indices[9] << 11u;
block.w |= indices[10] << 14u;
block.w |= indices[11] << 17u;
block.w |= indices[12] << 20u;
block.w |= indices[13] << 23u;
block.w |= indices[14] << 26u;
block.w |= indices[15] << 29u;
}
}
}
layout(local_size_x = 8,
local_size_y = 8,
local_size_z = 1) in;
void main() {
// gather texels for current 4x4 block
// 0 1 2 3
// 4 5 6 7
// 8 9 10 11
// 12 13 14 15
vec2 uv = gl_GlobalInvocationID.xy * params.p_textureSizeRcp * 4.0f + params.p_textureSizeRcp;
vec2 block0UV = uv;
vec2 block1UV = uv + vec2(2.0f * params.p_textureSizeRcp.x, 0.0f);
vec2 block2UV = uv + vec2(0.0f, 2.0f * params.p_textureSizeRcp.y);
vec2 block3UV = uv + vec2(2.0f * params.p_textureSizeRcp.x, 2.0f * params.p_textureSizeRcp.y);
vec4 block0X = textureGather(srcTexture, block0UV, 0);
vec4 block1X = textureGather(srcTexture, block1UV, 0);
vec4 block2X = textureGather(srcTexture, block2UV, 0);
vec4 block3X = textureGather(srcTexture, block3UV, 0);
vec4 block0Y = textureGather(srcTexture, block0UV, 1);
vec4 block1Y = textureGather(srcTexture, block1UV, 1);
vec4 block2Y = textureGather(srcTexture, block2UV, 1);
vec4 block3Y = textureGather(srcTexture, block3UV, 1);
vec4 block0Z = textureGather(srcTexture, block0UV, 2);
vec4 block1Z = textureGather(srcTexture, block1UV, 2);
vec4 block2Z = textureGather(srcTexture, block2UV, 2);
vec4 block3Z = textureGather(srcTexture, block3UV, 2);
vec3 texels[16];
texels[0] = vec3(block0X.w, block0Y.w, block0Z.w);
texels[1] = vec3(block0X.z, block0Y.z, block0Z.z);
texels[2] = vec3(block1X.w, block1Y.w, block1Z.w);
texels[3] = vec3(block1X.z, block1Y.z, block1Z.z);
texels[4] = vec3(block0X.x, block0Y.x, block0Z.x);
texels[5] = vec3(block0X.y, block0Y.y, block0Z.y);
texels[6] = vec3(block1X.x, block1Y.x, block1Z.x);
texels[7] = vec3(block1X.y, block1Y.y, block1Z.y);
texels[8] = vec3(block2X.w, block2Y.w, block2Z.w);
texels[9] = vec3(block2X.z, block2Y.z, block2Z.z);
texels[10] = vec3(block3X.w, block3Y.w, block3Z.w);
texels[11] = vec3(block3X.z, block3Y.z, block3Z.z);
texels[12] = vec3(block2X.x, block2Y.x, block2Z.x);
texels[13] = vec3(block2X.y, block2Y.y, block2Z.y);
texels[14] = vec3(block3X.x, block3Y.x, block3Z.x);
texels[15] = vec3(block3X.y, block3Y.y, block3Z.y);
uvec4 block = uvec4(0u, 0u, 0u, 0u);
float blockMSLE = 0.0f;
EncodeP1(block, blockMSLE, texels);
#ifdef QUALITY
float bestScore = EvaluateP2Pattern(0, texels);
uint bestPattern = 0;
for (uint i = 1u; i < PATTERN_NUM; ++i) {
float score = EvaluateP2Pattern(i, texels);
if (score < bestScore) {
bestPattern = i;
bestScore = score;
}
}
EncodeP2Pattern(block, blockMSLE, bestPattern, texels);
#endif
imageStore(dstTexture, ivec2(gl_GlobalInvocationID.xy), block);
}

1058
modules/betsy/betsy_bc1.h Normal file

File diff suppressed because it is too large Load Diff

6
modules/betsy/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return env.editor_build
def configure(env):
pass

View File

@@ -0,0 +1,775 @@
/**************************************************************************/
/* image_compress_betsy.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_compress_betsy.h"
#include "core/config/project_settings.h"
#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"
#include "servers/display_server.h"
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;
}
// Create local RD.
RenderingContextDriver *rcd = nullptr;
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
if (rd == nullptr) {
#if defined(RD_ENABLED)
#if defined(METAL_ENABLED)
rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
#if defined(VULKAN_ENABLED)
if (rcd == nullptr) {
rcd = memnew(RenderingContextDriverVulkan);
rd = memnew(RenderingDevice);
}
#endif
#endif
if (rcd != nullptr && rd != nullptr) {
Error err = rcd->initialize();
if (err == OK) {
err = rd->initialize(rcd);
}
if (err != OK) {
memdelete(rd);
memdelete(rcd);
rd = nullptr;
rcd = nullptr;
}
}
}
ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice.");
compress_rd = rd;
compress_rcd = rcd;
// Create the sampler state.
RD::SamplerState src_sampler_state;
{
src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;
src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;
src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;
}
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() {
WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true, "Betsy pump task", true);
command_queue.set_pump_task_id(tid);
command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid);
command_queue.push_and_sync(this, &BetsyCompressor::_init);
DEV_ASSERT(task_id == tid);
}
void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
task_id = p_pump_task_id;
}
// Yield thread to WTP so other tasks can be done on it.
// Automatically regains control as soon a task is pushed to the command queue.
void BetsyCompressor::_thread_loop() {
while (!exit) {
WorkerThreadPool::get_singleton()->yield();
command_queue.flush_all();
}
}
void BetsyCompressor::_thread_exit() {
exit = true;
if (compress_rd != nullptr) {
if (dxt1_encoding_table_buffer.is_valid()) {
compress_rd->free(dxt1_encoding_table_buffer);
}
compress_rd->free(src_sampler);
// Clear the shader cache, pipelines will be unreferenced automatically.
for (int i = 0; i < BETSY_SHADER_MAX; i++) {
if (cached_shaders[i].compiled.is_valid()) {
compress_rd->free(cached_shaders[i].compiled);
}
}
// Free the RD (and RCD if necessary).
memdelete(compress_rd);
compress_rd = nullptr;
if (compress_rcd != nullptr) {
memdelete(compress_rcd);
compress_rcd = nullptr;
}
}
}
void BetsyCompressor::finish() {
command_queue.push(this, &BetsyCompressor::_thread_exit);
if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
task_id = WorkerThreadPool::INVALID_TASK_ID;
}
}
// Helper functions.
static int get_next_multiple(int n, int m) {
return n + (m - (n % m));
}
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 Image::FORMAT_LA8:
r_img->convert(Image::FORMAT_RGBA8);
r_format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
break;
case Image::FORMAT_R8:
r_format = RD::DATA_FORMAT_R8_UNORM;
break;
case Image::FORMAT_RG8:
r_format = RD::DATA_FORMAT_R8G8_UNORM;
break;
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) {
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;
}
int img_width = r_img->get_width();
int img_height = r_img->get_height();
if (img_width % 4 != 0 || img_height % 4 != 0) {
img_width = img_width <= 2 ? img_width : (img_width + 3) & ~3;
img_height = img_height <= 2 ? img_height : (img_height + 3) & ~3;
}
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 || dest_format == Image::FORMAT_DXT5) && dxt1_encoding_table_buffer.is_null()) {
dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, Span(dxt1_encoding_table).reinterpret<uint8_t>());
}
const int mip_count = r_img->get_mipmap_count() + 1;
// Container for the compressed data.
Vector<uint8_t> dst_data;
dst_data.resize(Image::get_image_data_size(img_width, img_height, dest_format, r_img->has_mipmaps()));
uint8_t *dst_data_ptr = dst_data.ptrw();
Vector<Vector<uint8_t>> src_images;
src_images.push_back(Vector<uint8_t>());
Vector<uint8_t> *src_image_ptr = src_images.ptrw();
// Compress each mipmap.
for (int i = 0; i < mip_count; i++) {
int width, height;
Image::get_image_mipmap_offset_and_dimensions(img_width, img_height, dest_format, i, width, height);
int64_t src_mip_ofs, src_mip_size;
int src_mip_w, src_mip_h;
r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
// Set the source texture width and size.
src_texture_format.height = height;
src_texture_format.width = width;
// Set the destination texture width and size.
dst_texture_format.height = (height + 3) >> 2;
dst_texture_format.width = (width + 3) >> 2;
// Pad textures to nearest block by smearing.
if (width != src_mip_w || height != src_mip_h) {
const uint8_t *src_mip_read = r_img->ptr() + src_mip_ofs;
// Reserve the buffer for padded image data.
int px_size = Image::get_format_pixel_size(r_img->get_format());
src_image_ptr[0].resize(width * height * px_size);
uint8_t *ptrw = src_image_ptr[0].ptrw();
int x = 0, y = 0;
for (y = 0; y < src_mip_h; y++) {
for (x = 0; x < src_mip_w; x++) {
memcpy(ptrw + (width * y + x) * px_size, src_mip_read + (src_mip_w * y + x) * px_size, px_size);
}
// First, smear in x.
for (; x < width; x++) {
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - 1) * px_size, px_size);
}
}
// Then, smear in y.
for (; y < height; y++) {
for (x = 0; x < width; x++) {
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - width) * px_size, px_size);
}
}
} else {
// Create a buffer filled with the source mip layer data.
src_image_ptr[0].resize(src_mip_size);
memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + src_mip_ofs, src_mip_size);
}
// Create the textures on the GPU.
RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);
RID dst_texture_primary = compress_rd->texture_create(dst_texture_format, 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(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);
}
}
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 dst_texture_rid = dst_texture_primary;
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;
RID dst_texture_alpha = compress_rd->texture_create(dst_texture_format_alpha, 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(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);
BC4PushConstant push_constant;
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);
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);
}
// Copy data from the GPU to the buffer.
const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture_rid, 0);
int64_t dst_ofs = Image::get_image_mipmap_offset(img_width, img_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(src_texture);
compress_rd->free(dst_texture_rid);
}
src_images.clear();
// Set the compressed data to the image.
r_img->set_data(img_width, img_height, r_img->has_mipmaps(), dest_format, dst_data);
print_verbose(
vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",
img_width,
img_height,
r_img->get_mipmap_count(),
Image::get_format_name(dest_format),
OS::get_singleton()->get_ticks_msec() - start_time));
return OK;
}
void ensure_betsy_exists() {
betsy_mutex.lock();
if (betsy == nullptr) {
betsy = memnew(BetsyCompressor);
betsy->init();
}
betsy_mutex.unlock();
}
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) {
ensure_betsy_exists();
Image::Format format = r_img->get_format();
Error result = ERR_UNAVAILABLE;
if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) {
if (r_img->detect_signed()) {
result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img);
} else {
result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img);
}
}
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
free_device();
}
return result;
}
Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
ensure_betsy_exists();
Error result = ERR_UNAVAILABLE;
switch (p_channels) {
case Image::USED_CHANNELS_RGB:
case Image::USED_CHANNELS_L:
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;
}
if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
free_device();
}
return result;
}
void free_device() {
if (betsy != nullptr) {
betsy->finish();
memdelete(betsy);
}
}

View File

@@ -0,0 +1,129 @@
/**************************************************************************/
/* image_compress_betsy.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/thread.h"
#include "core/templates/command_queue_mt.h"
#include "servers/rendering/rendering_device_binds.h"
#include "servers/rendering/rendering_server_default.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
#if defined(METAL_ENABLED)
#include "drivers/metal/rendering_context_driver_metal.h"
#endif
enum BetsyFormat {
BETSY_FORMAT_BC1,
BETSY_FORMAT_BC1_DITHER,
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] = { 0 };
};
struct BC1PushConstant {
uint32_t num_refines;
uint32_t padding[3] = { 0 };
};
struct BC4PushConstant {
uint32_t channel_idx;
uint32_t padding[3] = { 0 };
};
void free_device();
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels);
Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels);
class BetsyCompressor : public Object {
mutable CommandQueueMT command_queue;
bool exit = false;
WorkerThreadPool::TaskID task_id = WorkerThreadPool::INVALID_TASK_ID;
struct BetsyShader {
RID compiled;
RID pipeline;
};
// Resources shared by all compression formats.
RenderingDevice *compress_rd = nullptr;
RenderingContextDriver *compress_rcd = nullptr;
BetsyShader cached_shaders[BETSY_SHADER_MAX];
RID src_sampler;
// Format-specific resources.
RID dxt1_encoding_table_buffer;
void _init();
void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id);
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:
void init();
void finish();
Error compress(BetsyFormat p_format, Image *r_img) {
Error err;
command_queue.push_and_ret(this, &BetsyCompressor::_compress, &err, p_format, r_img);
return err;
}
};

View File

@@ -0,0 +1,50 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_compress_betsy.h"
void initialize_betsy_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
Image::_image_compress_bptc_rd_func = _betsy_compress_bptc;
Image::_image_compress_bc_rd_func = _betsy_compress_s3tc;
}
void uninitialize_betsy_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
free_device();
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_betsy_module(ModuleInitializationLevel p_level);
void uninitialize_betsy_module(ModuleInitializationLevel p_level);

10
modules/bmp/SCsub Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_bmp = env_modules.Clone()
# Godot source files
env_bmp.add_source_files(env.modules_sources, "*.cpp")

6
modules/bmp/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return True
def configure(env):
pass

View File

@@ -0,0 +1,333 @@
/**************************************************************************/
/* image_loader_bmp.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_loader_bmp.h"
#include "core/io/file_access_memory.h"
static uint8_t get_mask_width(uint16_t mask) {
// Returns number of ones in the binary value of the parameter: mask.
// Uses a Simple pop_count.
uint8_t c = 0u;
for (; mask != 0u; mask &= mask - 1u) {
c++;
}
return c;
}
Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
const uint8_t *p_buffer,
const uint8_t *p_color_buffer,
const uint32_t color_table_size,
const bmp_header_s &p_header) {
Error err = OK;
if (p_buffer == nullptr) {
err = FAILED;
}
if (err == OK) {
size_t index = 0;
size_t width = (size_t)p_header.bmp_info_header.bmp_width;
size_t height = (size_t)p_header.bmp_info_header.bmp_height;
size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count;
// Image data (might be indexed)
Vector<uint8_t> data;
int data_len = 0;
if (bits_per_pixel <= 8) { // indexed
data_len = width * height;
} else { // color
data_len = width * height * 4;
}
ERR_FAIL_COND_V_MSG(data_len == 0, ERR_BUG, "Couldn't parse the BMP image data.");
err = data.resize(data_len);
uint8_t *data_w = data.ptrw();
uint8_t *write_buffer = data_w;
const uint32_t width_bytes = (width * bits_per_pixel + 7) / 8;
const uint32_t line_width = (width_bytes + 3) & ~3; // Padded to 4 bytes.
const uint8_t *line = p_buffer + (line_width * (height - 1));
const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset;
ERR_FAIL_COND_V(line + line_width > end_buffer, ERR_FILE_CORRUPT);
for (uint64_t i = 0; i < height; i++) {
const uint8_t *line_ptr = line;
for (unsigned int j = 0; j < width; j++) {
switch (bits_per_pixel) {
case 1: {
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 8))) & 0x01;
index++;
} break;
case 2: {
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 4))) & 0x03;
index++;
} break;
case 4: {
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 2))) & 0x0f;
index++;
} break;
case 8: {
uint8_t color_index = *line_ptr;
write_buffer[index] = color_index;
index += 1;
line_ptr += 1;
} break;
case 16: {
uint16_t rgb = (static_cast<uint16_t>(line_ptr[1]) << 8) | line_ptr[0];
// A1R5G5B5/X1R5G5B5 => uint16_t
// [A/X]1R5G2 | G3B5 => uint8_t | uint8_t
uint8_t ba = (rgb & p_header.bmp_bitfield.alpha_mask) >> p_header.bmp_bitfield.alpha_offset; // Alpha 0b 1000 ...
uint8_t b0 = (rgb & p_header.bmp_bitfield.red_mask) >> p_header.bmp_bitfield.red_offset; // Red 0b 0111 1100 ...
uint8_t b1 = (rgb & p_header.bmp_bitfield.green_mask) >> p_header.bmp_bitfield.green_offset; // Green 0b 0000 0011 1110 ...
uint8_t b2 = (rgb & p_header.bmp_bitfield.blue_mask); // >> p_header.bmp_bitfield.blue_offset; // Blue 0b ... 0001 1111
// Next we apply some color scaling going from a variable value space to a 256 value space.
// This may be simplified some but left as is for legibility.
// float scaled_value = unscaled_value * byte_max_value / color_channel_maximum_value + rounding_offset;
float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f;
float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f;
float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f;
write_buffer[index + 0] = static_cast<uint8_t>(f0); // R
write_buffer[index + 1] = static_cast<uint8_t>(f1); // G
write_buffer[index + 2] = static_cast<uint8_t>(f2); // B
if (p_header.bmp_bitfield.alpha_mask_width > 0) {
write_buffer[index + 3] = ba * 0xFF; // Alpha value(Always true or false so no scaling)
} else {
write_buffer[index + 3] = 0xFF; // No Alpha channel, Show everything.
}
index += 4;
line_ptr += 2;
} break;
case 24: {
write_buffer[index + 2] = line_ptr[0];
write_buffer[index + 1] = line_ptr[1];
write_buffer[index + 0] = line_ptr[2];
write_buffer[index + 3] = 0xff;
index += 4;
line_ptr += 3;
} break;
case 32: {
write_buffer[index + 2] = line_ptr[0];
write_buffer[index + 1] = line_ptr[1];
write_buffer[index + 0] = line_ptr[2];
write_buffer[index + 3] = line_ptr[3];
index += 4;
line_ptr += 4;
} break;
}
}
line -= line_width;
}
if (p_color_buffer == nullptr || color_table_size == 0) { // regular pixels
p_image->set_data(width, height, false, Image::FORMAT_RGBA8, data);
} else { // data is in indexed format, extend it
// Palette data
Vector<uint8_t> palette_data;
palette_data.resize(color_table_size * 4);
uint8_t *palette_data_w = palette_data.ptrw();
uint8_t *pal = palette_data_w;
const uint8_t *cb = p_color_buffer;
for (unsigned int i = 0; i < color_table_size; ++i) {
pal[i * 4 + 0] = cb[2];
pal[i * 4 + 1] = cb[1];
pal[i * 4 + 2] = cb[0];
pal[i * 4 + 3] = 0xff;
cb += 4;
}
// Extend palette to image
Vector<uint8_t> extended_data;
extended_data.resize(data.size() * 4);
uint8_t *ex_w = extended_data.ptrw();
uint8_t *dest = ex_w;
const int num_pixels = width * height;
for (int i = 0; i < num_pixels; i++) {
dest[0] = pal[write_buffer[i] * 4 + 0];
dest[1] = pal[write_buffer[i] * 4 + 1];
dest[2] = pal[write_buffer[i] * 4 + 2];
dest[3] = pal[write_buffer[i] * 4 + 3];
dest += 4;
}
p_image->set_data(width, height, false, Image::FORMAT_RGBA8, extended_data);
}
}
return err;
}
Error ImageLoaderBMP::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) {
bmp_header_s bmp_header;
Error err = ERR_INVALID_DATA;
// A valid bmp file should always at least have a
// file header and a minimal info header
if (f->get_length() > BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_MIN_SIZE) {
// File Header
bmp_header.bmp_file_header.bmp_signature = f->get_16();
if (bmp_header.bmp_file_header.bmp_signature == BITMAP_SIGNATURE) {
bmp_header.bmp_file_header.bmp_file_size = f->get_32();
bmp_header.bmp_file_header.bmp_file_padding = f->get_32();
bmp_header.bmp_file_header.bmp_file_offset = f->get_32();
// Info Header
bmp_header.bmp_info_header.bmp_header_size = f->get_32();
ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT,
vformat("Couldn't parse the BMP info header. The file is likely corrupt: %s", f->get_path()));
bmp_header.bmp_info_header.bmp_width = f->get_32();
bmp_header.bmp_info_header.bmp_height = f->get_32();
bmp_header.bmp_info_header.bmp_planes = f->get_16();
ERR_FAIL_COND_V_MSG(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT,
vformat("Couldn't parse the BMP planes. The file is likely corrupt: %s", f->get_path()));
bmp_header.bmp_info_header.bmp_bit_count = f->get_16();
bmp_header.bmp_info_header.bmp_compression = f->get_32();
bmp_header.bmp_info_header.bmp_size_image = f->get_32();
bmp_header.bmp_info_header.bmp_pixels_per_meter_x = f->get_32();
bmp_header.bmp_info_header.bmp_pixels_per_meter_y = f->get_32();
bmp_header.bmp_info_header.bmp_colors_used = f->get_32();
bmp_header.bmp_info_header.bmp_important_colors = f->get_32();
switch (bmp_header.bmp_info_header.bmp_compression) {
case BI_BITFIELDS: {
bmp_header.bmp_bitfield.red_mask = f->get_32();
bmp_header.bmp_bitfield.green_mask = f->get_32();
bmp_header.bmp_bitfield.blue_mask = f->get_32();
bmp_header.bmp_bitfield.alpha_mask = f->get_32();
bmp_header.bmp_bitfield.red_mask_width = get_mask_width(bmp_header.bmp_bitfield.red_mask);
bmp_header.bmp_bitfield.green_mask_width = get_mask_width(bmp_header.bmp_bitfield.green_mask);
bmp_header.bmp_bitfield.blue_mask_width = get_mask_width(bmp_header.bmp_bitfield.blue_mask);
bmp_header.bmp_bitfield.alpha_mask_width = get_mask_width(bmp_header.bmp_bitfield.alpha_mask);
bmp_header.bmp_bitfield.alpha_offset = bmp_header.bmp_bitfield.red_mask_width + bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.red_offset = bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.green_offset = bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.red_max = (1 << bmp_header.bmp_bitfield.red_mask_width) - 1;
bmp_header.bmp_bitfield.green_max = (1 << bmp_header.bmp_bitfield.green_mask_width) - 1;
bmp_header.bmp_bitfield.blue_max = (1 << bmp_header.bmp_bitfield.blue_mask_width) - 1;
} break;
case BI_RLE8:
case BI_RLE4:
case BI_CMYKRLE8:
case BI_CMYKRLE4: {
// Stop parsing.
ERR_FAIL_V_MSG(ERR_UNAVAILABLE,
vformat("RLE compressed BMP files are not yet supported: %s", f->get_path()));
} break;
}
// Don't rely on sizeof(bmp_file_header) as structure padding
// adds 2 bytes offset leading to misaligned color table reading
uint32_t ct_offset = BITMAP_FILE_HEADER_SIZE + bmp_header.bmp_info_header.bmp_header_size;
f->seek(ct_offset);
uint32_t color_table_size = 0;
// bmp_colors_used may report 0 despite having a color table
// for 4 and 1 bit images, so don't rely on this information
if (bmp_header.bmp_info_header.bmp_bit_count <= 8) {
// Support 256 colors max
color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count;
ERR_FAIL_COND_V_MSG(color_table_size == 0, ERR_BUG,
vformat("Couldn't parse the BMP color table: %s", f->get_path()));
}
Vector<uint8_t> bmp_color_table;
// Color table is usually 4 bytes per color -> [B][G][R][0]
bmp_color_table.resize(color_table_size * 4);
uint8_t *bmp_color_table_w = bmp_color_table.ptrw();
f->get_buffer(bmp_color_table_w, color_table_size * 4);
f->seek(bmp_header.bmp_file_header.bmp_file_offset);
uint32_t bmp_buffer_size = (bmp_header.bmp_file_header.bmp_file_size - bmp_header.bmp_file_header.bmp_file_offset);
Vector<uint8_t> bmp_buffer;
err = bmp_buffer.resize(bmp_buffer_size);
if (err == OK) {
uint8_t *bmp_buffer_w = bmp_buffer.ptrw();
f->get_buffer(bmp_buffer_w, bmp_buffer_size);
const uint8_t *bmp_buffer_r = bmp_buffer.ptr();
const uint8_t *bmp_color_table_r = bmp_color_table.ptr();
err = convert_to_image(p_image, bmp_buffer_r,
bmp_color_table_r, color_table_size, bmp_header);
}
}
}
return err;
}
void ImageLoaderBMP::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("bmp");
}
static Ref<Image> _bmp_mem_loader_func(const uint8_t *p_bmp, int p_size) {
Ref<FileAccessMemory> memfile;
memfile.instantiate();
Error open_memfile_error = memfile->open_custom(p_bmp, p_size);
ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for BMP image buffer.");
Ref<Image> img;
img.instantiate();
Error load_error = ImageLoaderBMP().load_image(img, memfile, false, 1.0f);
ERR_FAIL_COND_V_MSG(load_error, Ref<Image>(), "Failed to load BMP image.");
return img;
}
ImageLoaderBMP::ImageLoaderBMP() {
Image::_bmp_mem_loader_func = _bmp_mem_loader_func;
}

View File

@@ -0,0 +1,107 @@
/**************************************************************************/
/* image_loader_bmp.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image_loader.h"
class ImageLoaderBMP : public ImageFormatLoader {
protected:
static const unsigned BITMAP_SIGNATURE = 0x4d42;
static const unsigned BITMAP_FILE_HEADER_SIZE = 14; // bmp_file_header_s
static const unsigned BITMAP_INFO_HEADER_MIN_SIZE = 40; // bmp_info_header_s
enum bmp_compression_s {
BI_RGB = 0x00,
BI_RLE8 = 0x01, // compressed
BI_RLE4 = 0x02, // compressed
BI_BITFIELDS = 0x03,
BI_JPEG = 0x04,
BI_PNG = 0x05,
BI_ALPHABITFIELDS = 0x06,
BI_CMYK = 0x0b,
BI_CMYKRLE8 = 0x0c, // compressed
BI_CMYKRLE4 = 0x0d // compressed
};
struct bmp_header_s {
struct bmp_file_header_s {
uint16_t bmp_signature = 0;
uint32_t bmp_file_size = 0;
uint32_t bmp_file_padding = 0;
uint32_t bmp_file_offset = 0;
} bmp_file_header;
struct bmp_info_header_s {
uint32_t bmp_header_size = 0;
uint32_t bmp_width = 0;
uint32_t bmp_height = 0;
uint16_t bmp_planes = 0;
uint16_t bmp_bit_count = 0;
uint32_t bmp_compression = 0;
uint32_t bmp_size_image = 0;
uint32_t bmp_pixels_per_meter_x = 0;
uint32_t bmp_pixels_per_meter_y = 0;
uint32_t bmp_colors_used = 0;
uint32_t bmp_important_colors = 0;
} bmp_info_header;
struct bmp_bitfield_s {
uint16_t alpha_mask = 0x8000;
uint16_t red_mask = 0x7C00;
uint16_t green_mask = 0x03E0;
uint16_t blue_mask = 0x001F;
uint16_t alpha_mask_width = 1u;
uint16_t red_mask_width = 5u;
uint16_t green_mask_width = 5u;
uint16_t blue_mask_width = 5u;
uint8_t alpha_offset = 15u; // Used for bit shifting.
uint8_t red_offset = 10u; // Used for bit shifting.
uint8_t green_offset = 5u; // Used for bit shifting.
//uint8_t blue_offset = 0u; // Always LSB aligned no shifting needed.
//uint8_t alpha_max = 1u; // Always boolean or on, so no scaling needed.
uint8_t red_max = 32u; // Used for color space scaling.
uint8_t green_max = 32u; // Used for color space scaling.
uint8_t blue_max = 32u; // Used for color space scaling.
} bmp_bitfield;
};
static Error convert_to_image(Ref<Image> p_image,
const uint8_t *p_buffer,
const uint8_t *p_color_buffer,
const uint32_t color_table_size,
const bmp_header_s &p_header);
public:
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
ImageLoaderBMP();
};

View File

@@ -0,0 +1,53 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_loader_bmp.h"
static Ref<ImageLoaderBMP> image_loader_bmp;
void initialize_bmp_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
image_loader_bmp.instantiate();
ImageLoader::add_image_format_loader(image_loader_bmp);
}
void uninitialize_bmp_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
ImageLoader::remove_image_format_loader(image_loader_bmp);
image_loader_bmp.unref();
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_bmp_module(ModuleInitializationLevel p_level);
void uninitialize_bmp_module(ModuleInitializationLevel p_level);

25
modules/camera/SCsub Normal file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_camera = env_modules.Clone()
if env["platform"] in ["windows", "macos", "linuxbsd", "android"]:
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
if env["platform"] == "windows":
env_camera.add_source_files(env.modules_sources, "camera_win.cpp")
elif env["platform"] == "macos":
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")
elif env["platform"] == "android":
env_camera.add_source_files(env.modules_sources, "camera_android.cpp")
env.Append(LIBS=["camera2ndk", "mediandk"])
elif env["platform"] == "linuxbsd":
env_camera.add_source_files(env.modules_sources, "camera_linux.cpp")
env_camera.add_source_files(env.modules_sources, "camera_feed_linux.cpp")
env_camera.add_source_files(env.modules_sources, "buffer_decoder.cpp")

View File

@@ -0,0 +1,212 @@
/**************************************************************************/
/* buffer_decoder.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "buffer_decoder.h"
#include "servers/camera/camera_feed.h"
#include <linux/videodev2.h>
BufferDecoder::BufferDecoder(CameraFeed *p_camera_feed) {
camera_feed = p_camera_feed;
width = camera_feed->get_format().width;
height = camera_feed->get_format().height;
image.instantiate();
}
AbstractYuyvBufferDecoder::AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed) :
BufferDecoder(p_camera_feed) {
switch (camera_feed->get_format().pixel_format) {
case V4L2_PIX_FMT_YYUV:
component_indexes = new int[4]{ 0, 1, 2, 3 };
break;
case V4L2_PIX_FMT_YVYU:
component_indexes = new int[4]{ 0, 2, 3, 1 };
break;
case V4L2_PIX_FMT_UYVY:
component_indexes = new int[4]{ 1, 3, 0, 2 };
break;
case V4L2_PIX_FMT_VYUY:
component_indexes = new int[4]{ 1, 3, 2, 0 };
break;
default:
component_indexes = new int[4]{ 0, 2, 1, 3 };
}
}
AbstractYuyvBufferDecoder::~AbstractYuyvBufferDecoder() {
delete[] component_indexes;
}
SeparateYuyvBufferDecoder::SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
y_image_data.resize(width * height);
cbcr_image_data.resize(width * height);
y_image.instantiate();
cbcr_image.instantiate();
}
void SeparateYuyvBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *y_dst = (uint8_t *)y_image_data.ptrw();
uint8_t *uv_dst = (uint8_t *)cbcr_image_data.ptrw();
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];
uint8_t *u_src = src + component_indexes[2];
uint8_t *v_src = src + component_indexes[3];
for (int i = 0; i < width * height; i += 2) {
*y_dst++ = *y0_src;
*y_dst++ = *y1_src;
*uv_dst++ = *u_src;
*uv_dst++ = *v_src;
y0_src += 4;
y1_src += 4;
u_src += 4;
v_src += 4;
}
if (y_image.is_valid()) {
y_image->set_data(width, height, false, Image::FORMAT_L8, y_image_data);
} else {
y_image.instantiate(width, height, false, Image::FORMAT_RGB8, y_image_data);
}
if (cbcr_image.is_valid()) {
cbcr_image->set_data(width, height, false, Image::FORMAT_L8, cbcr_image_data);
} else {
cbcr_image.instantiate(width, height, false, Image::FORMAT_RGB8, cbcr_image_data);
}
camera_feed->set_ycbcr_images(y_image, cbcr_image);
}
YuyvToGrayscaleBufferDecoder::YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
image_data.resize(width * height);
}
void YuyvToGrayscaleBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *dst = (uint8_t *)image_data.ptrw();
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];
for (int i = 0; i < width * height; i += 2) {
*dst++ = *y0_src;
*dst++ = *y1_src;
y0_src += 4;
y1_src += 4;
}
if (image.is_valid()) {
image->set_data(width, height, false, Image::FORMAT_L8, image_data);
} else {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}
camera_feed->set_rgb_image(image);
}
YuyvToRgbBufferDecoder::YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
image_data.resize(width * height * 3);
}
void YuyvToRgbBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];
uint8_t *u_src = src + component_indexes[2];
uint8_t *v_src = src + component_indexes[3];
uint8_t *dst = (uint8_t *)image_data.ptrw();
for (int i = 0; i < width * height; i += 2) {
int u = *u_src;
int v = *v_src;
int u1 = (((u - 128) << 7) + (u - 128)) >> 6;
int rg = (((u - 128) << 1) + (u - 128) + ((v - 128) << 2) + ((v - 128) << 1)) >> 3;
int v1 = (((v - 128) << 1) + (v - 128)) >> 1;
*dst++ = CLAMP(*y0_src + v1, 0, 255);
*dst++ = CLAMP(*y0_src - rg, 0, 255);
*dst++ = CLAMP(*y0_src + u1, 0, 255);
*dst++ = CLAMP(*y1_src + v1, 0, 255);
*dst++ = CLAMP(*y1_src - rg, 0, 255);
*dst++ = CLAMP(*y1_src + u1, 0, 255);
y0_src += 4;
y1_src += 4;
u_src += 4;
v_src += 4;
}
if (image.is_valid()) {
image->set_data(width, height, false, Image::FORMAT_RGB8, image_data);
} else {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}
camera_feed->set_rgb_image(image);
}
CopyBufferDecoder::CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba) :
BufferDecoder(p_camera_feed) {
rgba = p_rgba;
image_data.resize(width * height * (rgba ? 4 : 2));
}
void CopyBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *dst = (uint8_t *)image_data.ptrw();
memcpy(dst, p_buffer.start, p_buffer.length);
if (image.is_valid()) {
image->set_data(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
} else {
image.instantiate(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
}
camera_feed->set_rgb_image(image);
}
JpegBufferDecoder::JpegBufferDecoder(CameraFeed *p_camera_feed) :
BufferDecoder(p_camera_feed) {
}
void JpegBufferDecoder::decode(StreamingBuffer p_buffer) {
image_data.resize(p_buffer.length);
uint8_t *dst = (uint8_t *)image_data.ptrw();
memcpy(dst, p_buffer.start, p_buffer.length);
if (image->load_jpg_from_buffer(image_data) == OK) {
camera_feed->set_rgb_image(image);
}
}

View File

@@ -0,0 +1,113 @@
/**************************************************************************/
/* buffer_decoder.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#include "core/templates/vector.h"
class CameraFeed;
struct StreamingBuffer {
void *start = nullptr;
size_t length = 0;
};
class BufferDecoder {
protected:
CameraFeed *camera_feed = nullptr;
Ref<Image> image;
int width = 0;
int height = 0;
public:
virtual void decode(StreamingBuffer p_buffer) = 0;
BufferDecoder(CameraFeed *p_camera_feed);
virtual ~BufferDecoder() {}
};
class AbstractYuyvBufferDecoder : public BufferDecoder {
protected:
int *component_indexes = nullptr;
public:
AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed);
~AbstractYuyvBufferDecoder();
};
class SeparateYuyvBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> y_image_data;
Vector<uint8_t> cbcr_image_data;
Ref<Image> y_image;
Ref<Image> cbcr_image;
public:
SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};
class YuyvToGrayscaleBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> image_data;
public:
YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};
class YuyvToRgbBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> image_data;
public:
YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};
class CopyBufferDecoder : public BufferDecoder {
private:
Vector<uint8_t> image_data;
bool rgba = false;
public:
CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba);
virtual void decode(StreamingBuffer p_buffer) override;
};
class JpegBufferDecoder : public BufferDecoder {
private:
Vector<uint8_t> image_data;
public:
JpegBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};

View File

@@ -0,0 +1,510 @@
/**************************************************************************/
/* camera_android.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "camera_android.h"
#include "core/os/os.h"
#include "platform/android/display_server_android.h"
//////////////////////////////////////////////////////////////////////////
// Helper functions
//
// The following code enables you to view the contents of a media type while
// debugging.
#ifndef IF_EQUAL_RETURN
#define MAKE_FORMAT_CONST(suffix) AIMAGE_FORMAT_##suffix
#define IF_EQUAL_RETURN(param, val) \
if (MAKE_FORMAT_CONST(val) == param) \
return #val
#endif
String GetFormatName(const int32_t &format) {
IF_EQUAL_RETURN(format, YUV_420_888);
IF_EQUAL_RETURN(format, RGB_888);
IF_EQUAL_RETURN(format, RGBA_8888);
return "Unsupported";
}
//////////////////////////////////////////////////////////////////////////
// CameraFeedAndroid - Subclass for our camera feed on Android
CameraFeedAndroid::CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
CameraFeed::FeedPosition position, int32_t orientation) :
CameraFeed() {
this->manager = manager;
this->metadata = metadata;
this->orientation = orientation;
_add_formats();
camera_id = id;
set_position(position);
// Position
switch (position) {
case CameraFeed::FEED_BACK:
name = vformat("%s | BACK", id);
break;
case CameraFeed::FEED_FRONT:
name = vformat("%s | FRONT", id);
break;
default:
name = vformat("%s", id);
break;
}
image_y.instantiate();
image_uv.instantiate();
}
CameraFeedAndroid::~CameraFeedAndroid() {
if (is_active()) {
deactivate_feed();
}
if (metadata != nullptr) {
ACameraMetadata_free(metadata);
}
}
void CameraFeedAndroid::_set_rotation() {
int display_rotation = DisplayServerAndroid::get_singleton()->get_display_rotation();
// reverse rotation
switch (display_rotation) {
case 90:
display_rotation = 270;
break;
case 270:
display_rotation = 90;
break;
default:
break;
}
int sign = position == CameraFeed::FEED_FRONT ? 1 : -1;
float imageRotation = (orientation - display_rotation * sign + 360) % 360;
transform.set_rotation(real_t(Math::deg_to_rad(imageRotation)));
}
void CameraFeedAndroid::_add_formats() {
// Get supported formats
ACameraMetadata_const_entry formats;
camera_status_t status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &formats);
if (status == ACAMERA_OK) {
for (uint32_t f = 0; f < formats.count; f += 4) {
// Only support output streams
int32_t input = formats.data.i32[f + 3];
if (input) {
continue;
}
// Get format and resolution
int32_t format = formats.data.i32[f + 0];
if (format == AIMAGE_FORMAT_YUV_420_888 ||
format == AIMAGE_FORMAT_RGBA_8888 ||
format == AIMAGE_FORMAT_RGB_888) {
CameraFeed::FeedFormat feed_format;
feed_format.width = formats.data.i32[f + 1];
feed_format.height = formats.data.i32[f + 2];
feed_format.format = GetFormatName(format);
feed_format.pixel_format = format;
this->formats.append(feed_format);
}
}
}
}
bool CameraFeedAndroid::activate_feed() {
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
if (is_active()) {
deactivate_feed();
};
// Request permission
if (!OS::get_singleton()->request_permission("CAMERA")) {
return false;
}
// Open device
static ACameraDevice_stateCallbacks deviceCallbacks = {
.context = this,
.onDisconnected = onDisconnected,
.onError = onError,
};
camera_status_t c_status = ACameraManager_openCamera(manager, camera_id.utf8().get_data(), &deviceCallbacks, &device);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
// Create image reader
const FeedFormat &feed_format = formats[selected_format];
media_status_t m_status = AImageReader_new(feed_format.width, feed_format.height, feed_format.pixel_format, 1, &reader);
if (m_status != AMEDIA_OK) {
onError(this, device, m_status);
return false;
}
// Get image listener
static AImageReader_ImageListener listener{
.context = this,
.onImageAvailable = onImage,
};
m_status = AImageReader_setImageListener(reader, &listener);
if (m_status != AMEDIA_OK) {
onError(this, device, m_status);
return false;
}
// Get image surface
ANativeWindow *surface;
m_status = AImageReader_getWindow(reader, &surface);
if (m_status != AMEDIA_OK) {
onError(this, device, m_status);
return false;
}
// Prepare session outputs
ACaptureSessionOutput *output = nullptr;
c_status = ACaptureSessionOutput_create(surface, &output);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
ACaptureSessionOutputContainer *outputs = nullptr;
c_status = ACaptureSessionOutputContainer_create(&outputs);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
c_status = ACaptureSessionOutputContainer_add(outputs, output);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
// Create capture session
static ACameraCaptureSession_stateCallbacks sessionStateCallbacks{
.context = this,
.onClosed = onSessionClosed,
.onReady = onSessionReady,
.onActive = onSessionActive
};
c_status = ACameraDevice_createCaptureSession(device, outputs, &sessionStateCallbacks, &session);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
// Create capture request
c_status = ACameraDevice_createCaptureRequest(device, TEMPLATE_PREVIEW, &request);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
// Set capture target
ACameraOutputTarget *imageTarget = nullptr;
c_status = ACameraOutputTarget_create(surface, &imageTarget);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
c_status = ACaptureRequest_addTarget(request, imageTarget);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
// Start capture
c_status = ACameraCaptureSession_setRepeatingRequest(session, nullptr, 1, &request, nullptr);
if (c_status != ACAMERA_OK) {
onError(this, device, c_status);
return false;
}
return true;
}
bool CameraFeedAndroid::set_format(int p_index, const Dictionary &p_parameters) {
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
selected_format = p_index;
return true;
}
Array CameraFeedAndroid::get_formats() const {
Array result;
for (const FeedFormat &feed_format : formats) {
Dictionary dictionary;
dictionary["width"] = feed_format.width;
dictionary["height"] = feed_format.height;
dictionary["format"] = feed_format.format;
result.push_back(dictionary);
}
return result;
}
CameraFeed::FeedFormat CameraFeedAndroid::get_format() const {
CameraFeed::FeedFormat feed_format = {};
return selected_format == -1 ? feed_format : formats[selected_format];
}
void CameraFeedAndroid::onImage(void *context, AImageReader *p_reader) {
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
Vector<uint8_t> data_y = feed->data_y;
Vector<uint8_t> data_uv = feed->data_uv;
Ref<Image> image_y = feed->image_y;
Ref<Image> image_uv = feed->image_uv;
// Get image
AImage *image = nullptr;
media_status_t status = AImageReader_acquireNextImage(p_reader, &image);
ERR_FAIL_COND(status != AMEDIA_OK);
// Get image data
uint8_t *data = nullptr;
int len = 0;
int32_t pixel_stride, row_stride;
FeedFormat format = feed->get_format();
int width = format.width;
int height = format.height;
switch (format.pixel_format) {
case AIMAGE_FORMAT_YUV_420_888:
AImage_getPlaneData(image, 0, &data, &len);
if (len <= 0) {
return;
}
if (len != data_y.size()) {
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_R8, false);
data_y.resize(len > size ? len : size);
}
memcpy(data_y.ptrw(), data, len);
AImage_getPlanePixelStride(image, 1, &pixel_stride);
AImage_getPlaneRowStride(image, 1, &row_stride);
AImage_getPlaneData(image, 1, &data, &len);
if (len <= 0) {
return;
}
if (len != data_uv.size()) {
int64_t size = Image::get_image_data_size(width / 2, height / 2, Image::FORMAT_RG8, false);
data_uv.resize(len > size ? len : size);
}
memcpy(data_uv.ptrw(), data, len);
image_y->initialize_data(width, height, false, Image::FORMAT_R8, data_y);
image_uv->initialize_data(width / 2, height / 2, false, Image::FORMAT_RG8, data_uv);
feed->set_ycbcr_images(image_y, image_uv);
break;
case AIMAGE_FORMAT_RGBA_8888:
AImage_getPlaneData(image, 0, &data, &len);
if (len <= 0) {
return;
}
if (len != data_y.size()) {
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGBA8, false);
data_y.resize(len > size ? len : size);
}
memcpy(data_y.ptrw(), data, len);
image_y->initialize_data(width, height, false, Image::FORMAT_RGBA8, data_y);
feed->set_rgb_image(image_y);
break;
case AIMAGE_FORMAT_RGB_888:
AImage_getPlaneData(image, 0, &data, &len);
if (len <= 0) {
return;
}
if (len != data_y.size()) {
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGB8, false);
data_y.resize(len > size ? len : size);
}
memcpy(data_y.ptrw(), data, len);
image_y->initialize_data(width, height, false, Image::FORMAT_RGB8, data_y);
feed->set_rgb_image(image_y);
break;
default:
return;
}
// Rotation
feed->_set_rotation();
// Release image
AImage_delete(image);
}
void CameraFeedAndroid::onSessionReady(void *context, ACameraCaptureSession *session) {
print_verbose("Capture session ready");
}
void CameraFeedAndroid::onSessionActive(void *context, ACameraCaptureSession *session) {
print_verbose("Capture session active");
}
void CameraFeedAndroid::onSessionClosed(void *context, ACameraCaptureSession *session) {
print_verbose("Capture session closed");
}
void CameraFeedAndroid::deactivate_feed() {
if (session != nullptr) {
ACameraCaptureSession_stopRepeating(session);
ACameraCaptureSession_close(session);
session = nullptr;
}
if (request != nullptr) {
ACaptureRequest_free(request);
request = nullptr;
}
if (reader != nullptr) {
AImageReader_delete(reader);
reader = nullptr;
}
if (device != nullptr) {
ACameraDevice_close(device);
device = nullptr;
}
}
void CameraFeedAndroid::onError(void *context, ACameraDevice *p_device, int error) {
print_error(vformat("Camera error: %d", error));
onDisconnected(context, p_device);
}
void CameraFeedAndroid::onDisconnected(void *context, ACameraDevice *p_device) {
print_verbose("Camera disconnected");
auto *feed = static_cast<CameraFeedAndroid *>(context);
feed->set_active(false);
}
//////////////////////////////////////////////////////////////////////////
// CameraAndroid - Subclass for our camera server on Android
void CameraAndroid::update_feeds() {
ACameraIdList *cameraIds = nullptr;
camera_status_t c_status = ACameraManager_getCameraIdList(cameraManager, &cameraIds);
ERR_FAIL_COND(c_status != ACAMERA_OK);
// remove existing devices
for (int i = feeds.size() - 1; i >= 0; i--) {
remove_feed(feeds[i]);
}
for (int c = 0; c < cameraIds->numCameras; ++c) {
const char *id = cameraIds->cameraIds[c];
ACameraMetadata *metadata = nullptr;
ACameraManager_getCameraCharacteristics(cameraManager, id, &metadata);
if (!metadata) {
continue;
}
// Get sensor orientation
ACameraMetadata_const_entry orientation;
c_status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientation);
int32_t cameraOrientation;
if (c_status == ACAMERA_OK) {
cameraOrientation = orientation.data.i32[0];
} else {
cameraOrientation = 0;
print_error(vformat("Unable to get sensor orientation: %s", id));
}
// Get position
ACameraMetadata_const_entry lensInfo;
CameraFeed::FeedPosition position = CameraFeed::FEED_UNSPECIFIED;
camera_status_t status;
status = ACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &lensInfo);
if (status != ACAMERA_OK) {
ACameraMetadata_free(metadata);
continue;
}
uint8_t lens_facing = static_cast<acamera_metadata_enum_android_lens_facing_t>(lensInfo.data.u8[0]);
if (lens_facing == ACAMERA_LENS_FACING_FRONT) {
position = CameraFeed::FEED_FRONT;
} else if (lens_facing == ACAMERA_LENS_FACING_BACK) {
position = CameraFeed::FEED_BACK;
} else {
ACameraMetadata_free(metadata);
continue;
}
Ref<CameraFeedAndroid> feed = memnew(CameraFeedAndroid(cameraManager, metadata, id, position, cameraOrientation));
add_feed(feed);
}
ACameraManager_deleteCameraIdList(cameraIds);
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
}
void CameraAndroid::remove_all_feeds() {
// remove existing devices
for (int i = feeds.size() - 1; i >= 0; i--) {
remove_feed(feeds[i]);
}
if (cameraManager != nullptr) {
ACameraManager_delete(cameraManager);
cameraManager = nullptr;
}
}
void CameraAndroid::set_monitoring_feeds(bool p_monitoring_feeds) {
if (p_monitoring_feeds == monitoring_feeds) {
return;
}
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
if (p_monitoring_feeds) {
if (cameraManager == nullptr) {
cameraManager = ACameraManager_create();
}
// Update feeds
update_feeds();
} else {
remove_all_feeds();
}
}
CameraAndroid::~CameraAndroid() {
remove_all_feeds();
}

View File

@@ -0,0 +1,96 @@
/**************************************************************************/
/* camera_android.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/camera/camera_feed.h"
#include "servers/camera_server.h"
#include <camera/NdkCameraDevice.h>
#include <camera/NdkCameraError.h>
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraMetadataTags.h>
#include <media/NdkImageReader.h>
class CameraFeedAndroid : public CameraFeed {
GDSOFTCLASS(CameraFeedAndroid, CameraFeed);
private:
String camera_id;
int32_t orientation;
Ref<Image> image_y;
Ref<Image> image_uv;
Vector<uint8_t> data_y;
Vector<uint8_t> data_uv;
ACameraManager *manager = nullptr;
ACameraMetadata *metadata = nullptr;
ACameraDevice *device = nullptr;
AImageReader *reader = nullptr;
ACameraCaptureSession *session = nullptr;
ACaptureRequest *request = nullptr;
void _add_formats();
void _set_rotation();
static void onError(void *context, ACameraDevice *p_device, int error);
static void onDisconnected(void *context, ACameraDevice *p_device);
static void onImage(void *context, AImageReader *p_reader);
static void onSessionReady(void *context, ACameraCaptureSession *session);
static void onSessionActive(void *context, ACameraCaptureSession *session);
static void onSessionClosed(void *context, ACameraCaptureSession *session);
protected:
public:
bool activate_feed() override;
void deactivate_feed() override;
bool set_format(int p_index, const Dictionary &p_parameters) override;
Array get_formats() const override;
FeedFormat get_format() const override;
CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
CameraFeed::FeedPosition position, int32_t orientation);
~CameraFeedAndroid() override;
};
class CameraAndroid : public CameraServer {
GDSOFTCLASS(CameraAndroid, CameraServer);
private:
ACameraManager *cameraManager = nullptr;
void update_feeds();
void remove_all_feeds();
public:
void set_monitoring_feeds(bool p_monitoring_feeds) override;
~CameraAndroid();
};

View File

@@ -0,0 +1,363 @@
/**************************************************************************/
/* camera_feed_linux.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "camera_feed_linux.h"
#include "servers/rendering_server.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
void CameraFeedLinux::update_buffer_thread_func(void *p_func) {
if (p_func) {
CameraFeedLinux *camera_feed_linux = (CameraFeedLinux *)p_func;
camera_feed_linux->_update_buffer();
}
}
void CameraFeedLinux::_update_buffer() {
while (!exit_flag.is_set()) {
_read_frame();
usleep(10000);
}
}
void CameraFeedLinux::_query_device(const String &p_device_name) {
file_descriptor = open(p_device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
ERR_FAIL_COND_MSG(file_descriptor == -1, vformat("Cannot open file descriptor for %s. Error: %d.", p_device_name, errno));
struct v4l2_capability capability;
if (ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
ERR_FAIL_MSG(vformat("Cannot query device. Error: %d.", errno));
}
name = String((char *)capability.card);
for (int index = 0;; index++) {
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = index;
if (ioctl(file_descriptor, VIDIOC_ENUM_FMT, &fmtdesc) == -1) {
break;
}
for (int res_index = 0;; res_index++) {
struct v4l2_frmsizeenum frmsizeenum;
memset(&frmsizeenum, 0, sizeof(frmsizeenum));
frmsizeenum.pixel_format = fmtdesc.pixelformat;
frmsizeenum.index = res_index;
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) {
break;
}
for (int framerate_index = 0;; framerate_index++) {
struct v4l2_frmivalenum frmivalenum;
memset(&frmivalenum, 0, sizeof(frmivalenum));
frmivalenum.pixel_format = fmtdesc.pixelformat;
frmivalenum.width = frmsizeenum.discrete.width;
frmivalenum.height = frmsizeenum.discrete.height;
frmivalenum.index = framerate_index;
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == -1) {
if (framerate_index == 0) {
_add_format(fmtdesc, frmsizeenum.discrete, -1, 1);
}
break;
}
_add_format(fmtdesc, frmsizeenum.discrete, frmivalenum.discrete.numerator, frmivalenum.discrete.denominator);
}
}
}
close(file_descriptor);
}
void CameraFeedLinux::_add_format(v4l2_fmtdesc p_description, v4l2_frmsize_discrete p_size, int p_frame_numerator, int p_frame_denominator) {
FeedFormat feed_format;
feed_format.width = p_size.width;
feed_format.height = p_size.height;
feed_format.format = String((char *)p_description.description);
feed_format.frame_numerator = p_frame_numerator;
feed_format.frame_denominator = p_frame_denominator;
feed_format.pixel_format = p_description.pixelformat;
print_verbose(vformat("%s %dx%d@%d/%dfps", (char *)p_description.description, p_size.width, p_size.height, p_frame_denominator, p_frame_numerator));
formats.push_back(feed_format);
}
bool CameraFeedLinux::_request_buffers() {
struct v4l2_requestbuffers requestbuffers;
memset(&requestbuffers, 0, sizeof(requestbuffers));
requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
requestbuffers.memory = V4L2_MEMORY_MMAP;
requestbuffers.count = 4;
if (ioctl(file_descriptor, VIDIOC_REQBUFS, &requestbuffers) == -1) {
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_REQBUFS) error: %d.", errno));
}
ERR_FAIL_COND_V_MSG(requestbuffers.count < 2, false, "Not enough buffers granted.");
buffer_count = requestbuffers.count;
buffers = new StreamingBuffer[buffer_count];
for (unsigned int i = 0; i < buffer_count; i++) {
struct v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = requestbuffers.type;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = i;
if (ioctl(file_descriptor, VIDIOC_QUERYBUF, &buffer) == -1) {
delete[] buffers;
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QUERYBUF) error: %d.", errno));
}
buffers[i].length = buffer.length;
buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
if (buffers[i].start == MAP_FAILED) {
for (unsigned int b = 0; b < i; b++) {
_unmap_buffers(i);
}
delete[] buffers;
ERR_FAIL_V_MSG(false, "Mapping buffers failed.");
}
}
return true;
}
bool CameraFeedLinux::_start_capturing() {
for (unsigned int i = 0; i < buffer_count; i++) {
struct v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = i;
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
}
}
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(file_descriptor, VIDIOC_STREAMON, &type) == -1) {
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_STREAMON) error: %d.", errno));
}
return true;
}
void CameraFeedLinux::_read_frame() {
struct v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(file_descriptor, VIDIOC_DQBUF, &buffer) == -1) {
if (errno != EAGAIN) {
print_error(vformat("ioctl(VIDIOC_DQBUF) error: %d.", errno));
exit_flag.set();
}
return;
}
buffer_decoder->decode(buffers[buffer.index]);
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
print_error(vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
}
}
void CameraFeedLinux::_stop_capturing() {
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(file_descriptor, VIDIOC_STREAMOFF, &type) == -1) {
print_error(vformat("ioctl(VIDIOC_STREAMOFF) error: %d.", errno));
}
}
void CameraFeedLinux::_unmap_buffers(unsigned int p_count) {
for (unsigned int i = 0; i < p_count; i++) {
munmap(buffers[i].start, buffers[i].length);
}
}
void CameraFeedLinux::_start_thread() {
exit_flag.clear();
thread = memnew(Thread);
thread->start(CameraFeedLinux::update_buffer_thread_func, this);
}
String CameraFeedLinux::get_device_name() const {
return device_name;
}
bool CameraFeedLinux::activate_feed() {
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
if (_request_buffers() && _start_capturing()) {
buffer_decoder = _create_buffer_decoder();
_start_thread();
return true;
}
ERR_FAIL_V_MSG(false, "Could not activate feed.");
}
BufferDecoder *CameraFeedLinux::_create_buffer_decoder() {
switch (formats[selected_format].pixel_format) {
case V4L2_PIX_FMT_MJPEG:
case V4L2_PIX_FMT_JPEG:
return memnew(JpegBufferDecoder(this));
case V4L2_PIX_FMT_YUYV:
case V4L2_PIX_FMT_YYUV:
case V4L2_PIX_FMT_YVYU:
case V4L2_PIX_FMT_UYVY:
case V4L2_PIX_FMT_VYUY: {
String output = parameters["output"];
if (output == "separate") {
return memnew(SeparateYuyvBufferDecoder(this));
}
if (output == "grayscale") {
return memnew(YuyvToGrayscaleBufferDecoder(this));
}
if (output == "copy") {
return memnew(CopyBufferDecoder(this, false));
}
return memnew(YuyvToRgbBufferDecoder(this));
}
default:
return memnew(CopyBufferDecoder(this, true));
}
}
void CameraFeedLinux::deactivate_feed() {
exit_flag.set();
thread->wait_to_finish();
memdelete(thread);
_stop_capturing();
_unmap_buffers(buffer_count);
delete[] buffers;
memdelete(buffer_decoder);
for (int i = 0; i < CameraServer::FEED_IMAGES; i++) {
RID placeholder = RenderingServer::get_singleton()->texture_2d_placeholder_create();
RenderingServer::get_singleton()->texture_replace(texture[i], placeholder);
}
base_width = 0;
base_height = 0;
close(file_descriptor);
emit_signal(SNAME("format_changed"));
}
Array CameraFeedLinux::get_formats() const {
Array result;
for (const FeedFormat &format : formats) {
Dictionary dictionary;
dictionary["width"] = format.width;
dictionary["height"] = format.height;
dictionary["format"] = format.format;
dictionary["frame_numerator"] = format.frame_numerator;
dictionary["frame_denominator"] = format.frame_denominator;
result.push_back(dictionary);
}
return result;
}
CameraFeed::FeedFormat CameraFeedLinux::get_format() const {
FeedFormat feed_format = {};
return selected_format == -1 ? feed_format : formats[selected_format];
}
bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
FeedFormat feed_format = formats[p_index];
file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = feed_format.width;
format.fmt.pix.height = feed_format.height;
format.fmt.pix.pixelformat = feed_format.pixel_format;
if (ioctl(file_descriptor, VIDIOC_S_FMT, &format) == -1) {
close(file_descriptor);
ERR_FAIL_V_MSG(false, vformat("Cannot set format, error: %d.", errno));
}
if (feed_format.frame_numerator > 0) {
struct v4l2_streamparm param;
memset(&param, 0, sizeof(param));
param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
param.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
param.parm.capture.timeperframe.numerator = feed_format.frame_numerator;
param.parm.capture.timeperframe.denominator = feed_format.frame_denominator;
if (ioctl(file_descriptor, VIDIOC_S_PARM, &param) == -1) {
close(file_descriptor);
ERR_FAIL_V_MSG(false, vformat("Cannot set framerate, error: %d.", errno));
}
}
close(file_descriptor);
parameters = p_parameters.duplicate();
selected_format = p_index;
emit_signal(SNAME("format_changed"));
return true;
}
CameraFeedLinux::CameraFeedLinux(const String &p_device_name) :
CameraFeed() {
device_name = p_device_name;
_query_device(device_name);
}
CameraFeedLinux::~CameraFeedLinux() {
if (is_active()) {
deactivate_feed();
}
}

View File

@@ -0,0 +1,77 @@
/**************************************************************************/
/* camera_feed_linux.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "buffer_decoder.h"
#include "core/os/thread.h"
#include "servers/camera/camera_feed.h"
#include <linux/videodev2.h>
struct StreamingBuffer;
class CameraFeedLinux : public CameraFeed {
GDSOFTCLASS(CameraFeedLinux, CameraFeed);
private:
SafeFlag exit_flag;
Thread *thread = nullptr;
String device_name;
int file_descriptor = -1;
StreamingBuffer *buffers = nullptr;
unsigned int buffer_count = 0;
BufferDecoder *buffer_decoder = nullptr;
static void update_buffer_thread_func(void *p_func);
void _update_buffer();
void _query_device(const String &p_device_name);
void _add_format(v4l2_fmtdesc description, v4l2_frmsize_discrete size, int frame_numerator, int frame_denominator);
bool _request_buffers();
bool _start_capturing();
void _read_frame();
void _stop_capturing();
void _unmap_buffers(unsigned int p_count);
BufferDecoder *_create_buffer_decoder();
void _start_thread();
public:
String get_device_name() const;
bool activate_feed() override;
void deactivate_feed() override;
bool set_format(int p_index, const Dictionary &p_parameters) override;
Array get_formats() const override;
FeedFormat get_format() const override;
CameraFeedLinux(const String &p_device_name);
~CameraFeedLinux() override;
};

View File

@@ -0,0 +1,188 @@
/**************************************************************************/
/* camera_linux.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "camera_linux.h"
#include "camera_feed_linux.h"
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
void CameraLinux::camera_thread_func(void *p_camera_linux) {
if (p_camera_linux) {
CameraLinux *camera_linux = (CameraLinux *)p_camera_linux;
camera_linux->_update_devices();
}
}
void CameraLinux::_update_devices() {
while (!exit_flag.is_set()) {
{
MutexLock lock(camera_mutex);
for (int i = feeds.size() - 1; i >= 0; i--) {
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
if (feed.is_null()) {
continue;
}
String device_name = feed->get_device_name();
if (!_is_active(device_name)) {
remove_feed(feed);
}
}
struct dirent **devices;
int count = scandir("/dev", &devices, nullptr, alphasort);
if (count != -1) {
for (int i = 0; i < count; i++) {
struct dirent *device = devices[i];
if (strncmp(device->d_name, "video", 5) == 0) {
String device_name = String("/dev/") + String(device->d_name);
if (!_has_device(device_name)) {
_add_device(device_name);
}
}
free(device);
}
}
free(devices);
}
call_deferred("emit_signal", SNAME(CameraServer::feeds_updated_signal_name));
usleep(1000000);
}
}
bool CameraLinux::_has_device(const String &p_device_name) {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
if (feed.is_null()) {
continue;
}
if (feed->get_device_name() == p_device_name) {
return true;
}
}
return false;
}
void CameraLinux::_add_device(const String &p_device_name) {
int file_descriptor = _open_device(p_device_name);
if (file_descriptor != -1) {
if (_is_video_capture_device(file_descriptor)) {
Ref<CameraFeedLinux> feed = memnew(CameraFeedLinux(p_device_name));
add_feed(feed);
}
}
close(file_descriptor);
}
int CameraLinux::_open_device(const String &p_device_name) {
struct stat s;
if (stat(p_device_name.ascii().get_data(), &s) == -1) {
return -1;
}
if (!S_ISCHR(s.st_mode)) {
return -1;
}
return open(p_device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
}
// TODO any cheaper/cleaner way to check if file descriptor is invalid?
bool CameraLinux::_is_active(const String &p_device_name) {
struct v4l2_capability capability;
bool result = false;
int file_descriptor = _open_device(p_device_name);
if (file_descriptor != -1 && ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) != -1) {
result = true;
}
close(file_descriptor);
return result;
}
bool CameraLinux::_is_video_capture_device(int p_file_descriptor) {
struct v4l2_capability capability;
if (ioctl(p_file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
return false;
}
if (!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
return false;
}
if (!(capability.capabilities & V4L2_CAP_STREAMING)) {
return false;
}
return _can_query_format(p_file_descriptor, V4L2_BUF_TYPE_VIDEO_CAPTURE);
}
bool CameraLinux::_can_query_format(int p_file_descriptor, int p_type) {
struct v4l2_format format;
memset(&format, 0, sizeof(format));
format.type = p_type;
return ioctl(p_file_descriptor, VIDIOC_G_FMT, &format) != -1;
}
inline void CameraLinux::set_monitoring_feeds(bool p_monitoring_feeds) {
if (p_monitoring_feeds == monitoring_feeds) {
return;
}
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
if (p_monitoring_feeds) {
exit_flag.clear();
camera_thread.start(CameraLinux::camera_thread_func, this);
} else {
exit_flag.set();
if (camera_thread.is_started()) {
camera_thread.wait_to_finish();
}
}
}
CameraLinux::~CameraLinux() {
exit_flag.set();
if (camera_thread.is_started()) {
camera_thread.wait_to_finish();
}
}

View File

@@ -0,0 +1,59 @@
/**************************************************************************/
/* camera_linux.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "servers/camera_server.h"
class CameraLinux : public CameraServer {
private:
SafeFlag exit_flag;
Thread camera_thread;
Mutex camera_mutex;
static void camera_thread_func(void *p_camera_linux);
void _update_devices();
bool _has_device(const String &p_device_name);
void _add_device(const String &p_device_name);
void _remove_device(const String &p_device_name);
int _open_device(const String &p_device_name);
bool _is_active(const String &p_device_name);
bool _is_video_capture_device(int p_file_descriptor);
bool _can_query_format(int p_file_descriptor, int p_type);
public:
CameraLinux() = default;
~CameraLinux();
void set_monitoring_feeds(bool p_monitoring_feeds) override;
};

View File

@@ -0,0 +1,46 @@
/**************************************************************************/
/* camera_macos.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
// If you fix something here, make sure you fix it there as well!
#include "servers/camera_server.h"
class CameraMacOS : public CameraServer {
GDSOFTCLASS(CameraMacOS, CameraServer);
public:
CameraMacOS() = default;
void update_feeds();
void set_monitoring_feeds(bool p_monitoring_feeds) override;
};

View File

@@ -0,0 +1,385 @@
/**************************************************************************/
/* camera_macos.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
// If you fix something here, make sure you fix it there as well!
#import "camera_macos.h"
#include "servers/camera/camera_feed.h"
#import <AVFoundation/AVFoundation.h>
//////////////////////////////////////////////////////////////////////////
// MyCaptureSession - This is a little helper class so we can capture our frames
@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
Ref<CameraFeed> feed;
size_t width[2];
size_t height[2];
Vector<uint8_t> img_data[2];
AVCaptureDeviceInput *input;
AVCaptureVideoDataOutput *output;
}
@end
@implementation MyCaptureSession
- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
if (self = [super init]) {
NSError *error;
feed = p_feed;
width[0] = 0;
height[0] = 0;
width[1] = 0;
height[1] = 0;
[self beginConfiguration];
input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
if (!input) {
print_line("Couldn't get input device for camera");
} else {
[self addInput:input];
}
output = [AVCaptureVideoDataOutput new];
if (!output) {
print_line("Couldn't get output device for camera");
} else {
NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
output.videoSettings = settings;
// discard if the data output queue is blocked (as we process the still image)
[output setAlwaysDiscardsLateVideoFrames:YES];
// now set ourselves as the delegate to receive new frames.
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
// this takes ownership
[self addOutput:output];
}
[self commitConfiguration];
// kick off our session..
[self startRunning];
};
return self;
}
- (void)cleanup {
// stop running
[self stopRunning];
// cleanup
[self beginConfiguration];
// remove input
if (input) {
[self removeInput:input];
// don't release this
input = nullptr;
}
// free up our output
if (output) {
[self removeOutput:output];
[output setSampleBufferDelegate:nil queue:nullptr];
output = nullptr;
}
[self commitConfiguration];
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
// This gets called every time our camera has a new image for us to process.
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
// For now, version 1, we're just doing the bare minimum to make this work...
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// int _width = CVPixelBufferGetWidth(pixelBuffer);
// int _height = CVPixelBufferGetHeight(pixelBuffer);
// It says that we need to lock this on the documentation pages but it's not in the samples
// need to lock our base address so we can access our pixel buffers, better safe then sorry?
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
// get our buffers
unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
if (dataY == nullptr) {
print_line("Couldn't access Y pixel buffer data");
} else if (dataCbCr == nullptr) {
print_line("Couldn't access CbCr pixel buffer data");
} else {
Ref<Image> img[2];
{
// do Y
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
if ((width[0] != new_width) || (height[0] != new_height)) {
width[0] = new_width;
height[0] = new_height;
img_data[0].resize(new_width * new_height);
}
uint8_t *w = img_data[0].ptrw();
memcpy(w, dataY, new_width * new_height);
img[0].instantiate();
img[0]->set_data(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
}
{
// do CbCr
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
if ((width[1] != new_width) || (height[1] != new_height)) {
width[1] = new_width;
height[1] = new_height;
img_data[1].resize(2 * new_width * new_height);
}
uint8_t *w = img_data[1].ptrw();
memcpy(w, dataCbCr, 2 * new_width * new_height);
///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion
img[1].instantiate();
img[1]->set_data(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
}
// set our texture...
feed->set_ycbcr_images(img[0], img[1]);
}
// and unlock
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
}
@end
//////////////////////////////////////////////////////////////////////////
// CameraFeedMacOS - Subclass for camera feeds in macOS
class CameraFeedMacOS : public CameraFeed {
GDSOFTCLASS(CameraFeedMacOS, CameraFeed);
private:
AVCaptureDevice *device;
MyCaptureSession *capture_session;
public:
AVCaptureDevice *get_device() const;
CameraFeedMacOS();
void set_device(AVCaptureDevice *p_device);
bool activate_feed() override;
void deactivate_feed() override;
};
AVCaptureDevice *CameraFeedMacOS::get_device() const {
return device;
}
CameraFeedMacOS::CameraFeedMacOS() {
device = nullptr;
capture_session = nullptr;
}
void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
device = p_device;
// get some info
NSString *device_name = p_device.localizedName;
name = String::utf8(device_name.UTF8String);
position = CameraFeed::FEED_UNSPECIFIED;
if ([p_device position] == AVCaptureDevicePositionBack) {
position = CameraFeed::FEED_BACK;
} else if ([p_device position] == AVCaptureDevicePositionFront) {
position = CameraFeed::FEED_FRONT;
};
}
bool CameraFeedMacOS::activate_feed() {
if (capture_session) {
// Already recording!
} else {
// Start camera capture, check permission.
if (@available(macOS 10.14, *)) {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status == AVAuthorizationStatusAuthorized) {
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
} else if (status == AVAuthorizationStatusNotDetermined) {
// Request permission.
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
if (granted) {
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
}
}];
}
} else {
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
}
};
return true;
}
void CameraFeedMacOS::deactivate_feed() {
// end camera capture if we have one
if (capture_session) {
[capture_session cleanup];
capture_session = nullptr;
};
}
//////////////////////////////////////////////////////////////////////////
// MyDeviceNotifications - This is a little helper class gets notifications
// when devices are connected/disconnected
@interface MyDeviceNotifications : NSObject {
CameraMacOS *camera_server;
}
@end
@implementation MyDeviceNotifications
- (void)devices_changed:(NSNotification *)notification {
camera_server->update_feeds();
}
- (id)initForServer:(CameraMacOS *)p_server {
if (self = [super init]) {
camera_server = p_server;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
};
return self;
}
- (void)dealloc {
// remove notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
}
@end
MyDeviceNotifications *device_notifications = nil;
//////////////////////////////////////////////////////////////////////////
// CameraMacOS - Subclass for our camera server on macOS
void CameraMacOS::update_feeds() {
NSArray<AVCaptureDevice *> *devices = nullptr;
#if defined(__x86_64__)
if (@available(macOS 10.15, *)) {
#endif
AVCaptureDeviceDiscoverySession *session;
if (@available(macOS 14.0, *)) {
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeContinuityCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
} else {
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
}
devices = session.devices;
#if defined(__x86_64__)
} else {
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
}
#endif
// remove devices that are gone..
for (int i = feeds.size() - 1; i >= 0; i--) {
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
if (feed.is_null()) {
continue;
}
if (![devices containsObject:feed->get_device()]) {
// remove it from our array, this will also destroy it ;)
remove_feed(feed);
};
};
for (AVCaptureDevice *device in devices) {
bool found = false;
for (int i = 0; i < feeds.size() && !found; i++) {
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
if (feed.is_null()) {
continue;
}
if (feed->get_device() == device) {
found = true;
};
};
if (!found) {
Ref<CameraFeedMacOS> newfeed;
newfeed.instantiate();
newfeed->set_device(device);
// assume display camera so inverse
Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0);
newfeed->set_transform(transform);
add_feed(newfeed);
};
};
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
}
void CameraMacOS::set_monitoring_feeds(bool p_monitoring_feeds) {
if (p_monitoring_feeds == monitoring_feeds) {
return;
}
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
if (p_monitoring_feeds) {
// Find available cameras we have at this time.
update_feeds();
// Get notified on feed changes.
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
} else {
// Stop monitoring feed changes.
device_notifications = nil;
}
}

View File

@@ -0,0 +1,94 @@
/**************************************************************************/
/* camera_win.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "camera_win.h"
///@TODO sorry guys, I got about 80% through implementing this using DirectShow only
// to find out Microsoft deprecated half the API and its replacement is as confusing
// as they could make it. Joey suggested looking into libuvc which offers a more direct
// route to webcams over USB and this is very promising but it wouldn't compile on
// windows for me...I've gutted the classes I implemented DirectShow in just to have
// a skeleton for someone to work on, mail me for more details or if you want a copy....
//////////////////////////////////////////////////////////////////////////
// CameraFeedWindows - Subclass for our camera feed on windows
/// @TODO need to implement this
class CameraFeedWindows : public CameraFeed {
private:
protected:
public:
CameraFeedWindows();
virtual ~CameraFeedWindows();
bool activate_feed();
void deactivate_feed();
};
CameraFeedWindows::CameraFeedWindows() {
///@TODO implement this, should store information about our available camera
}
CameraFeedWindows::~CameraFeedWindows() {
// make sure we stop recording if we are!
if (is_active()) {
deactivate_feed();
};
///@TODO free up anything used by this
}
bool CameraFeedWindows::activate_feed() {
///@TODO this should activate our camera and start the process of capturing frames
return true;
}
///@TODO we should probably have a callback method here that is being called by the
// camera API which provides frames and call back into the CameraServer to update our texture
void CameraFeedWindows::deactivate_feed() {
///@TODO this should deactivate our camera and stop the process of capturing frames
}
//////////////////////////////////////////////////////////////////////////
// CameraWindows - Subclass for our camera server on windows
void CameraWindows::add_active_cameras() {
///@TODO scan through any active cameras and create CameraFeedWindows objects for them
}
CameraWindows::CameraWindows() {
// Find cameras active right now
add_active_cameras();
// need to add something that will react to devices being connected/removed...
}

View File

@@ -0,0 +1,43 @@
/**************************************************************************/
/* camera_win.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/camera/camera_feed.h"
#include "servers/camera_server.h"
class CameraWindows : public CameraServer {
private:
void add_active_cameras();
public:
CameraWindows();
~CameraWindows() {}
};

10
modules/camera/config.py Normal file
View File

@@ -0,0 +1,10 @@
def can_build(env, platform):
import sys
if sys.platform.startswith("freebsd") or sys.platform.startswith("openbsd"):
return False
return platform == "macos" or platform == "windows" or platform == "linuxbsd" or platform == "android"
def configure(env):
pass

View File

@@ -0,0 +1,69 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#if defined(LINUXBSD_ENABLED)
#include "camera_linux.h"
#endif
#if defined(WINDOWS_ENABLED)
#include "camera_win.h"
#endif
#if defined(MACOS_ENABLED)
#include "camera_macos.h"
#endif
#if defined(ANDROID_ENABLED)
#include "camera_android.h"
#endif
void initialize_camera_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
#if defined(LINUXBSD_ENABLED)
CameraServer::make_default<CameraLinux>();
#endif
#if defined(WINDOWS_ENABLED)
CameraServer::make_default<CameraWindows>();
#endif
#if defined(MACOS_ENABLED)
CameraServer::make_default<CameraMacOS>();
#endif
#if defined(ANDROID_ENABLED)
CameraServer::make_default<CameraAndroid>();
#endif
}
void uninitialize_camera_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_camera_module(ModuleInitializationLevel p_level);
void uninitialize_camera_module(ModuleInitializationLevel p_level);

54
modules/csg/SCsub Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_csg = env_modules.Clone()
env_csg.Append(CPPDEFINES=("MANIFOLD_PAR", "-1"))
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/manifold/"
thirdparty_sources = [
"src/boolean_result.cpp",
"src/boolean3.cpp",
"src/constructors.cpp",
"src/csg_tree.cpp",
"src/edge_op.cpp",
"src/face_op.cpp",
"src/impl.cpp",
"src/manifold.cpp",
"src/polygon.cpp",
"src/properties.cpp",
"src/quickhull.cpp",
"src/sdf.cpp",
"src/smoothing.cpp",
"src/sort.cpp",
"src/subdivision.cpp",
"src/tree2d.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_csg.Prepend(CPPEXTPATH=[thirdparty_dir + "include"])
env_thirdparty = env_csg.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_csg.add_source_files(module_obj, "*.cpp")
if env.editor_build:
env_csg.add_source_files(module_obj, "editor/*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

24
modules/csg/config.py Normal file
View File

@@ -0,0 +1,24 @@
def can_build(env, platform):
return not env["disable_3d"]
def configure(env):
pass
def get_doc_classes():
return [
"CSGBox3D",
"CSGCombiner3D",
"CSGCylinder3D",
"CSGMesh3D",
"CSGPolygon3D",
"CSGPrimitive3D",
"CSGShape3D",
"CSGSphere3D",
"CSGTorus3D",
]
def get_doc_path():
return "doc_classes"

117
modules/csg/csg.cpp Normal file
View File

@@ -0,0 +1,117 @@
/**************************************************************************/
/* csg.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "csg.h"
// CSGBrush
void CSGBrush::build_from_faces(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials, const Vector<bool> &p_flip_faces) {
faces.clear();
int vc = p_vertices.size();
ERR_FAIL_COND((vc % 3) != 0);
const Vector3 *rv = p_vertices.ptr();
int uvc = p_uvs.size();
const Vector2 *ruv = p_uvs.ptr();
int sc = p_smooth.size();
const bool *rs = p_smooth.ptr();
int mc = p_materials.size();
const Ref<Material> *rm = p_materials.ptr();
int ic = p_flip_faces.size();
const bool *ri = p_flip_faces.ptr();
HashMap<Ref<Material>, int> material_map;
faces.resize(p_vertices.size() / 3);
for (int i = 0; i < faces.size(); i++) {
Face &f = faces.write[i];
f.vertices[0] = rv[i * 3 + 0];
f.vertices[1] = rv[i * 3 + 1];
f.vertices[2] = rv[i * 3 + 2];
if (uvc == vc) {
f.uvs[0] = ruv[i * 3 + 0];
f.uvs[1] = ruv[i * 3 + 1];
f.uvs[2] = ruv[i * 3 + 2];
}
if (sc == vc / 3) {
f.smooth = rs[i];
} else {
f.smooth = false;
}
if (ic == vc / 3) {
f.invert = ri[i];
} else {
f.invert = false;
}
if (mc == vc / 3) {
Ref<Material> mat = rm[i];
if (mat.is_valid()) {
HashMap<Ref<Material>, int>::ConstIterator E = material_map.find(mat);
if (E) {
f.material = E->value;
} else {
f.material = material_map.size();
material_map[mat] = f.material;
}
} else {
f.material = -1;
}
}
}
materials.resize(material_map.size());
for (const KeyValue<Ref<Material>, int> &E : material_map) {
materials.write[E.value] = E.key;
}
_regen_face_aabbs();
}
void CSGBrush::copy_from(const CSGBrush &p_brush, const Transform3D &p_xform) {
faces = p_brush.faces;
materials = p_brush.materials;
for (int i = 0; i < faces.size(); i++) {
for (int j = 0; j < 3; j++) {
faces.write[i].vertices[j] = p_xform.xform(p_brush.faces[i].vertices[j]);
}
}
_regen_face_aabbs();
}

66
modules/csg/csg.h Normal file
View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* csg.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/math/aabb.h"
#include "core/math/transform_3d.h"
#include "core/math/vector2.h"
#include "core/math/vector3.h"
#include "core/object/ref_counted.h"
#include "core/templates/vector.h"
#include "scene/resources/material.h"
struct CSGBrush {
struct Face {
Vector3 vertices[3];
Vector2 uvs[3];
AABB aabb;
bool smooth = false;
bool invert = false;
int material = 0;
};
Vector<Face> faces;
Vector<Ref<Material>> materials;
inline void _regen_face_aabbs() {
for (int i = 0; i < faces.size(); i++) {
faces.write[i].aabb = AABB();
faces.write[i].aabb.position = faces[i].vertices[0];
faces.write[i].aabb.expand_to(faces[i].vertices[1]);
faces.write[i].aabb.expand_to(faces[i].vertices[2]);
}
}
// Create a brush from faces.
void build_from_faces(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uvs, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials, const Vector<bool> &p_invert_faces);
void copy_from(const CSGBrush &p_brush, const Transform3D &p_xform);
};

2829
modules/csg/csg_shape.cpp Normal file

File diff suppressed because it is too large Load Diff

494
modules/csg/csg_shape.h Normal file
View File

@@ -0,0 +1,494 @@
/**************************************************************************/
/* csg_shape.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "csg.h"
#include "scene/3d/path_3d.h"
#include "scene/3d/visual_instance_3d.h"
#ifndef PHYSICS_3D_DISABLED
#include "scene/resources/3d/concave_polygon_shape_3d.h"
#endif // PHYSICS_3D_DISABLED
#include "thirdparty/misc/mikktspace.h"
class NavigationMesh;
class NavigationMeshSourceGeometryData3D;
class CSGShape3D : public GeometryInstance3D {
GDCLASS(CSGShape3D, GeometryInstance3D);
public:
enum Operation {
OPERATION_UNION,
OPERATION_INTERSECTION,
OPERATION_SUBTRACTION,
};
private:
Operation operation = OPERATION_UNION;
CSGShape3D *parent_shape = nullptr;
CSGBrush *brush = nullptr;
AABB node_aabb;
bool dirty = false;
bool last_visible = false;
float snap = 0.001;
#ifndef PHYSICS_3D_DISABLED
bool use_collision = false;
uint32_t collision_layer = 1;
uint32_t collision_mask = 1;
real_t collision_priority = 1.0;
Ref<ConcavePolygonShape3D> root_collision_shape;
RID root_collision_instance;
RID root_collision_debug_instance;
Transform3D debug_shape_old_transform;
#endif // PHYSICS_3D_DISABLED
bool calculate_tangents = true;
Ref<ArrayMesh> root_mesh;
struct Vector3Hasher {
_ALWAYS_INLINE_ uint32_t hash(const Vector3 &p_vec3) const {
uint32_t h = hash_murmur3_one_float(p_vec3.x);
h = hash_murmur3_one_float(p_vec3.y, h);
h = hash_murmur3_one_float(p_vec3.z, h);
return h;
}
};
struct ShapeUpdateSurface {
Vector<Vector3> vertices;
Vector<Vector3> normals;
Vector<Vector2> uvs;
Vector<real_t> tans;
Ref<Material> material;
int last_added = 0;
Vector3 *verticesw = nullptr;
Vector3 *normalsw = nullptr;
Vector2 *uvsw = nullptr;
real_t *tansw = nullptr;
};
//mikktspace callbacks
static int mikktGetNumFaces(const SMikkTSpaceContext *pContext);
static int mikktGetNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace);
static void mikktGetPosition(const SMikkTSpaceContext *pContext, float fvPosOut[], const int iFace, const int iVert);
static void mikktGetNormal(const SMikkTSpaceContext *pContext, float fvNormOut[], const int iFace, const int iVert);
static void mikktGetTexCoord(const SMikkTSpaceContext *pContext, float fvTexcOut[], const int iFace, const int iVert);
static void mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
const tbool bIsOrientationPreserving, const int iFace, const int iVert);
#ifndef PHYSICS_3D_DISABLED
void _update_collision_faces();
bool _is_debug_collision_shape_visible();
void _update_debug_collision_shape();
void _clear_debug_collision_shape();
void _on_transform_changed();
Vector<Vector3> _get_brush_collision_faces();
#endif // PHYSICS_3D_DISABLED
protected:
void _notification(int p_what);
virtual CSGBrush *_build_brush() = 0;
void _make_dirty(bool p_parent_removing = false);
PackedStringArray get_configuration_warnings() const override;
static void _bind_methods();
friend class CSGCombiner3D;
CSGBrush *_get_brush();
void _validate_property(PropertyInfo &p_property) const;
public:
Array get_meshes() const;
void update_shape();
void set_operation(Operation p_operation);
Operation get_operation() const;
virtual Vector<Vector3> get_brush_faces();
virtual AABB get_aabb() const override;
void set_use_collision(bool p_enable);
bool is_using_collision() const;
void set_collision_layer(uint32_t p_layer);
uint32_t get_collision_layer() const;
void set_collision_mask(uint32_t p_mask);
uint32_t get_collision_mask() const;
void set_collision_layer_value(int p_layer_number, bool p_value);
bool get_collision_layer_value(int p_layer_number) const;
void set_collision_mask_value(int p_layer_number, bool p_value);
bool get_collision_mask_value(int p_layer_number) const;
RID _get_root_collision_instance() const;
void set_collision_priority(real_t p_priority);
real_t get_collision_priority() const;
#ifndef DISABLE_DEPRECATED
void set_snap(float p_snap);
float get_snap() const;
#endif // DISABLE_DEPRECATED
void set_calculate_tangents(bool p_calculate_tangents);
bool is_calculating_tangents() const;
bool is_root_shape() const;
Ref<ArrayMesh> bake_static_mesh();
#ifndef PHYSICS_3D_DISABLED
Ref<ConcavePolygonShape3D> bake_collision_shape();
#endif // PHYSICS_3D_DISABLED
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
#ifndef NAVIGATION_3D_DISABLED
private:
static Callable _navmesh_source_geometry_parsing_callback;
static RID _navmesh_source_geometry_parser;
public:
static void navmesh_parse_init();
static void navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node);
#endif // NAVIGATION_3D_DISABLED
CSGShape3D();
~CSGShape3D();
};
VARIANT_ENUM_CAST(CSGShape3D::Operation)
class CSGCombiner3D : public CSGShape3D {
GDCLASS(CSGCombiner3D, CSGShape3D);
private:
virtual CSGBrush *_build_brush() override;
public:
CSGCombiner3D();
};
class CSGPrimitive3D : public CSGShape3D {
GDCLASS(CSGPrimitive3D, CSGShape3D);
protected:
bool flip_faces;
CSGBrush *_create_brush_from_arrays(const Vector<Vector3> &p_vertices, const Vector<Vector2> &p_uv, const Vector<bool> &p_smooth, const Vector<Ref<Material>> &p_materials);
static void _bind_methods();
public:
void set_flip_faces(bool p_invert);
bool get_flip_faces();
CSGPrimitive3D();
};
class CSGMesh3D : public CSGPrimitive3D {
GDCLASS(CSGMesh3D, CSGPrimitive3D);
virtual CSGBrush *_build_brush() override;
Ref<Mesh> mesh;
Ref<Material> material;
void _mesh_changed();
protected:
static void _bind_methods();
public:
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh();
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
};
class CSGSphere3D : public CSGPrimitive3D {
GDCLASS(CSGSphere3D, CSGPrimitive3D);
virtual CSGBrush *_build_brush() override;
Ref<Material> material;
bool smooth_faces;
float radius;
int radial_segments;
int rings;
protected:
static void _bind_methods();
public:
void set_radius(const float p_radius);
float get_radius() const;
void set_radial_segments(const int p_radial_segments);
int get_radial_segments() const;
void set_rings(const int p_rings);
int get_rings() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
void set_smooth_faces(bool p_smooth_faces);
bool get_smooth_faces() const;
CSGSphere3D();
};
class CSGBox3D : public CSGPrimitive3D {
GDCLASS(CSGBox3D, CSGPrimitive3D);
virtual CSGBrush *_build_brush() override;
Ref<Material> material;
Vector3 size = Vector3(1, 1, 1);
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
#endif
public:
void set_size(const Vector3 &p_size);
Vector3 get_size() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
CSGBox3D() {}
};
class CSGCylinder3D : public CSGPrimitive3D {
GDCLASS(CSGCylinder3D, CSGPrimitive3D);
virtual CSGBrush *_build_brush() override;
Ref<Material> material;
float radius;
float height;
int sides;
bool cone;
bool smooth_faces;
protected:
static void _bind_methods();
public:
void set_radius(const float p_radius);
float get_radius() const;
void set_height(const float p_height);
float get_height() const;
void set_sides(const int p_sides);
int get_sides() const;
void set_cone(const bool p_cone);
bool is_cone() const;
void set_smooth_faces(bool p_smooth_faces);
bool get_smooth_faces() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
CSGCylinder3D();
};
class CSGTorus3D : public CSGPrimitive3D {
GDCLASS(CSGTorus3D, CSGPrimitive3D);
virtual CSGBrush *_build_brush() override;
Ref<Material> material;
float inner_radius;
float outer_radius;
int sides;
int ring_sides;
bool smooth_faces;
protected:
static void _bind_methods();
public:
void set_inner_radius(const float p_inner_radius);
float get_inner_radius() const;
void set_outer_radius(const float p_outer_radius);
float get_outer_radius() const;
void set_sides(const int p_sides);
int get_sides() const;
void set_ring_sides(const int p_ring_sides);
int get_ring_sides() const;
void set_smooth_faces(bool p_smooth_faces);
bool get_smooth_faces() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
CSGTorus3D();
};
class CSGPolygon3D : public CSGPrimitive3D {
GDCLASS(CSGPolygon3D, CSGPrimitive3D);
public:
enum Mode {
MODE_DEPTH,
MODE_SPIN,
MODE_PATH
};
enum PathIntervalType {
PATH_INTERVAL_DISTANCE,
PATH_INTERVAL_SUBDIVIDE
};
enum PathRotation {
PATH_ROTATION_POLYGON,
PATH_ROTATION_PATH,
PATH_ROTATION_PATH_FOLLOW,
};
private:
virtual CSGBrush *_build_brush() override;
Vector<Vector2> polygon;
Ref<Material> material;
Mode mode;
float depth;
float spin_degrees;
int spin_sides;
NodePath path_node;
PathIntervalType path_interval_type;
float path_interval;
float path_simplify_angle;
PathRotation path_rotation;
bool path_rotation_accurate;
bool path_local;
Path3D *path = nullptr;
bool smooth_faces;
bool path_continuous_u;
real_t path_u_distance;
bool path_joined;
bool _is_editable_3d_polygon() const;
bool _has_editable_3d_polygon_no_depth() const;
void _path_changed();
void _path_exited();
protected:
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
public:
void set_polygon(const Vector<Vector2> &p_polygon);
Vector<Vector2> get_polygon() const;
void set_mode(Mode p_mode);
Mode get_mode() const;
void set_depth(float p_depth);
float get_depth() const;
void set_spin_degrees(float p_spin_degrees);
float get_spin_degrees() const;
void set_spin_sides(int p_spin_sides);
int get_spin_sides() const;
void set_path_node(const NodePath &p_path);
NodePath get_path_node() const;
void set_path_interval_type(PathIntervalType p_interval_type);
PathIntervalType get_path_interval_type() const;
void set_path_interval(float p_interval);
float get_path_interval() const;
void set_path_simplify_angle(float p_angle);
float get_path_simplify_angle() const;
void set_path_rotation(PathRotation p_rotation);
PathRotation get_path_rotation() const;
void set_path_rotation_accurate(bool p_enable);
bool get_path_rotation_accurate() const;
void set_path_local(bool p_enable);
bool is_path_local() const;
void set_path_continuous_u(bool p_enable);
bool is_path_continuous_u() const;
void set_path_u_distance(real_t p_path_u_distance);
real_t get_path_u_distance() const;
void set_path_joined(bool p_enable);
bool is_path_joined() const;
void set_smooth_faces(bool p_smooth_faces);
bool get_smooth_faces() const;
void set_material(const Ref<Material> &p_material);
Ref<Material> get_material() const;
CSGPolygon3D();
};
VARIANT_ENUM_CAST(CSGPolygon3D::Mode)
VARIANT_ENUM_CAST(CSGPolygon3D::PathRotation)
VARIANT_ENUM_CAST(CSGPolygon3D::PathIntervalType)

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGBox3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG Box shape.
</brief_description>
<description>
This node allows you to create a box for use with the CSG system.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the box.
</member>
<member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
The box's width, height and depth.
</member>
</members>
</class>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGCombiner3D" inherits="CSGShape3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG node that allows you to combine other CSG modifiers.
</brief_description>
<description>
For complex arrangements of shapes, it is sometimes needed to add structure to your CSG nodes. The CSGCombiner3D node allows you to create this structure. The node encapsulates the result of the CSG operations of its children. In this way, it is possible to do operations on one set of shapes that are children of one CSGCombiner3D node, and a set of separate operations on a second set of shapes that are children of a second CSGCombiner3D node, and then do an operation that takes the two end results as its input to create the final shape.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
</class>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGCylinder3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG Cylinder shape.
</brief_description>
<description>
This node allows you to create a cylinder (or cone) for use with the CSG system.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false">
If [code]true[/code] a cone is created, the [member radius] will only apply to one side.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The height of the cylinder.
</member>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the cylinder.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
The radius of the cylinder.
</member>
<member name="sides" type="int" setter="set_sides" getter="get_sides" default="8">
The number of sides of the cylinder, the higher this number the more detail there will be in the cylinder.
</member>
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
If [code]true[/code] the normals of the cylinder are set to give a smooth effect making the cylinder seem rounded. If [code]false[/code] the cylinder will have a flat shaded look.
</member>
</members>
</class>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGMesh3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG Mesh shape that uses a mesh resource.
</brief_description>
<description>
This CSG node allows you to use any mesh resource as a CSG shape, provided it is [i]manifold[/i]. A manifold shape is closed, does not self-intersect, does not contain internal faces and has no edges that connect to more than two faces. See also [CSGPolygon3D] for drawing 2D extruded polygons to be used as CSG nodes.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="material" type="Material" setter="set_material" getter="get_material">
The [Material] used in drawing the CSG shape.
</member>
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
The [Mesh] resource to use as a CSG shape.
[b]Note:[/b] Some [Mesh] types such as [PlaneMesh], [PointMesh], [QuadMesh], and [RibbonTrailMesh] are excluded from the type hint for this property, as these primitives are non-[i]manifold[/i] and thus not compatible with the CSG algorithm.
[b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU.
[constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal.
</member>
</members>
</class>

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGPolygon3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Extrudes a 2D polygon shape to create a 3D mesh.
</brief_description>
<description>
An array of 2D points is extruded to quickly and easily create a variety of 3D meshes. See also [CSGMesh3D] for using 3D meshes as CSG nodes.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="depth" type="float" setter="set_depth" getter="get_depth" default="1.0">
When [member mode] is [constant MODE_DEPTH], the depth of the extrusion.
</member>
<member name="material" type="Material" setter="set_material" getter="get_material">
Material to use for the resulting mesh. The UV maps the top half of the material to the extruded shape (U along the length of the extrusions and V around the outline of the [member polygon]), the bottom-left quarter to the front end face, and the bottom-right quarter to the back end face.
</member>
<member name="mode" type="int" setter="set_mode" getter="get_mode" enum="CSGPolygon3D.Mode" default="0">
The [member mode] used to extrude the [member polygon].
</member>
<member name="path_continuous_u" type="bool" setter="set_path_continuous_u" getter="is_path_continuous_u">
When [member mode] is [constant MODE_PATH], by default, the top half of the [member material] is stretched along the entire length of the extruded shape. If [code]false[/code] the top half of the material is repeated every step of the extrusion.
</member>
<member name="path_interval" type="float" setter="set_path_interval" getter="get_path_interval">
When [member mode] is [constant MODE_PATH], the path interval or ratio of path points to extrusions.
</member>
<member name="path_interval_type" type="int" setter="set_path_interval_type" getter="get_path_interval_type" enum="CSGPolygon3D.PathIntervalType">
When [member mode] is [constant MODE_PATH], this will determine if the interval should be by distance ([constant PATH_INTERVAL_DISTANCE]) or subdivision fractions ([constant PATH_INTERVAL_SUBDIVIDE]).
</member>
<member name="path_joined" type="bool" setter="set_path_joined" getter="is_path_joined">
When [member mode] is [constant MODE_PATH], if [code]true[/code] the ends of the path are joined, by adding an extrusion between the last and first points of the path.
</member>
<member name="path_local" type="bool" setter="set_path_local" getter="is_path_local">
When [member mode] is [constant MODE_PATH], if [code]true[/code] the [Transform3D] of the [CSGPolygon3D] is used as the starting point for the extrusions, not the [Transform3D] of the [member path_node].
</member>
<member name="path_node" type="NodePath" setter="set_path_node" getter="get_path_node">
When [member mode] is [constant MODE_PATH], the location of the [Path3D] object used to extrude the [member polygon].
</member>
<member name="path_rotation" type="int" setter="set_path_rotation" getter="get_path_rotation" enum="CSGPolygon3D.PathRotation">
When [member mode] is [constant MODE_PATH], the path rotation method used to rotate the [member polygon] as it is extruded.
</member>
<member name="path_rotation_accurate" type="bool" setter="set_path_rotation_accurate" getter="get_path_rotation_accurate">
When [member mode] is [constant MODE_PATH], if [code]true[/code] the polygon will be rotated according to the proper tangent of the path at the sampled points. If [code]false[/code] an approximation is used, which decreases in accuracy as the number of subdivisions decreases.
</member>
<member name="path_simplify_angle" type="float" setter="set_path_simplify_angle" getter="get_path_simplify_angle">
When [member mode] is [constant MODE_PATH], extrusions that are less than this angle, will be merged together to reduce polygon count.
</member>
<member name="path_u_distance" type="float" setter="set_path_u_distance" getter="get_path_u_distance">
When [member mode] is [constant MODE_PATH], this is the distance along the path, in meters, the texture coordinates will tile. When set to 0, texture coordinates will match geometry exactly with no tiling.
</member>
<member name="polygon" type="PackedVector2Array" setter="set_polygon" getter="get_polygon" default="PackedVector2Array(0, 0, 0, 1, 1, 1, 1, 0)">
The point array that defines the 2D polygon that is extruded. This can be a convex or concave polygon with 3 or more points. The polygon must [i]not[/i] have any intersecting edges. Otherwise, triangulation will fail and no mesh will be generated.
[b]Note:[/b] If only 1 or 2 points are defined in [member polygon], no mesh will be generated.
</member>
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="false">
If [code]true[/code], applies smooth shading to the extrusions.
</member>
<member name="spin_degrees" type="float" setter="set_spin_degrees" getter="get_spin_degrees">
When [member mode] is [constant MODE_SPIN], the total number of degrees the [member polygon] is rotated when extruding.
</member>
<member name="spin_sides" type="int" setter="set_spin_sides" getter="get_spin_sides">
When [member mode] is [constant MODE_SPIN], the number of extrusions made.
</member>
</members>
<constants>
<constant name="MODE_DEPTH" value="0" enum="Mode">
The [member polygon] shape is extruded along the negative Z axis.
</constant>
<constant name="MODE_SPIN" value="1" enum="Mode">
The [member polygon] shape is extruded by rotating it around the Y axis.
</constant>
<constant name="MODE_PATH" value="2" enum="Mode">
The [member polygon] shape is extruded along the [Path3D] specified in [member path_node].
</constant>
<constant name="PATH_ROTATION_POLYGON" value="0" enum="PathRotation">
The [member polygon] shape is not rotated.
[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
</constant>
<constant name="PATH_ROTATION_PATH" value="1" enum="PathRotation">
The [member polygon] shape is rotated along the path, but it is not rotated around the path axis.
[b]Note:[/b] Requires the path Z coordinates to continually decrease to ensure viable shapes.
</constant>
<constant name="PATH_ROTATION_PATH_FOLLOW" value="2" enum="PathRotation">
The [member polygon] shape follows the path and its rotations around the path axis.
</constant>
<constant name="PATH_INTERVAL_DISTANCE" value="0" enum="PathIntervalType">
When [member mode] is set to [constant MODE_PATH], [member path_interval] will determine the distance, in meters, each interval of the path will extrude.
</constant>
<constant name="PATH_INTERVAL_SUBDIVIDE" value="1" enum="PathIntervalType">
When [member mode] is set to [constant MODE_PATH], [member path_interval] will subdivide the polygons along the path.
</constant>
</constants>
</class>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGPrimitive3D" inherits="CSGShape3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Base class for CSG primitives.
</brief_description>
<description>
Parent class for various CSG primitives. It contains code and functionality that is common between them. It cannot be used directly. Instead use one of the various classes that inherit from it.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="flip_faces" type="bool" setter="set_flip_faces" getter="get_flip_faces" default="false">
If set, the order of the vertices in each triangle are reversed resulting in the backside of the mesh being drawn.
</member>
</members>
</class>

View File

@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGShape3D" inherits="GeometryInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
The CSG base class.
</brief_description>
<description>
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
[b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost. Consider baking final CSG operation results into static geometry that replaces the CSG nodes.
Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected.
Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision.
Entire scenes of CSG nodes can be baked to static geometry and exported with the editor glTF scene exporter: [b]Scene &gt; Export As... &gt; glTF 2.0 Scene...[/b]
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<methods>
<method name="bake_collision_shape">
<return type="ConcavePolygonShape3D" />
<description>
Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry.
[b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving.
[b]Note:[/b] CSG mesh data updates are deferred, which means they are updated with a delay of one rendered frame. To avoid getting an empty shape or outdated mesh data, make sure to call [code]await get_tree().process_frame[/code] before using [method bake_collision_shape] in [method Node._ready] or after changing properties on the [CSGShape3D].
</description>
</method>
<method name="bake_static_mesh">
<return type="ArrayMesh" />
<description>
Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry.
[b]Note:[/b] CSG mesh data updates are deferred, which means they are updated with a delay of one rendered frame. To avoid getting an empty mesh or outdated mesh data, make sure to call [code]await get_tree().process_frame[/code] before using [method bake_static_mesh] in [method Node._ready] or after changing properties on the [CSGShape3D].
</description>
</method>
<method name="get_collision_layer_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
Returns whether or not the specified layer of the [member collision_layer] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_collision_mask_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
<description>
Returns whether or not the specified layer of the [member collision_mask] is enabled, given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="get_meshes" qualifiers="const">
<return type="Array" />
<description>
Returns an [Array] with two elements, the first is the [Transform3D] of this node and the second is the root [Mesh] of this node. Only works when this node is the root shape.
[b]Note:[/b] CSG mesh data updates are deferred, which means they are updated with a delay of one rendered frame. To avoid getting an empty shape or outdated mesh data, make sure to call [code]await get_tree().process_frame[/code] before using [method get_meshes] in [method Node._ready] or after changing properties on the [CSGShape3D].
</description>
</method>
<method name="is_root_shape" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if this is a root shape and is thus the object that is rendered.
</description>
</method>
<method name="set_collision_layer_value">
<return type="void" />
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
Based on [param value], enables or disables the specified layer in the [member collision_layer], given a [param layer_number] between 1 and 32.
</description>
</method>
<method name="set_collision_mask_value">
<return type="void" />
<param index="0" name="layer_number" type="int" />
<param index="1" name="value" type="bool" />
<description>
Based on [param value], enables or disables the specified layer in the [member collision_mask], given a [param layer_number] between 1 and 32.
</description>
</method>
</methods>
<members>
<member name="calculate_tangents" type="bool" setter="set_calculate_tangents" getter="is_calculating_tangents" default="true">
Calculate tangents for the CSG shape which allows the use of normal and height maps. This is only applied on the root shape, this setting is ignored on any child. Setting this to [code]false[/code] can speed up shape generation slightly.
</member>
<member name="collision_layer" type="int" setter="set_collision_layer" getter="get_collision_layer" default="1">
The physics layers this area is in.
Collidable objects can exist in any of 32 different layers. These layers work like a tagging system, and are not visual. A collidable can use these layers to select with which objects it can collide, using the collision_mask property.
A contact is detected if object A is in any of the layers that object B scans, or object B is in any layer scanned by object A. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
The physics layers this CSG shape scans for collisions. Only effective if [member use_collision] is [code]true[/code]. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="collision_priority" type="float" setter="set_collision_priority" getter="get_collision_priority" default="1.0">
The priority used to solve colliding when occurring penetration. Only effective if [member use_collision] is [code]true[/code]. The higher the priority is, the lower the penetration into the object will be. This can for example be used to prevent the player from breaking through the boundaries of a level.
</member>
<member name="operation" type="int" setter="set_operation" getter="get_operation" enum="CSGShape3D.Operation" default="0">
The operation that is performed on this shape. This is ignored for the first CSG child node as the operation is between this node and the previous child of this nodes parent.
</member>
<member name="snap" type="float" setter="set_snap" getter="get_snap" deprecated="The CSG library no longer uses snapping.">
This property does nothing.
</member>
<member name="use_collision" type="bool" setter="set_use_collision" getter="is_using_collision" default="false">
Adds a collision shape to the physics engine for our CSG shape. This will always act like a static body. Note that the collision shape is still active even if the CSG shape itself is hidden. See also [member collision_mask] and [member collision_priority].
</member>
</members>
<constants>
<constant name="OPERATION_UNION" value="0" enum="Operation">
Geometry of both primitives is merged, intersecting geometry is removed.
</constant>
<constant name="OPERATION_INTERSECTION" value="1" enum="Operation">
Only intersecting geometry remains, the rest is removed.
</constant>
<constant name="OPERATION_SUBTRACTION" value="2" enum="Operation">
The second shape is subtracted from the first, leaving a dent with its shape.
</constant>
</constants>
</class>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGSphere3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG Sphere shape.
</brief_description>
<description>
This node allows you to create a sphere for use with the CSG system.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the sphere.
</member>
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="12">
Number of vertical slices for the sphere.
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
Radius of the sphere.
</member>
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="6">
Number of horizontal slices for the sphere.
</member>
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
If [code]true[/code] the normals of the sphere are set to give a smooth effect making the sphere seem rounded. If [code]false[/code] the sphere will have a flat shaded look.
</member>
</members>
</class>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CSGTorus3D" inherits="CSGPrimitive3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A CSG Torus shape.
</brief_description>
<description>
This node allows you to create a torus for use with the CSG system.
[b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<members>
<member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5">
The inner radius of the torus.
</member>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the torus.
</member>
<member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0">
The outer radius of the torus.
</member>
<member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6">
The number of edges each ring of the torus is constructed of.
</member>
<member name="sides" type="int" setter="set_sides" getter="get_sides" default="8">
The number of slices the torus is constructed of.
</member>
<member name="smooth_faces" type="bool" setter="set_smooth_faces" getter="get_smooth_faces" default="true">
If [code]true[/code] the normals of the torus are set to give a smooth effect making the torus seem rounded. If [code]false[/code] the torus will have a flat shaded look.
</member>
</members>
</class>

View File

@@ -0,0 +1,507 @@
/**************************************************************************/
/* csg_gizmos.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "csg_gizmos.h"
#include "editor/editor_node.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
#include "editor/scene/3d/node_3d_editor_plugin.h"
#include "editor/settings/editor_settings.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/menu_button.h"
void CSGShapeEditor::_node_removed(Node *p_node) {
if (p_node == node) {
node = nullptr;
options->hide();
}
}
void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
node = p_csg_shape;
if (node) {
options->show();
} else {
options->hide();
}
}
void CSGShapeEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
options->set_button_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
} break;
}
}
void CSGShapeEditor::_menu_option(int p_option) {
Array meshes = node->get_meshes();
if (meshes.is_empty()) {
err_dialog->set_text(TTR("CSG operation returned an empty array."));
err_dialog->popup_centered();
return;
}
switch (p_option) {
case MENU_OPTION_BAKE_MESH_INSTANCE: {
_create_baked_mesh_instance();
} break;
case MENU_OPTION_BAKE_COLLISION_SHAPE: {
_create_baked_collision_shape();
} break;
}
}
void CSGShapeEditor::_create_baked_mesh_instance() {
if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node."));
err_dialog->popup_centered();
return;
}
Ref<ArrayMesh> mesh = node->bake_static_mesh();
if (mesh.is_null()) {
err_dialog->set_text(TTR("CSG operation returned an empty mesh."));
err_dialog->popup_centered();
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Create baked CSGShape3D Mesh Instance"));
Node *owner = get_tree()->get_edited_scene_root();
MeshInstance3D *mi = memnew(MeshInstance3D);
mi->set_mesh(mesh);
mi->set_name("CSGBakedMeshInstance3D");
mi->set_transform(node->get_transform());
ur->add_do_method(node, "add_sibling", mi, true);
ur->add_do_method(mi, "set_owner", owner);
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi);
ur->add_do_reference(mi);
ur->add_undo_method(node->get_parent(), "remove_child", mi);
ur->commit_action();
}
void CSGShapeEditor::_create_baked_collision_shape() {
if (node == get_tree()->get_edited_scene_root()) {
err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node."));
err_dialog->popup_centered();
return;
}
Ref<Shape3D> shape = node->bake_collision_shape();
if (shape.is_null()) {
err_dialog->set_text(TTR("CSG operation returned an empty shape."));
err_dialog->popup_centered();
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Create baked CSGShape3D Collision Shape"));
Node *owner = get_tree()->get_edited_scene_root();
CollisionShape3D *cshape = memnew(CollisionShape3D);
cshape->set_shape(shape);
cshape->set_name("CSGBakedCollisionShape3D");
cshape->set_transform(node->get_transform());
ur->add_do_method(node, "add_sibling", cshape, true);
ur->add_do_method(cshape, "set_owner", owner);
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape);
ur->add_do_reference(cshape);
ur->add_undo_method(node->get_parent(), "remove_child", cshape);
ur->commit_action();
}
CSGShapeEditor::CSGShapeEditor() {
options = memnew(MenuButton);
options->hide();
options->set_text(TTR("CSG"));
options->set_switch_on_hover(true);
options->set_flat(false);
options->set_theme_type_variation("FlatMenuButton");
Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE);
options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE);
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option));
err_dialog = memnew(AcceptDialog);
add_child(err_dialog);
}
///////////
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
helper.instantiate();
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg");
create_material("shape_union_material", gizmo_color);
create_material("shape_union_solid_material", gizmo_color);
gizmo_color.invert();
create_material("shape_subtraction_material", gizmo_color);
create_material("shape_subtraction_solid_material", gizmo_color);
gizmo_color.r = 0.95;
gizmo_color.g = 0.95;
gizmo_color.b = 0.95;
create_material("shape_intersection_material", gizmo_color);
create_material("shape_intersection_solid_material", gizmo_color);
create_handle_material("handles");
}
String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
return "Radius";
}
if (Object::cast_to<CSGBox3D>(cs)) {
return helper->box_get_handle_name(p_id);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
return p_id == 0 ? "Radius" : "Height";
}
if (Object::cast_to<CSGTorus3D>(cs)) {
return p_id == 0 ? "InnerRadius" : "OuterRadius";
}
return "";
}
Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
return s->get_radius();
}
if (Object::cast_to<CSGBox3D>(cs)) {
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
return s->get_size();
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
return Vector2(s->get_radius(), s->get_height());
}
if (Object::cast_to<CSGTorus3D>(cs)) {
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius();
}
return Variant();
}
void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
}
void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
Vector3 sg[2];
helper->get_segment(p_camera, p_point, sg);
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
float d = ra.x;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
s->set_radius(d);
}
if (Object::cast_to<CSGBox3D>(cs)) {
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
Vector3 size = s->get_size();
Vector3 position;
helper->box_set_handle(sg, p_id, size, position);
s->set_size(size);
s->set_global_position(position);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
real_t height = s->get_height();
real_t radius = s->get_radius();
Vector3 position;
helper->cylinder_set_handle(sg, p_id, height, radius, position);
s->set_height(height);
s->set_radius(radius);
s->set_global_position(position);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
Vector3 axis;
axis[0] = 1.0;
Vector3 ra, rb;
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
float d = axis.dot(ra);
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
}
if (d < 0.001) {
d = 0.001;
}
if (p_id == 0) {
s->set_inner_radius(d);
} else if (p_id == 1) {
s->set_outer_radius(d);
}
}
}
void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
if (p_cancel) {
s->set_radius(p_restore);
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
ur->create_action(TTR("Change Sphere Shape Radius"));
ur->add_do_method(s, "set_radius", s->get_radius());
ur->add_undo_method(s, "set_radius", p_restore);
ur->commit_action();
}
if (Object::cast_to<CSGBox3D>(cs)) {
helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
if (p_cancel) {
if (p_id == 0) {
s->set_inner_radius(p_restore);
} else {
s->set_outer_radius(p_restore);
}
return;
}
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
if (p_id == 0) {
ur->create_action(TTR("Change Torus Inner Radius"));
ur->add_do_method(s, "set_inner_radius", s->get_inner_radius());
ur->add_undo_method(s, "set_inner_radius", p_restore);
} else {
ur->create_action(TTR("Change Torus Outer Radius"));
ur->add_do_method(s, "set_outer_radius", s->get_outer_radius());
ur->add_undo_method(s, "set_outer_radius", p_restore);
}
ur->commit_action();
}
}
bool CSGShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
return Object::cast_to<CSGSphere3D>(p_spatial) || Object::cast_to<CSGBox3D>(p_spatial) || Object::cast_to<CSGCylinder3D>(p_spatial) || Object::cast_to<CSGTorus3D>(p_spatial) || Object::cast_to<CSGMesh3D>(p_spatial) || Object::cast_to<CSGPolygon3D>(p_spatial);
}
String CSGShape3DGizmoPlugin::get_gizmo_name() const {
return "CSGShape3D";
}
int CSGShape3DGizmoPlugin::get_priority() const {
return -1;
}
bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const {
return true;
}
void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->clear();
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
Vector<Vector3> faces = cs->get_brush_faces();
if (faces.is_empty()) {
return;
}
Vector<Vector3> lines;
lines.resize(faces.size() * 2);
{
const Vector3 *r = faces.ptr();
for (int i = 0; i < lines.size(); i += 6) {
int f = i / 6;
for (int j = 0; j < 3; j++) {
int j_n = (j + 1) % 3;
lines.write[i + j * 2 + 0] = r[f * 3 + j];
lines.write[i + j * 2 + 1] = r[f * 3 + j_n];
}
}
}
Ref<Material> material;
switch (cs->get_operation()) {
case CSGShape3D::OPERATION_UNION:
material = get_material("shape_union_material", p_gizmo);
break;
case CSGShape3D::OPERATION_INTERSECTION:
material = get_material("shape_intersection_material", p_gizmo);
break;
case CSGShape3D::OPERATION_SUBTRACTION:
material = get_material("shape_subtraction_material", p_gizmo);
break;
}
Ref<Material> handles_material = get_material("handles");
p_gizmo->add_lines(lines, material);
p_gizmo->add_collision_segments(lines);
if (cs->is_root_shape()) {
Array csg_meshes = cs->get_meshes();
if (csg_meshes.size() == 2) {
Ref<Mesh> csg_mesh = csg_meshes[1];
if (csg_mesh.is_valid()) {
p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh());
}
}
}
if (p_gizmo->is_selected()) {
// Draw a translucent representation of the CSG node
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
Array array;
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = faces;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
Ref<Material> solid_material;
switch (cs->get_operation()) {
case CSGShape3D::OPERATION_UNION:
solid_material = get_material("shape_union_solid_material", p_gizmo);
break;
case CSGShape3D::OPERATION_INTERSECTION:
solid_material = get_material("shape_intersection_solid_material", p_gizmo);
break;
case CSGShape3D::OPERATION_SUBTRACTION:
solid_material = get_material("shape_subtraction_solid_material", p_gizmo);
break;
}
p_gizmo->add_mesh(mesh, solid_material);
}
if (Object::cast_to<CSGSphere3D>(cs)) {
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
float r = s->get_radius();
Vector<Vector3> handles;
handles.push_back(Vector3(r, 0, 0));
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<CSGBox3D>(cs)) {
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
Vector<Vector3> handles = helper->box_get_handles(s->get_size());
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
Vector<Vector3> handles;
handles.push_back(Vector3(s->get_inner_radius(), 0, 0));
handles.push_back(Vector3(s->get_outer_radius(), 0, 0));
p_gizmo->add_handles(handles, handles_material);
}
}
void EditorPluginCSG::edit(Object *p_object) {
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
if (csg_shape && csg_shape->is_root_shape()) {
csg_shape_editor->edit(csg_shape);
} else {
csg_shape_editor->edit(nullptr);
}
}
bool EditorPluginCSG::handles(Object *p_object) const {
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
return csg_shape && csg_shape->is_root_shape();
}
EditorPluginCSG::EditorPluginCSG() {
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
csg_shape_editor = memnew(CSGShapeEditor);
EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
}

View File

@@ -0,0 +1,102 @@
/**************************************************************************/
/* csg_gizmos.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "../csg_shape.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/scene/3d/node_3d_editor_gizmos.h"
#include "scene/gui/control.h"
class AcceptDialog;
class Gizmo3DHelper;
class MenuButton;
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
Ref<Gizmo3DHelper> helper;
public:
virtual bool has_gizmo(Node3D *p_spatial) override;
virtual String get_gizmo_name() const override;
virtual int get_priority() const override;
virtual bool is_selectable_when_hidden() const override;
virtual void redraw(EditorNode3DGizmo *p_gizmo) override;
virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override;
void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override;
virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override;
virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) override;
CSGShape3DGizmoPlugin();
};
class CSGShapeEditor : public Control {
GDCLASS(CSGShapeEditor, Control);
enum Menu {
MENU_OPTION_BAKE_MESH_INSTANCE,
MENU_OPTION_BAKE_COLLISION_SHAPE,
};
CSGShape3D *node = nullptr;
MenuButton *options = nullptr;
AcceptDialog *err_dialog = nullptr;
void _menu_option(int p_option);
void _create_baked_mesh_instance();
void _create_baked_collision_shape();
protected:
void _node_removed(Node *p_node);
void _notification(int p_what);
public:
void edit(CSGShape3D *p_csg_shape);
CSGShapeEditor();
};
class EditorPluginCSG : public EditorPlugin {
GDCLASS(EditorPluginCSG, EditorPlugin);
CSGShapeEditor *csg_shape_editor = nullptr;
public:
virtual String get_plugin_name() const override { return "CSGShape3D"; }
virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;
EditorPluginCSG();
};

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="m8 2 6 3v6l-6 3-6-3V5zm0 12V8l6-3M8 8 2 5" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M4 6a4 4 0 0 1 8 0v4a4 4 0 0 1-8 0zm0 1.25a2.5 1 0 0 0 8 0m-4-5v12" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 476 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="#fc7f7f" d="M3 1a2 2 0 0 0-2 2h2zm2 0v2h2V1zm4 0v2h2V1zm4 0v2h2a2 2 0 0 0-2-2zM1 5v2h2V5zm12 0v2h2V5zM1 9v2h2V9zm0 4a2 2 0 0 0 2 2v-2zm4 0v2h2v-2z"/></svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M2 4v8a6 2 0 0 0 12 0V4A6 2 0 0 0 2 4a6 2 0 0 0 12 0" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fc7f7f" d="M4.73 2A2 2 0 1 0 2 4.73v6.541A2 2 0 1 0 4.729 14H8v-2H4.729A2 2 0 0 0 4 11.271V5.415l4.914 4.916A2 2 0 0 1 9.998 10a2 2 0 0 1 .33-1.084L5.414 4h5.856a2 2 0 0 0 .73.729V8h2V4.729A2 2 0 1 0 11.27 2z"/><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-linejoin="round" stroke-width="2" d="m8 2 6 3.5v5L8 14l-6-3.5v-5h6zm6 3.5L8 9 2 5.5M8 9v5" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><path fill="none" stroke="#fc7f7f" stroke-width="2" d="M8 2a6 6 0 0 0 0 12A6 6 0 0 0 8 2v12M2.05 7.4a6 2 0 0 0 11.9 0" mask="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 472 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><mask id="a"><path fill="#fefefe" d="M0 0h16v10a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2 2 2 0 0 0-2 2v2a2 2 0 0 0 2 2H0z"/></mask><path fill="#5fb2ff" d="M12 9a1 1 0 0 0-1 1v1h2v2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1zm1 4h-2v-2h-1a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"/><g fill="none" stroke="#fc7f7f" mask="url(#a)"><path stroke-width="2" d="M2.5 10a6 4 0 0 0 11 0 4 4 0 0 0 0-4 6 4 0 0 0-11 0 4 4 0 0 0 0 4z"/><path stroke-linecap="round" stroke-width="1.75" d="M6.2 7.2a2 1 0 1 0 3.6 0"/></g></svg>

After

Width:  |  Height:  |  Size: 562 B

View File

@@ -0,0 +1,65 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "csg_shape.h"
#ifdef TOOLS_ENABLED
#include "editor/csg_gizmos.h"
#endif
void initialize_csg_module(ModuleInitializationLevel p_level) {
if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
GDREGISTER_ABSTRACT_CLASS(CSGShape3D);
GDREGISTER_ABSTRACT_CLASS(CSGPrimitive3D);
GDREGISTER_CLASS(CSGMesh3D);
GDREGISTER_CLASS(CSGSphere3D);
GDREGISTER_CLASS(CSGBox3D);
GDREGISTER_CLASS(CSGCylinder3D);
GDREGISTER_CLASS(CSGTorus3D);
GDREGISTER_CLASS(CSGPolygon3D);
GDREGISTER_CLASS(CSGCombiner3D);
#ifndef NAVIGATION_3D_DISABLED
CSGShape3D::navmesh_parse_init();
#endif // NAVIGATION_3D_DISABLED
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
EditorPlugins::add_by_type<EditorPluginCSG>();
}
#endif
}
void uninitialize_csg_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_csg_module(ModuleInitializationLevel p_level);
void uninitialize_csg_module(ModuleInitializationLevel p_level);

View File

@@ -0,0 +1,111 @@
/**************************************************************************/
/* test_csg.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "../csg.h"
#include "../csg_shape.h"
#include "tests/test_macros.h"
namespace TestCSG {
TEST_CASE("[SceneTree][CSG] CSGPolygon3D") {
SUBCASE("[SceneTree][CSG] CSGPolygon3D: using accurate path tangent for polygon rotation") {
const float polygon_radius = 10.0f;
const Vector3 expected_min_bounds = Vector3(-polygon_radius, -polygon_radius, 0);
const Vector3 expected_max_bounds = Vector3(100 + polygon_radius, polygon_radius, 100);
const AABB expected_aabb = AABB(expected_min_bounds, expected_max_bounds - expected_min_bounds);
Ref<Curve3D> curve;
curve.instantiate();
curve->add_point(
// p_position
Vector3(0, 0, 0),
// p_in
Vector3(),
// p_out
Vector3(0, 0, 60));
curve->add_point(
// p_position
Vector3(100, 0, 100),
// p_in
Vector3(0, 0, -60),
// p_out
Vector3());
Path3D *path = memnew(Path3D);
path->set_curve(curve);
CSGPolygon3D *csg_polygon_3d = memnew(CSGPolygon3D);
SceneTree::get_singleton()->get_root()->add_child(csg_polygon_3d);
csg_polygon_3d->add_child(path);
csg_polygon_3d->set_path_node(csg_polygon_3d->get_path_to(path));
csg_polygon_3d->set_mode(CSGPolygon3D::Mode::MODE_PATH);
PackedVector2Array polygon;
polygon.append(Vector2(-polygon_radius, 0));
polygon.append(Vector2(0, polygon_radius));
polygon.append(Vector2(polygon_radius, 0));
polygon.append(Vector2(0, -polygon_radius));
csg_polygon_3d->set_polygon(polygon);
csg_polygon_3d->set_path_rotation(CSGPolygon3D::PathRotation::PATH_ROTATION_PATH);
csg_polygon_3d->set_path_rotation_accurate(true);
// Minimize the number of extrusions.
// This decreases the number of samples taken from the curve.
// Having fewer samples increases the inaccuracy of the line between samples as an approximation of the tangent of the curve.
// With correct polygon orientation, the bounding box for the given curve should be independent of the number of extrusions.
csg_polygon_3d->set_path_interval_type(CSGPolygon3D::PathIntervalType::PATH_INTERVAL_DISTANCE);
csg_polygon_3d->set_path_interval(1000.0f);
// Call get_brush_faces to force the bounding box to update.
csg_polygon_3d->get_brush_faces();
CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb));
// Perform the bounding box check again with a greater number of extrusions.
csg_polygon_3d->set_path_interval(1.0f);
csg_polygon_3d->get_brush_faces();
CHECK(csg_polygon_3d->get_aabb().is_equal_approx(expected_aabb));
csg_polygon_3d->remove_child(path);
SceneTree::get_singleton()->get_root()->remove_child(csg_polygon_3d);
memdelete(csg_polygon_3d);
memdelete(path);
}
}
} // namespace TestCSG

44
modules/cvtt/SCsub Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_cvtt = env_modules.Clone()
# Thirdparty source files
thirdparty_obj = []
thirdparty_dir = "#thirdparty/cvtt/"
thirdparty_sources = [
"ConvectionKernels_API.cpp",
"ConvectionKernels_ETC.cpp",
"ConvectionKernels_BC67.cpp",
"ConvectionKernels_IndexSelector.cpp",
"ConvectionKernels_BC6H_IO.cpp",
"ConvectionKernels_S3TC.cpp",
"ConvectionKernels_BC7_PrioData.cpp",
"ConvectionKernels_SingleFile.cpp",
"ConvectionKernels_BCCommon.cpp",
"ConvectionKernels_Util.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_cvtt.Prepend(CPPEXTPATH=[thirdparty_dir])
env_thirdparty = env_cvtt.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_cvtt.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

6
modules/cvtt/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return env.editor_build
def configure(env):
pass

View File

@@ -0,0 +1,397 @@
/**************************************************************************/
/* image_compress_cvtt.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_compress_cvtt.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/templates/safe_refcount.h"
#include <ConvectionKernels.h>
struct CVTTCompressionJobParams {
bool is_hdr = false;
bool is_signed = false;
int bytes_per_pixel = 0;
cvtt::BC7EncodingPlan bc7_plan;
cvtt::Options options;
};
struct CVTTCompressionRowTask {
Vector<uint8_t> in_mm;
uint8_t *out_mm_bytes = nullptr;
int y_start = 0;
int width = 0;
int height = 0;
};
struct CVTTCompressionJobQueue {
CVTTCompressionJobParams job_params;
const CVTTCompressionRowTask *job_tasks = nullptr;
uint32_t num_tasks = 0;
SafeNumeric<uint32_t> current_task;
};
static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const CVTTCompressionRowTask &p_row_task) {
const uint8_t *in_bytes = p_row_task.in_mm.ptr();
uint8_t *out_bytes = p_row_task.out_mm_bytes;
int w = p_row_task.width;
int h = p_row_task.height;
int y_start = p_row_task.y_start;
int y_end = y_start + 4;
int bytes_per_pixel = p_job_params.bytes_per_pixel;
bool is_hdr = p_job_params.is_hdr;
bool is_signed = p_job_params.is_signed;
cvtt::PixelBlockU8 input_blocks_ldr[cvtt::NumParallelBlocks];
cvtt::PixelBlockF16 input_blocks_hdr[cvtt::NumParallelBlocks];
for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) {
int x_end = x_start + 4 * cvtt::NumParallelBlocks;
for (int y = y_start; y < y_end; y++) {
int first_input_element = (y - y_start) * 4;
const uint8_t *row_start;
if (y >= h) {
row_start = in_bytes + (h - 1) * (w * bytes_per_pixel);
} else {
row_start = in_bytes + y * (w * bytes_per_pixel);
}
for (int x = x_start; x < x_end; x++) {
const uint8_t *pixel_start;
if (x >= w) {
pixel_start = row_start + (w - 1) * bytes_per_pixel;
} else {
pixel_start = row_start + x * bytes_per_pixel;
}
int block_index = (x - x_start) / 4;
int block_element = (x - x_start) % 4 + first_input_element;
if (is_hdr) {
memcpy(input_blocks_hdr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel);
input_blocks_hdr[block_index].m_pixels[block_element][3] = 0x3c00; // 1.0 (unused)
} else {
memcpy(input_blocks_ldr[block_index].m_pixels[block_element], pixel_start, bytes_per_pixel);
}
}
}
uint8_t output_blocks[16 * cvtt::NumParallelBlocks];
if (is_hdr) {
if (is_signed) {
cvtt::Kernels::EncodeBC6HS(output_blocks, input_blocks_hdr, p_job_params.options);
} else {
cvtt::Kernels::EncodeBC6HU(output_blocks, input_blocks_hdr, p_job_params.options);
}
} else {
cvtt::Kernels::EncodeBC7(output_blocks, input_blocks_ldr, p_job_params.options, p_job_params.bc7_plan);
}
unsigned int num_real_blocks = ((w - x_start) + 3) / 4;
if (num_real_blocks > cvtt::NumParallelBlocks) {
num_real_blocks = cvtt::NumParallelBlocks;
}
memcpy(out_bytes, output_blocks, 16 * num_real_blocks);
out_bytes += 16 * num_real_blocks;
}
}
static void _digest_job_queue(void *p_job_queue, uint32_t p_index) {
CVTTCompressionJobQueue *job_queue = static_cast<CVTTCompressionJobQueue *>(p_job_queue);
uint32_t num_tasks = job_queue->num_tasks;
uint32_t total_threads = WorkerThreadPool::get_singleton()->get_thread_count();
uint32_t start = p_index * num_tasks / total_threads;
uint32_t end = (p_index + 1 == total_threads) ? num_tasks : ((p_index + 1) * num_tasks / total_threads);
for (uint32_t i = start; i < end; i++) {
_digest_row_task(job_queue->job_params, job_queue->job_tasks[i]);
}
}
void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
if (p_image->is_compressed()) {
return; //do not compress, already compressed
}
int w = p_image->get_width();
int h = p_image->get_height();
if (w % 4 != 0 || h % 4 != 0) {
w = w <= 2 ? w : (w + 3) & ~3;
h = h <= 2 ? h : (h + 3) & ~3;
}
bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8);
bool is_hdr = (p_image->get_format() >= Image::FORMAT_RF) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
if (!is_ldr && !is_hdr) {
return; // Not a usable source format
}
cvtt::Options options;
uint32_t flags = cvtt::Flags::Default;
flags |= cvtt::Flags::BC7_RespectPunchThrough;
if (p_channels == Image::USED_CHANNELS_RG) { //guessing this is a normal map
flags |= cvtt::Flags::Uniform;
}
options.flags = flags;
Image::Format target_format = Image::FORMAT_BPTC_RGBA;
bool is_signed = false;
if (is_hdr) {
if (p_image->get_format() != Image::FORMAT_RGBH) {
p_image->convert(Image::FORMAT_RGBH);
}
is_signed = p_image->detect_signed();
target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU;
} else {
p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert
}
Vector<uint8_t> data;
int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0;
data.resize(target_size);
int shift = Image::get_format_pixel_rshift(target_format);
uint8_t *wb = data.ptrw();
int64_t dst_ofs = 0;
CVTTCompressionJobQueue job_queue;
job_queue.job_params.is_hdr = is_hdr;
job_queue.job_params.is_signed = is_signed;
job_queue.job_params.options = options;
job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4;
cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5);
// Amdahl's law (Wikipedia)
// If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized,
// therefore only the remaining 19 hours (p = 0.95) of execution time can be parallelized, then regardless of how many threads are devoted
// to a parallelized execution of this program, the minimum execution time cannot be less than one hour.
//
// The number of executions with different inputs can be increased while the latency is the same.
Vector<CVTTCompressionRowTask> tasks;
for (int i = 0; i <= mm_count; i++) {
Vector<uint8_t> in_data;
int width, height;
Image::get_image_mipmap_offset_and_dimensions(w, h, target_format, i, width, height);
int bw = width % 4 != 0 ? width + (4 - width % 4) : width;
int bh = height % 4 != 0 ? height + (4 - height % 4) : height;
int64_t src_mip_ofs, src_mip_size;
int src_mip_w, src_mip_h;
p_image->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
// Pad textures to nearest block by smearing.
if (width != src_mip_w || height != src_mip_h) {
const uint8_t *src_mip_read = p_image->ptr() + src_mip_ofs;
// Reserve the buffer for padded image data.
int px_size = Image::get_format_pixel_size(p_image->get_format());
in_data.resize(width * height * px_size);
uint8_t *ptrw = in_data.ptrw();
int x = 0, y = 0;
for (y = 0; y < src_mip_h; y++) {
for (x = 0; x < src_mip_w; x++) {
memcpy(ptrw + (width * y + x) * px_size, src_mip_read + (src_mip_w * y + x) * px_size, px_size);
}
// First, smear in x.
for (; x < width; x++) {
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - 1) * px_size, px_size);
}
}
// Then, smear in y.
for (; y < height; y++) {
for (x = 0; x < width; x++) {
memcpy(ptrw + (width * y + x) * px_size, ptrw + (width * y + x - width) * px_size, px_size);
}
}
} else {
// Create a buffer filled with the source mip layer data.
in_data.resize(src_mip_size);
memcpy(in_data.ptrw(), p_image->ptr() + src_mip_ofs, src_mip_size);
}
//const uint8_t *in_bytes = &rb[src_ofs];
uint8_t *out_bytes = &wb[dst_ofs];
for (int y_start = 0; y_start < height; y_start += 4) {
CVTTCompressionRowTask row_task;
row_task.width = width;
row_task.height = height;
row_task.y_start = y_start;
row_task.in_mm = in_data;
row_task.out_mm_bytes = out_bytes;
tasks.push_back(row_task);
out_bytes += 16 * (bw / 4);
}
dst_ofs += (MAX(4, bw) * MAX(4, bh)) >> shift;
}
const CVTTCompressionRowTask *tasks_rb = tasks.ptr();
job_queue.job_tasks = &tasks_rb[0];
job_queue.num_tasks = static_cast<uint32_t>(tasks.size());
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&_digest_job_queue, &job_queue, WorkerThreadPool::get_singleton()->get_thread_count(), -1, true, SNAME("CVTT Compress"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
p_image->set_data(w, h, p_image->has_mipmaps(), target_format, data);
print_verbose(vformat("CVTT: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
}
void image_decompress_cvtt(Image *p_image) {
Image::Format target_format;
bool is_signed = false;
bool is_hdr = false;
Image::Format input_format = p_image->get_format();
switch (input_format) {
case Image::FORMAT_BPTC_RGBA:
target_format = Image::FORMAT_RGBA8;
break;
case Image::FORMAT_BPTC_RGBF:
case Image::FORMAT_BPTC_RGBFU:
target_format = Image::FORMAT_RGBH;
is_signed = (input_format == Image::FORMAT_BPTC_RGBF);
is_hdr = true;
break;
default:
return; // Invalid input format
};
int w = p_image->get_width();
int h = p_image->get_height();
const uint8_t *rb = p_image->get_data().ptr();
Vector<uint8_t> data;
int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps());
int mm_count = p_image->get_mipmap_count();
data.resize(target_size);
uint8_t *wb = data.ptrw();
int bytes_per_pixel = is_hdr ? 6 : 4;
int64_t dst_ofs = 0;
for (int i = 0; i <= mm_count; i++) {
int64_t src_ofs = p_image->get_mipmap_offset(i);
const uint8_t *in_bytes = &rb[src_ofs];
uint8_t *out_bytes = &wb[dst_ofs];
cvtt::PixelBlockU8 output_blocks_ldr[cvtt::NumParallelBlocks];
cvtt::PixelBlockF16 output_blocks_hdr[cvtt::NumParallelBlocks];
for (int y_start = 0; y_start < h; y_start += 4) {
int y_end = y_start + 4;
for (int x_start = 0; x_start < w; x_start += 4 * cvtt::NumParallelBlocks) {
uint8_t input_blocks[16 * cvtt::NumParallelBlocks];
memset(input_blocks, 0, sizeof(input_blocks));
unsigned int num_real_blocks = ((w - x_start) + 3) / 4;
if (num_real_blocks > cvtt::NumParallelBlocks) {
num_real_blocks = cvtt::NumParallelBlocks;
}
memcpy(input_blocks, in_bytes, 16 * num_real_blocks);
in_bytes += 16 * num_real_blocks;
int x_end = x_start + 4 * num_real_blocks;
if (is_hdr) {
if (is_signed) {
cvtt::Kernels::DecodeBC6HS(output_blocks_hdr, input_blocks);
} else {
cvtt::Kernels::DecodeBC6HU(output_blocks_hdr, input_blocks);
}
} else {
cvtt::Kernels::DecodeBC7(output_blocks_ldr, input_blocks);
}
for (int y = y_start; y < y_end; y++) {
int first_input_element = (y - y_start) * 4;
uint8_t *row_start;
if (y >= h) {
row_start = out_bytes + (h - 1) * (w * bytes_per_pixel);
} else {
row_start = out_bytes + y * (w * bytes_per_pixel);
}
for (int x = x_start; x < x_end; x++) {
uint8_t *pixel_start;
if (x >= w) {
pixel_start = row_start + (w - 1) * bytes_per_pixel;
} else {
pixel_start = row_start + x * bytes_per_pixel;
}
int block_index = (x - x_start) / 4;
int block_element = (x - x_start) % 4 + first_input_element;
if (is_hdr) {
memcpy(pixel_start, output_blocks_hdr[block_index].m_pixels[block_element], bytes_per_pixel);
} else {
memcpy(pixel_start, output_blocks_ldr[block_index].m_pixels[block_element], bytes_per_pixel);
}
}
}
}
}
dst_ofs += w * h * bytes_per_pixel;
w >>= 1;
h >>= 1;
}
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* image_compress_cvtt.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels);
void image_decompress_cvtt(Image *p_image);

View File

@@ -0,0 +1,51 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#ifdef TOOLS_ENABLED
#include "image_compress_cvtt.h"
void initialize_cvtt_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
Image::_image_compress_bptc_func = image_compress_cvtt;
}
void uninitialize_cvtt_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
#endif // TOOLS_ENABLED

View File

@@ -0,0 +1,40 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef TOOLS_ENABLED
#include "modules/register_module_types.h"
void initialize_cvtt_module(ModuleInitializationLevel p_level);
void uninitialize_cvtt_module(ModuleInitializationLevel p_level);
#endif // TOOLS_ENABLED

9
modules/dds/SCsub Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_dds = env_modules.Clone()
env_dds.add_source_files(env.modules_sources, "*.cpp")

6
modules/dds/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return True
def configure(env):
pass

207
modules/dds/dds_enums.h Normal file
View File

@@ -0,0 +1,207 @@
/**************************************************************************/
/* dds_enums.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
#define PF_FOURCC(m_s) ((uint32_t)(((m_s)[3] << 24U) | ((m_s)[2] << 16U) | ((m_s)[1] << 8U) | ((m_s)[0])))
// Reference: https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
enum {
DDS_MAGIC = 0x20534444,
DDS_HEADER_SIZE = 124,
DDS_PIXELFORMAT_SIZE = 32,
DDSD_PITCH = 0x00000008,
DDSD_LINEARSIZE = 0x00080000,
DDSD_MIPMAPCOUNT = 0x00020000,
DDSD_CAPS = 0x1,
DDSD_HEIGHT = 0x2,
DDSD_WIDTH = 0x4,
DDSD_PIXELFORMAT = 0x1000,
DDPF_ALPHAPIXELS = 0x00000001,
DDPF_ALPHAONLY = 0x00000002,
DDPF_FOURCC = 0x00000004,
DDPF_RGB = 0x00000040,
DDPF_RG_SNORM = 0x00080000,
DDSC2_CUBEMAP = 0x200,
DDSC2_VOLUME = 0x200000,
DX10D_1D = 2,
DX10D_2D = 3,
DX10D_3D = 4,
};
enum DDSFourCC {
DDFCC_DXT1 = PF_FOURCC("DXT1"),
DDFCC_DXT2 = PF_FOURCC("DXT2"),
DDFCC_DXT3 = PF_FOURCC("DXT3"),
DDFCC_DXT4 = PF_FOURCC("DXT4"),
DDFCC_DXT5 = PF_FOURCC("DXT5"),
DDFCC_ATI1 = PF_FOURCC("ATI1"),
DDFCC_BC4U = PF_FOURCC("BC4U"),
DDFCC_ATI2 = PF_FOURCC("ATI2"),
DDFCC_BC5U = PF_FOURCC("BC5U"),
DDFCC_A2XY = PF_FOURCC("A2XY"),
DDFCC_DX10 = PF_FOURCC("DX10"),
DDFCC_R16F = 111,
DDFCC_RG16F = 112,
DDFCC_RGBA16F = 113,
DDFCC_R32F = 114,
DDFCC_RG32F = 115,
DDFCC_RGBA32F = 116,
};
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
enum DXGIFormat {
DXGI_R32G32B32A32_FLOAT = 2,
DXGI_R32G32B32_FLOAT = 6,
DXGI_R16G16B16A16_FLOAT = 10,
DXGI_R32G32_FLOAT = 16,
DXGI_R10G10B10A2_UNORM = 24,
DXGI_R8G8B8A8_UNORM = 28,
DXGI_R8G8B8A8_UNORM_SRGB = 29,
DXGI_R16G16_FLOAT = 34,
DXGI_R32_FLOAT = 41,
DXGI_R8G8_UNORM = 49,
DXGI_R16_FLOAT = 54,
DXGI_R8_UNORM = 61,
DXGI_A8_UNORM = 65,
DXGI_R9G9B9E5 = 67,
DXGI_BC1_UNORM = 71,
DXGI_BC1_UNORM_SRGB = 72,
DXGI_BC2_UNORM = 74,
DXGI_BC2_UNORM_SRGB = 75,
DXGI_BC3_UNORM = 77,
DXGI_BC3_UNORM_SRGB = 78,
DXGI_BC4_UNORM = 80,
DXGI_BC5_UNORM = 83,
DXGI_B5G6R5_UNORM = 85,
DXGI_B5G5R5A1_UNORM = 86,
DXGI_B8G8R8A8_UNORM = 87,
DXGI_BC6H_UF16 = 95,
DXGI_BC6H_SF16 = 96,
DXGI_BC7_UNORM = 98,
DXGI_BC7_UNORM_SRGB = 99,
DXGI_B4G4R4A4_UNORM = 115,
};
// The legacy bitmasked format names here represent the actual data layout in the files,
// while their official names are flipped (e.g. RGBA8 layout is officially called ABGR8).
enum DDSFormat {
DDS_DXT1,
DDS_DXT3,
DDS_DXT5,
DDS_ATI1,
DDS_ATI2,
DDS_BC6U,
DDS_BC6S,
DDS_BC7,
DDS_R16F,
DDS_RG16F,
DDS_RGBA16F,
DDS_R32F,
DDS_RG32F,
DDS_RGB32F,
DDS_RGBA32F,
DDS_RGB9E5,
DDS_RGB8,
DDS_RGBA8,
DDS_RGBX8,
DDS_BGR8,
DDS_BGRA8,
DDS_BGRX8,
DDS_BGR5A1,
DDS_BGR565,
DDS_B2GR3,
DDS_B2GR3A8,
DDS_BGR10A2,
DDS_RGB10A2,
DDS_BGRA4,
DDS_LUMINANCE,
DDS_LUMINANCE_ALPHA,
DDS_LUMINANCE_ALPHA_4,
DDS_MAX
};
enum DDSType {
DDST_2D = 1,
DDST_CUBEMAP,
DDST_3D,
DDST_TYPE_MASK = 0x7F,
DDST_ARRAY = 0x80,
};
struct DDSFormatInfo {
const char *name = nullptr;
bool compressed = false;
uint32_t divisor = 0;
uint32_t block_size = 0;
Image::Format format = Image::Format::FORMAT_BPTC_RGBA;
};
static const DDSFormatInfo dds_format_info[DDS_MAX] = {
{ "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 },
{ "DXT2/DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
{ "DXT4/DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
{ "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R },
{ "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG },
{ "BC6UF", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
{ "BC6SF", true, 4, 16, Image::FORMAT_BPTC_RGBF },
{ "BC7", true, 4, 16, Image::FORMAT_BPTC_RGBA },
{ "R16F", false, 1, 2, Image::FORMAT_RH },
{ "RG16F", false, 1, 4, Image::FORMAT_RGH },
{ "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH },
{ "R32F", false, 1, 4, Image::FORMAT_RF },
{ "RG32F", false, 1, 8, Image::FORMAT_RGF },
{ "RGB32F", false, 1, 12, Image::FORMAT_RGBF },
{ "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF },
{ "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 },
{ "RGB8", false, 1, 3, Image::FORMAT_RGB8 },
{ "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
{ "RGBX8", false, 1, 4, Image::FORMAT_RGB8 },
{ "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
{ "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
{ "BGRX8", false, 1, 4, Image::FORMAT_RGB8 },
{ "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 },
{ "BGR565", false, 1, 2, Image::FORMAT_RGB565 },
{ "B2GR3", false, 1, 1, Image::FORMAT_RGB8 },
{ "B2GR3A8", false, 1, 2, Image::FORMAT_RGBA8 },
{ "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 },
{ "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 },
{ "BGRA4", false, 1, 2, Image::FORMAT_RGBA4444 },
{ "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 },
{ "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 },
{ "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 },
};

View File

@@ -0,0 +1,470 @@
/**************************************************************************/
/* image_saver_dds.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_saver_dds.h"
#include "dds_enums.h"
#include "core/io/file_access.h"
#include "core/io/stream_peer.h"
Error save_dds(const String &p_path, const Ref<Image> &p_img) {
Vector<uint8_t> buffer = save_dds_buffer(p_img);
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);
if (file.is_null()) {
return ERR_CANT_CREATE;
}
file->store_buffer(buffer.ptr(), buffer.size());
return OK;
}
enum DDSFormatType {
DDFT_BITMASK,
DDFT_FOURCC,
DDFT_DXGI,
};
DDSFormatType _dds_format_get_type(DDSFormat p_format) {
switch (p_format) {
case DDS_DXT1:
case DDS_DXT3:
case DDS_DXT5:
case DDS_ATI1:
case DDS_ATI2:
case DDS_R16F:
case DDS_RG16F:
case DDS_RGBA16F:
case DDS_R32F:
case DDS_RG32F:
case DDS_RGBA32F:
return DDFT_FOURCC;
case DDS_BC6S:
case DDS_BC6U:
case DDS_BC7:
case DDS_RGB9E5:
case DDS_RGB32F:
return DDFT_DXGI;
default:
return DDFT_BITMASK;
}
}
DDSFormat _image_format_to_dds_format(Image::Format p_image_format) {
switch (p_image_format) {
case Image::FORMAT_RGBAF: {
return DDS_RGBA32F;
}
case Image::FORMAT_RGBF: {
return DDS_RGB32F;
}
case Image::FORMAT_RGBAH: {
return DDS_RGBA16F;
}
case Image::FORMAT_RGF: {
return DDS_RG32F;
}
case Image::FORMAT_RGBA8: {
return DDS_RGBA8;
}
case Image::FORMAT_RGH: {
return DDS_RG16F;
}
case Image::FORMAT_RF: {
return DDS_R32F;
}
case Image::FORMAT_L8:
case Image::FORMAT_R8: {
return DDS_LUMINANCE;
}
case Image::FORMAT_RH: {
return DDS_R16F;
}
case Image::FORMAT_LA8:
case Image::FORMAT_RG8: {
return DDS_LUMINANCE_ALPHA;
}
case Image::FORMAT_RGBA4444: {
return DDS_BGRA4;
}
case Image::FORMAT_RGB565: {
return DDS_BGR565;
}
case Image::FORMAT_RGBE9995: {
return DDS_RGB9E5;
}
case Image::FORMAT_DXT1: {
return DDS_DXT1;
}
case Image::FORMAT_DXT3: {
return DDS_DXT3;
}
case Image::FORMAT_DXT5: {
return DDS_DXT5;
}
case Image::FORMAT_RGTC_R: {
return DDS_ATI1;
}
case Image::FORMAT_RGTC_RG: {
return DDS_ATI2;
}
case Image::FORMAT_RGB8: {
return DDS_RGB8;
}
case Image::FORMAT_BPTC_RGBFU: {
return DDS_BC6U;
}
case Image::FORMAT_BPTC_RGBF: {
return DDS_BC6S;
}
case Image::FORMAT_BPTC_RGBA: {
return DDS_BC7;
}
default: {
return DDS_MAX;
}
}
}
uint32_t _image_format_to_fourcc_format(Image::Format p_format) {
switch (p_format) {
case Image::FORMAT_DXT1:
return DDFCC_DXT1;
case Image::FORMAT_DXT3:
return DDFCC_DXT3;
case Image::FORMAT_DXT5:
return DDFCC_DXT5;
case Image::FORMAT_RGTC_R:
return DDFCC_ATI1;
case Image::FORMAT_RGTC_RG:
return DDFCC_ATI2;
case Image::FORMAT_RF:
return DDFCC_R32F;
case Image::FORMAT_RGF:
return DDFCC_RG32F;
case Image::FORMAT_RGBAF:
return DDFCC_RGBA32F;
case Image::FORMAT_RH:
return DDFCC_R16F;
case Image::FORMAT_RGH:
return DDFCC_RG16F;
case Image::FORMAT_RGBAH:
return DDFCC_RGBA16F;
default:
return 0;
}
}
uint32_t _image_format_to_dxgi_format(Image::Format p_format) {
switch (p_format) {
case Image::FORMAT_DXT1:
return DXGI_BC1_UNORM;
case Image::FORMAT_DXT3:
return DXGI_BC2_UNORM;
case Image::FORMAT_DXT5:
return DXGI_BC3_UNORM;
case Image::FORMAT_RGTC_R:
return DXGI_BC4_UNORM;
case Image::FORMAT_RGTC_RG:
return DXGI_BC5_UNORM;
case Image::FORMAT_BPTC_RGBFU:
return DXGI_BC6H_UF16;
case Image::FORMAT_BPTC_RGBF:
return DXGI_BC6H_SF16;
case Image::FORMAT_BPTC_RGBA:
return DXGI_BC7_UNORM;
case Image::FORMAT_RF:
return DXGI_R32_FLOAT;
case Image::FORMAT_RGF:
return DXGI_R32G32_FLOAT;
case Image::FORMAT_RGBF:
return DXGI_R32G32B32_FLOAT;
case Image::FORMAT_RGBAF:
return DXGI_R32G32B32A32_FLOAT;
case Image::FORMAT_RH:
return DXGI_R16_FLOAT;
case Image::FORMAT_RGH:
return DXGI_R16G16_FLOAT;
case Image::FORMAT_RGBAH:
return DXGI_R16G16B16A16_FLOAT;
case Image::FORMAT_RGBE9995:
return DXGI_R9G9B9E5;
default:
return 0;
}
}
void _get_dds_pixel_bitmask(Image::Format p_format, uint32_t &r_bit_count, uint32_t &r_red_mask, uint32_t &r_green_mask, uint32_t &r_blue_mask, uint32_t &r_alpha_mask) {
switch (p_format) {
case Image::FORMAT_R8:
case Image::FORMAT_L8: {
r_bit_count = 8;
r_red_mask = 0xff;
r_green_mask = 0;
r_blue_mask = 0;
r_alpha_mask = 0;
} break;
case Image::FORMAT_RG8:
case Image::FORMAT_LA8: {
r_bit_count = 16;
r_red_mask = 0xff;
r_green_mask = 0;
r_blue_mask = 0;
r_alpha_mask = 0xff00;
} break;
case Image::FORMAT_RGB8: {
// BGR8
r_bit_count = 24;
r_red_mask = 0xff0000;
r_green_mask = 0xff00;
r_blue_mask = 0xff;
r_alpha_mask = 0;
} break;
case Image::FORMAT_RGBA8: {
r_bit_count = 32;
r_red_mask = 0xff;
r_green_mask = 0xff00;
r_blue_mask = 0xff0000;
r_alpha_mask = 0xff000000;
} break;
case Image::FORMAT_RGBA4444: {
// BGRA4444
r_bit_count = 16;
r_red_mask = 0xf00;
r_green_mask = 0xf0;
r_blue_mask = 0xf;
r_alpha_mask = 0xf000;
} break;
case Image::FORMAT_RGB565: {
// BGR565
r_bit_count = 16;
r_red_mask = 0xf800;
r_green_mask = 0x7e0;
r_blue_mask = 0x1f;
r_alpha_mask = 0;
} break;
default: {
r_bit_count = 0;
r_red_mask = 0;
r_green_mask = 0;
r_blue_mask = 0;
r_alpha_mask = 0;
} break;
}
}
Vector<uint8_t> save_dds_buffer(const Ref<Image> &p_img) {
Ref<StreamPeerBuffer> stream_buffer;
stream_buffer.instantiate();
Ref<Image> image = p_img;
stream_buffer->put_32(DDS_MAGIC);
stream_buffer->put_32(DDS_HEADER_SIZE);
uint32_t flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_PITCH | DDSD_LINEARSIZE;
if (image->has_mipmaps()) {
flags |= DDSD_MIPMAPCOUNT;
}
stream_buffer->put_32(flags);
uint32_t height = image->get_height();
stream_buffer->put_32(height);
uint32_t width = image->get_width();
stream_buffer->put_32(width);
DDSFormat dds_format = _image_format_to_dds_format(image->get_format());
const DDSFormatInfo &info = dds_format_info[dds_format];
uint32_t depth = 1; // Default depth for 2D textures
uint32_t pitch;
if (info.compressed) {
pitch = ((MAX(info.divisor, width) + info.divisor - 1) / info.divisor) * ((MAX(info.divisor, height) + info.divisor - 1) / info.divisor) * info.block_size;
} else {
pitch = width * info.block_size;
}
stream_buffer->put_32(pitch);
stream_buffer->put_32(depth);
uint32_t mipmaps = image->get_mipmap_count() + 1;
stream_buffer->put_32(mipmaps);
uint32_t reserved = 0;
for (int i = 0; i < 11; i++) {
stream_buffer->put_32(reserved);
}
stream_buffer->put_32(DDS_PIXELFORMAT_SIZE);
uint32_t pf_flags = 0;
DDSFormatType format_type = _dds_format_get_type(dds_format);
if (format_type == DDFT_BITMASK) {
pf_flags = DDPF_RGB;
if (image->get_format() == Image::FORMAT_LA8 || image->get_format() == Image::FORMAT_RG8 || image->get_format() == Image::FORMAT_RGBA8 || image->get_format() == Image::FORMAT_RGBA4444) {
pf_flags |= DDPF_ALPHAPIXELS;
}
} else {
pf_flags = DDPF_FOURCC;
}
stream_buffer->put_32(pf_flags);
bool needs_pixeldata_swap = false;
if (format_type == DDFT_BITMASK) {
// Uncompressed bitmasked.
stream_buffer->put_32(0); // FourCC
uint32_t bit_count, r_mask, g_mask, b_mask, a_mask;
_get_dds_pixel_bitmask(image->get_format(), bit_count, r_mask, g_mask, b_mask, a_mask);
stream_buffer->put_32(bit_count);
stream_buffer->put_32(r_mask);
stream_buffer->put_32(g_mask);
stream_buffer->put_32(b_mask);
stream_buffer->put_32(a_mask);
if (image->get_format() == Image::FORMAT_RGBA4444 || image->get_format() == Image::FORMAT_RGB8) {
needs_pixeldata_swap = true;
}
} else if (format_type == DDFT_FOURCC) {
// FourCC.
uint32_t fourcc = _image_format_to_fourcc_format(image->get_format());
stream_buffer->put_32(fourcc);
stream_buffer->put_32(0); // Bit count
stream_buffer->put_32(0); // R Bitmask
stream_buffer->put_32(0); // G Bitmask
stream_buffer->put_32(0); // B Bitmask
stream_buffer->put_32(0); // A Bitmask
} else {
// DXGI format and DX10 header.
stream_buffer->put_32(DDFCC_DX10);
stream_buffer->put_32(0); // Bit count
stream_buffer->put_32(0); // R Bitmask
stream_buffer->put_32(0); // G Bitmask
stream_buffer->put_32(0); // B Bitmask
stream_buffer->put_32(0); // A Bitmask
}
uint32_t caps1 = info.compressed ? DDSD_LINEARSIZE : DDSD_PITCH;
stream_buffer->put_32(caps1);
stream_buffer->put_32(0); // Caps2
stream_buffer->put_32(0); // Caps3
stream_buffer->put_32(0); // Caps4
stream_buffer->put_32(0); // Reserved 2
if (format_type == DDFT_DXGI) {
// DX10 header.
uint32_t dxgi_format = _image_format_to_dxgi_format(image->get_format());
stream_buffer->put_32(dxgi_format);
stream_buffer->put_32(DX10D_2D);
stream_buffer->put_32(0); // Misc flags 1
stream_buffer->put_32(1); // Array size
stream_buffer->put_32(0); // Misc flags 2
}
for (uint32_t mip_i = 0; mip_i < mipmaps; mip_i++) {
uint32_t mip_width = MAX(1u, width >> mip_i);
uint32_t mip_height = MAX(1u, height >> mip_i);
uint32_t expected_size = 0;
if (info.compressed) {
uint32_t blocks_x = (mip_width + info.divisor - 1) / info.divisor;
uint32_t blocks_y = (mip_height + info.divisor - 1) / info.divisor;
expected_size = blocks_x * blocks_y * info.block_size;
} else {
expected_size = mip_width * mip_height * info.block_size;
}
if (needs_pixeldata_swap) {
// The image's channels need to be swapped.
Ref<Image> mip_image = image->get_image_from_mipmap(mip_i);
Vector<uint8_t> data = mip_image->get_data();
ERR_FAIL_COND_V_MSG(data.size() != expected_size, Vector<uint8_t>(),
"Image data size mismatch for mipmap level " + itos(mip_i) +
". Expected size: " + itos(expected_size) + ", actual size: " + itos(data.size()) + ".");
if (mip_image->get_format() == Image::FORMAT_RGBA4444) {
// RGBA4 to BGRA4
const int64_t data_size = data.size();
uint8_t *wb = data.ptrw();
for (int64_t data_i = 0; data_i < data_size; data_i += 2) {
uint8_t ar = wb[data_i + 0];
uint8_t gb = wb[data_i + 1];
wb[data_i + 1] = ((ar & 0x0F) << 4) | ((gb & 0xF0) >> 4);
wb[data_i + 0] = ((ar & 0xF0) >> 4) | ((gb & 0x0F) << 4);
}
} else if (mip_image->get_format() == Image::FORMAT_RGB8) {
// RGB8 to BGR8
const int64_t data_size = data.size();
uint8_t *wb = data.ptrw();
for (int64_t data_i = 0; data_i < data_size; data_i += 3) {
SWAP(wb[data_i], wb[data_i + 2]);
}
}
stream_buffer->put_data(data.ptr(), data.size());
} else {
int64_t ofs, size;
image->get_mipmap_offset_and_size(mip_i, ofs, size);
ERR_FAIL_COND_V_MSG(size != expected_size, Vector<uint8_t>(),
"Image data size mismatch for mipmap level " + itos(mip_i) +
". Expected size: " + itos(expected_size) + ", actual size: " + itos(size) + ".");
stream_buffer->put_data(image->ptr() + ofs, size);
}
}
return stream_buffer->get_data_array();
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* image_saver_dds.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image.h"
Error save_dds(const String &p_path, const Ref<Image> &p_img);
Vector<uint8_t> save_dds_buffer(const Ref<Image> &p_img);

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "image_saver_dds.h"
#include "texture_loader_dds.h"
#include "scene/resources/texture.h"
static Ref<ResourceFormatDDS> resource_loader_dds;
void initialize_dds_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
Image::save_dds_func = save_dds;
Image::save_dds_buffer_func = save_dds_buffer;
if (GD_IS_CLASS_ENABLED(Texture)) {
resource_loader_dds.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_dds);
}
}
void uninitialize_dds_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
if (GD_IS_CLASS_ENABLED(Texture)) {
ResourceLoader::remove_resource_format_loader(resource_loader_dds);
resource_loader_dds.unref();
}
Image::save_dds_func = nullptr;
Image::save_dds_buffer_func = nullptr;
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_dds_module(ModuleInitializationLevel p_level);
void uninitialize_dds_module(ModuleInitializationLevel p_level);

View File

@@ -0,0 +1,161 @@
/**************************************************************************/
/* test_dds.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "../image_saver_dds.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/image.h"
#include "tests/core/config/test_project_settings.h"
#include "tests/test_macros.h"
#include "tests/test_utils.h"
namespace TestDDS {
String init(const String &p_test, const String &p_copy_target = String()) {
String old_resource_path = TestProjectSettingsInternalsAccessor::resource_path();
Error err;
// Setup project settings since it's needed for the import process.
String project_folder = TestUtils::get_temp_path(p_test.get_file().get_basename());
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
da->make_dir_recursive(project_folder.path_join(".godot").path_join("imported"));
// Initialize res:// to `project_folder`.
TestProjectSettingsInternalsAccessor::resource_path() = project_folder;
err = ProjectSettings::get_singleton()->setup(project_folder, String(), true);
if (p_copy_target.is_empty()) {
return old_resource_path;
}
// Copy all the necessary test data files to the res:// directory.
da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String test_data = String("tests/data").path_join(p_test);
da = DirAccess::open(test_data);
CHECK_MESSAGE(da.is_valid(), "Unable to open folder.");
da->list_dir_begin();
for (String item = da->get_next(); !item.is_empty(); item = da->get_next()) {
if (!FileAccess::exists(test_data.path_join(item))) {
continue;
}
Ref<FileAccess> output = FileAccess::open(p_copy_target.path_join(item), FileAccess::WRITE, &err);
CHECK_MESSAGE(err == OK, "Unable to open output file.");
output->store_buffer(FileAccess::get_file_as_bytes(test_data.path_join(item)));
output->close();
}
da->list_dir_end();
return old_resource_path;
}
TEST_CASE("[SceneTree][DDSSaver] Save DDS - Save valid image with mipmap" * doctest::skip(true)) {
String old_resource_path = init("save_dds_valid_image_with_mipmap");
Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
image->fill(Color(1, 0, 0)); // Fill with red color
image->generate_mipmaps();
image->compress_from_channels(Image::COMPRESS_S3TC, Image::USED_CHANNELS_RGBA);
Error err = save_dds("res://valid_image_with_mipmap.dds", image);
CHECK(err == OK);
Ref<Image> loaded_image;
loaded_image.instantiate();
Vector<uint8_t> buffer = FileAccess::get_file_as_bytes("res://valid_image_with_mipmap.dds", &err);
CHECK(err == OK);
err = loaded_image->load_dds_from_buffer(buffer);
CHECK(err == OK);
Dictionary metrics = image->compute_image_metrics(loaded_image, false);
CHECK(metrics.size() > 0);
CHECK_MESSAGE(metrics.has("root_mean_squared"), "Metrics dictionary contains 'root_mean_squared'.");
float rms = metrics["root_mean_squared"];
CHECK(rms == 0.0f);
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
}
TEST_CASE("[SceneTree][DDSSaver] Save DDS - Save valid image with BPTC and S3TC compression" * doctest::skip(true)) {
String old_resource_path = init("save_dds_valid_image_bptc_s3tc");
Ref<Image> image_bptc = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
image_bptc->fill(Color(0, 0, 1)); // Fill with blue color
image_bptc->compress_from_channels(Image::COMPRESS_BPTC, Image::USED_CHANNELS_RGBA);
Error err_bptc = image_bptc->save_dds("res://valid_image_bptc.dds");
CHECK(err_bptc == OK);
Ref<Image> image_s3tc = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
image_s3tc->fill(Color(1, 1, 1)); // Fill with white color
image_s3tc->compress_from_channels(Image::COMPRESS_S3TC, Image::USED_CHANNELS_RGBA);
Error err_s3tc = image_s3tc->save_dds("res://valid_image_s3tc_combined.dds");
CHECK(err_s3tc == OK);
// Validate BPTC image
Ref<Image> loaded_image_bptc;
loaded_image_bptc.instantiate();
Vector<uint8_t> buffer_bptc = FileAccess::get_file_as_bytes("res://valid_image_bptc.dds", &err_bptc);
CHECK(err_bptc == OK);
err_bptc = loaded_image_bptc->load_dds_from_buffer(buffer_bptc);
CHECK(err_bptc == OK);
Dictionary metrics_bptc = image_bptc->compute_image_metrics(loaded_image_bptc, false);
CHECK(metrics_bptc.size() > 0);
CHECK_MESSAGE(metrics_bptc.has("root_mean_squared"), "Metrics dictionary contains 'root_mean_squared' for BPTC.");
float rms_bptc = metrics_bptc["root_mean_squared"];
CHECK(rms_bptc == 0.0f);
// Validate S3TC image
Ref<Image> loaded_image_s3tc;
loaded_image_s3tc.instantiate();
Vector<uint8_t> buffer_s3tc = FileAccess::get_file_as_bytes("res://valid_image_s3tc_combined.dds", &err_s3tc);
CHECK(err_s3tc == OK);
err_s3tc = loaded_image_s3tc->load_dds_from_buffer(buffer_s3tc);
CHECK(err_s3tc == OK);
Dictionary metrics_s3tc = image_s3tc->compute_image_metrics(loaded_image_s3tc, false);
CHECK(metrics_s3tc.size() > 0);
CHECK_MESSAGE(metrics_s3tc.has("root_mean_squared"), "Metrics dictionary contains 'root_mean_squared' for S3TC.");
float rms_s3tc = metrics_s3tc["root_mean_squared"];
CHECK(rms_s3tc == 0.0f);
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
}
TEST_CASE("[SceneTree][DDSSaver] Save DDS - Save valid uncompressed image") {
String old_resource_path = init("save_dds_valid_uncompressed");
Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
image->fill(Color(0, 0, 1)); // Fill with blue color
Error err = image->save_dds("res://valid_image_uncompressed.dds");
CHECK(err == OK);
Vector<uint8_t> buffer = FileAccess::get_file_as_bytes("res://valid_image_uncompressed.dds", &err);
CHECK(err == OK);
Ref<Image> loaded_image;
loaded_image.instantiate();
err = loaded_image->load_dds_from_buffer(buffer);
CHECK(err == OK);
Dictionary metrics = image->compute_image_metrics(loaded_image, false);
CHECK(metrics.size() > 0);
CHECK_MESSAGE(metrics.has("root_mean_squared"), "Metrics dictionary contains 'root_mean_squared' for uncompressed.");
float rms = metrics["root_mean_squared"];
CHECK(rms == 0.0f);
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
}
} //namespace TestDDS

View File

@@ -0,0 +1,736 @@
/**************************************************************************/
/* texture_loader_dds.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "texture_loader_dds.h"
#include "dds_enums.h"
#include "core/io/file_access.h"
#include "core/io/file_access_memory.h"
#include "scene/resources/image_texture.h"
DDSFormat _dxgi_to_dds_format(uint32_t p_dxgi_format) {
switch (p_dxgi_format) {
case DXGI_R32G32B32A32_FLOAT: {
return DDS_RGBA32F;
}
case DXGI_R32G32B32_FLOAT: {
return DDS_RGB32F;
}
case DXGI_R16G16B16A16_FLOAT: {
return DDS_RGBA16F;
}
case DXGI_R32G32_FLOAT: {
return DDS_RG32F;
}
case DXGI_R10G10B10A2_UNORM: {
return DDS_RGB10A2;
}
case DXGI_R8G8B8A8_UNORM:
case DXGI_R8G8B8A8_UNORM_SRGB: {
return DDS_RGBA8;
}
case DXGI_R16G16_FLOAT: {
return DDS_RG16F;
}
case DXGI_R32_FLOAT: {
return DDS_R32F;
}
case DXGI_R8_UNORM:
case DXGI_A8_UNORM: {
return DDS_LUMINANCE;
}
case DXGI_R16_FLOAT: {
return DDS_R16F;
}
case DXGI_R8G8_UNORM: {
return DDS_LUMINANCE_ALPHA;
}
case DXGI_R9G9B9E5: {
return DDS_RGB9E5;
}
case DXGI_BC1_UNORM:
case DXGI_BC1_UNORM_SRGB: {
return DDS_DXT1;
}
case DXGI_BC2_UNORM:
case DXGI_BC2_UNORM_SRGB: {
return DDS_DXT3;
}
case DXGI_BC3_UNORM:
case DXGI_BC3_UNORM_SRGB: {
return DDS_DXT5;
}
case DXGI_BC4_UNORM: {
return DDS_ATI1;
}
case DXGI_BC5_UNORM: {
return DDS_ATI2;
}
case DXGI_B5G6R5_UNORM: {
return DDS_BGR565;
}
case DXGI_B5G5R5A1_UNORM: {
return DDS_BGR5A1;
}
case DXGI_B8G8R8A8_UNORM: {
return DDS_BGRA8;
}
case DXGI_BC6H_UF16: {
return DDS_BC6U;
}
case DXGI_BC6H_SF16: {
return DDS_BC6S;
}
case DXGI_BC7_UNORM:
case DXGI_BC7_UNORM_SRGB: {
return DDS_BC7;
}
case DXGI_B4G4R4A4_UNORM: {
return DDS_BGRA4;
}
default: {
return DDS_MAX;
}
}
}
static Ref<Image> _dds_load_layer(Ref<FileAccess> p_file, DDSFormat p_dds_format, uint32_t p_width, uint32_t p_height, uint32_t p_mipmaps, uint32_t p_pitch, uint32_t p_flags, Vector<uint8_t> &r_src_data) {
const DDSFormatInfo &info = dds_format_info[p_dds_format];
uint32_t w = p_width;
uint32_t h = p_height;
if (info.compressed) {
// BC compressed.
w += w % info.divisor;
h += h % info.divisor;
if (w != p_width) {
WARN_PRINT(vformat("%s: DDS width '%d' is not divisible by %d. This is not allowed as per the DDS specification, attempting to load anyway.", p_file->get_path(), p_width, info.divisor));
}
if (h != p_height) {
WARN_PRINT(vformat("%s: DDS height '%d' is not divisible by %d. This is not allowed as per the DDS specification, attempting to load anyway.", p_file->get_path(), p_height, info.divisor));
}
uint32_t size = MAX(1u, (w + 3) / 4) * MAX(1u, (h + 3) / 4) * info.block_size;
if (p_flags & DDSD_LINEARSIZE) {
ERR_FAIL_COND_V_MSG(size != p_pitch, Ref<Resource>(), "DDS header flags specify that a linear size of the top-level image is present, but the specified size does not match the expected value.");
} else {
ERR_FAIL_COND_V_MSG(p_pitch != 0, Ref<Resource>(), "DDS header flags specify that no linear size will given for the top-level image, but a non-zero linear size value is present in the header.");
}
for (uint32_t i = 1; i < p_mipmaps; i++) {
w = MAX(1u, w >> 1);
h = MAX(1u, h >> 1);
uint32_t bsize = MAX(1u, (w + 3) / 4) * MAX(1u, (h + 3) / 4) * info.block_size;
size += bsize;
}
r_src_data.resize(size);
uint8_t *wb = r_src_data.ptrw();
p_file->get_buffer(wb, size);
} else {
// Generic uncompressed.
uint32_t size = p_width * p_height * info.block_size;
for (uint32_t i = 1; i < p_mipmaps; i++) {
w = MAX(1u, w >> 1);
h = MAX(1u, h >> 1);
size += w * h * info.block_size;
}
// Calculate the space these formats will take up after decoding.
switch (p_dds_format) {
case DDS_BGR5A1:
case DDS_B2GR3A8:
case DDS_LUMINANCE_ALPHA_4:
size = size * 2;
break;
case DDS_B2GR3:
size = size * 3;
break;
default:
break;
}
r_src_data.resize(size);
uint8_t *wb = r_src_data.ptrw();
p_file->get_buffer(wb, size);
switch (p_dds_format) {
case DDS_BGR5A1: {
// To RGBA8.
int colcount = size / 4;
for (int i = colcount - 1; i >= 0; i--) {
int src_ofs = i * 2;
int dst_ofs = i * 4;
uint8_t a = wb[src_ofs + 1] & 0x80;
uint8_t b = wb[src_ofs] & 0x1F;
uint8_t g = (wb[src_ofs] >> 5) | ((wb[src_ofs + 1] & 0x3) << 3);
uint8_t r = (wb[src_ofs + 1] >> 2) & 0x1F;
wb[dst_ofs + 0] = r << 3;
wb[dst_ofs + 1] = g << 3;
wb[dst_ofs + 2] = b << 3;
wb[dst_ofs + 3] = a ? 255 : 0;
}
} break;
case DDS_BGRA4: {
// To RGBA4.
for (uint32_t i = 0; i < size; i += 2) {
uint8_t ar = wb[i + 0];
uint8_t gb = wb[i + 1];
wb[i + 0] = ((ar & 0x0F) << 4) | ((gb & 0xF0) >> 4);
wb[i + 1] = ((ar & 0xF0) >> 4) | ((gb & 0x0F) << 4);
}
} break;
case DDS_B2GR3: {
// To RGB8.
int colcount = size / 3;
for (int i = colcount - 1; i >= 0; i--) {
int src_ofs = i;
int dst_ofs = i * 3;
uint8_t b = (wb[src_ofs] & 0x3) << 6;
uint8_t g = (wb[src_ofs] & 0x1C) << 3;
uint8_t r = (wb[src_ofs] & 0xE0);
wb[dst_ofs] = r;
wb[dst_ofs + 1] = g;
wb[dst_ofs + 2] = b;
}
} break;
case DDS_B2GR3A8: {
// To RGBA8.
int colcount = size / 4;
for (int i = colcount - 1; i >= 0; i--) {
int src_ofs = i * 2;
int dst_ofs = i * 4;
uint8_t b = (wb[src_ofs] & 0x3) << 6;
uint8_t g = (wb[src_ofs] & 0x1C) << 3;
uint8_t r = (wb[src_ofs] & 0xE0);
uint8_t a = wb[src_ofs + 1];
wb[dst_ofs] = r;
wb[dst_ofs + 1] = g;
wb[dst_ofs + 2] = b;
wb[dst_ofs + 3] = a;
}
} break;
case DDS_RGB10A2: {
// To RGBA8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
int ofs = i * 4;
uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
// This method follows the 'standard' way of decoding 10-bit dds files,
// which means the ones created with DirectXTex will be loaded incorrectly.
uint8_t a = (w32 & 0xc0000000) >> 24;
uint8_t r = (w32 & 0x3ff) >> 2;
uint8_t g = (w32 & 0xffc00) >> 12;
uint8_t b = (w32 & 0x3ff00000) >> 22;
wb[ofs + 0] = r;
wb[ofs + 1] = g;
wb[ofs + 2] = b;
wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
}
} break;
case DDS_BGR10A2: {
// To RGBA8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
int ofs = i * 4;
uint32_t w32 = uint32_t(wb[ofs + 0]) | (uint32_t(wb[ofs + 1]) << 8) | (uint32_t(wb[ofs + 2]) << 16) | (uint32_t(wb[ofs + 3]) << 24);
// This method follows the 'standard' way of decoding 10-bit dds files,
// which means the ones created with DirectXTex will be loaded incorrectly.
uint8_t a = (w32 & 0xc0000000) >> 24;
uint8_t r = (w32 & 0x3ff00000) >> 22;
uint8_t g = (w32 & 0xffc00) >> 12;
uint8_t b = (w32 & 0x3ff) >> 2;
wb[ofs + 0] = r;
wb[ofs + 1] = g;
wb[ofs + 2] = b;
wb[ofs + 3] = a == 0xc0 ? 255 : a; // 0xc0 should be opaque.
}
} break;
// Channel-swapped.
case DDS_BGRA8: {
// To RGBA8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
SWAP(wb[i * 4 + 0], wb[i * 4 + 2]);
}
} break;
case DDS_BGR8: {
// To RGB8.
int colcount = size / 3;
for (int i = 0; i < colcount; i++) {
SWAP(wb[i * 3 + 0], wb[i * 3 + 2]);
}
} break;
case DDS_RGBX8: {
// To RGB8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
int src_ofs = i * 4;
int dst_ofs = i * 3;
wb[dst_ofs + 0] = wb[src_ofs + 0];
wb[dst_ofs + 1] = wb[src_ofs + 1];
wb[dst_ofs + 2] = wb[src_ofs + 2];
}
r_src_data.resize(size * 3 / 4);
} break;
case DDS_BGRX8: {
// To RGB8.
int colcount = size / 4;
for (int i = 0; i < colcount; i++) {
int src_ofs = i * 4;
int dst_ofs = i * 3;
wb[dst_ofs + 0] = wb[src_ofs + 2];
wb[dst_ofs + 1] = wb[src_ofs + 1];
wb[dst_ofs + 2] = wb[src_ofs + 0];
}
r_src_data.resize(size * 3 / 4);
} break;
// Grayscale.
case DDS_LUMINANCE_ALPHA_4: {
// To LA8.
int colcount = size / 2;
for (int i = colcount - 1; i >= 0; i--) {
int src_ofs = i;
int dst_ofs = i * 2;
uint8_t l = wb[src_ofs] & 0x0F;
uint8_t a = wb[src_ofs] & 0xF0;
wb[dst_ofs] = (l << 4) | l;
wb[dst_ofs + 1] = a | (a >> 4);
}
} break;
default: {
}
}
}
return memnew(Image(p_width, p_height, p_mipmaps > 1, info.format, r_src_data));
}
static Vector<Ref<Image>> _dds_load_images(Ref<FileAccess> p_f, DDSFormat p_dds_format, uint32_t p_width, uint32_t p_height, uint32_t p_mipmaps, uint32_t p_pitch, uint32_t p_flags, uint32_t p_layer_count) {
Vector<uint8_t> src_data;
Vector<Ref<Image>> images;
images.resize(p_layer_count);
for (uint32_t i = 0; i < p_layer_count; i++) {
images.write[i] = _dds_load_layer(p_f, p_dds_format, p_width, p_height, p_mipmaps, p_pitch, p_flags, src_data);
ERR_FAIL_COND_V(images.write[i].is_null(), Vector<Ref<Image>>());
}
return images;
}
static Ref<Resource> _dds_create_texture(const Vector<Ref<Image>> &p_images, uint32_t p_dds_type, uint32_t p_width, uint32_t p_height, uint32_t p_layer_count, uint32_t p_mipmaps, Error *r_error) {
ERR_FAIL_COND_V(p_images.is_empty(), Ref<Resource>());
if ((p_dds_type & DDST_TYPE_MASK) == DDST_2D) {
if (p_dds_type & DDST_ARRAY) {
Ref<Texture2DArray> texture;
texture.instantiate();
texture->create_from_images(p_images);
if (r_error) {
*r_error = OK;
}
return texture;
} else {
if (r_error) {
*r_error = OK;
}
return ImageTexture::create_from_image(p_images[0]);
}
} else if ((p_dds_type & DDST_TYPE_MASK) == DDST_CUBEMAP) {
ERR_FAIL_COND_V(p_layer_count % 6 != 0, Ref<Resource>());
if (p_dds_type & DDST_ARRAY) {
Ref<CubemapArray> texture;
texture.instantiate();
texture->create_from_images(p_images);
if (r_error) {
*r_error = OK;
}
return texture;
} else {
Ref<Cubemap> texture;
texture.instantiate();
texture->create_from_images(p_images);
if (r_error) {
*r_error = OK;
}
return texture;
}
} else if ((p_dds_type & DDST_TYPE_MASK) == DDST_3D) {
Ref<ImageTexture3D> texture;
texture.instantiate();
texture->create(p_images[0]->get_format(), p_width, p_height, p_layer_count, p_mipmaps > 1, p_images);
if (r_error) {
*r_error = OK;
}
return texture;
}
return Ref<Resource>();
}
static Ref<Resource> _dds_create_texture_from_images(const Vector<Ref<Image>> &p_images, DDSFormat p_dds_format, uint32_t p_width, uint32_t p_height, uint32_t p_mipmaps, uint32_t p_pitch, uint32_t p_flags, uint32_t p_layer_count, uint32_t p_dds_type, Error *r_error) {
return _dds_create_texture(p_images, p_dds_type, p_width, p_height, p_layer_count, p_mipmaps, r_error);
}
static Vector<Ref<Image>> _dds_load_images_from_buffer(Ref<FileAccess> p_f, DDSFormat &r_dds_format, uint32_t &r_width, uint32_t &r_height, uint32_t &r_mipmaps, uint32_t &r_pitch, uint32_t &r_flags, uint32_t &r_layer_count, uint32_t &r_dds_type, const String &p_path = "") {
ERR_FAIL_COND_V_MSG(p_f.is_null(), Vector<Ref<Image>>(), vformat("Empty DDS texture file."));
ERR_FAIL_COND_V_MSG(!p_f->get_length(), Vector<Ref<Image>>(), vformat("Empty DDS texture file."));
uint32_t magic = p_f->get_32();
uint32_t hsize = p_f->get_32();
r_flags = p_f->get_32();
r_height = p_f->get_32();
r_width = p_f->get_32();
r_pitch = p_f->get_32();
uint32_t depth = p_f->get_32();
r_mipmaps = p_f->get_32();
// Skip reserved.
for (int i = 0; i < 11; i++) {
p_f->get_32();
}
// Validate.
// We don't check DDSD_CAPS or DDSD_PIXELFORMAT, as they're mandatory when writing,
// but non-mandatory when reading (as some writers don't set them).
if (magic != DDS_MAGIC || hsize != 124) {
ERR_FAIL_V_MSG(Vector<Ref<Image>>(), vformat("Invalid or unsupported DDS texture file '%s'.", p_path));
}
/* uint32_t format_size = */ p_f->get_32();
uint32_t format_flags = p_f->get_32();
uint32_t format_fourcc = p_f->get_32();
uint32_t format_rgb_bits = p_f->get_32();
uint32_t format_red_mask = p_f->get_32();
uint32_t format_green_mask = p_f->get_32();
uint32_t format_blue_mask = p_f->get_32();
uint32_t format_alpha_mask = p_f->get_32();
/* uint32_t caps_1 = */ p_f->get_32();
uint32_t caps_2 = p_f->get_32();
/* uint32_t caps_3 = */ p_f->get_32();
/* uint32_t caps_4 = */ p_f->get_32();
// Skip reserved.
p_f->get_32();
if (p_f->get_position() < 128) {
p_f->seek(128);
}
r_layer_count = 1;
r_dds_type = DDST_2D;
if (caps_2 & DDSC2_CUBEMAP) {
r_dds_type = DDST_CUBEMAP;
r_layer_count *= 6;
} else if (caps_2 & DDSC2_VOLUME) {
r_dds_type = DDST_3D;
r_layer_count = depth;
}
r_dds_format = DDS_MAX;
if (format_flags & DDPF_FOURCC) {
// FourCC formats.
switch (format_fourcc) {
case DDFCC_DXT1: {
r_dds_format = DDS_DXT1;
} break;
case DDFCC_DXT2:
case DDFCC_DXT3: {
r_dds_format = DDS_DXT3;
} break;
case DDFCC_DXT4:
case DDFCC_DXT5: {
r_dds_format = DDS_DXT5;
} break;
case DDFCC_ATI1:
case DDFCC_BC4U: {
r_dds_format = DDS_ATI1;
} break;
case DDFCC_ATI2:
case DDFCC_BC5U:
case DDFCC_A2XY: {
r_dds_format = DDS_ATI2;
} break;
case DDFCC_R16F: {
r_dds_format = DDS_R16F;
} break;
case DDFCC_RG16F: {
r_dds_format = DDS_RG16F;
} break;
case DDFCC_RGBA16F: {
r_dds_format = DDS_RGBA16F;
} break;
case DDFCC_R32F: {
r_dds_format = DDS_R32F;
} break;
case DDFCC_RG32F: {
r_dds_format = DDS_RG32F;
} break;
case DDFCC_RGBA32F: {
r_dds_format = DDS_RGBA32F;
} break;
case DDFCC_DX10: {
uint32_t dxgi_format = p_f->get_32();
uint32_t dimension = p_f->get_32();
/* uint32_t misc_flags_1 = */ p_f->get_32();
uint32_t array_size = p_f->get_32();
/* uint32_t misc_flags_2 = */ p_f->get_32();
if (dimension == DX10D_3D) {
r_dds_type = DDST_3D;
r_layer_count = depth;
}
if (array_size > 1) {
r_layer_count *= array_size;
r_dds_type |= DDST_ARRAY;
}
r_dds_format = _dxgi_to_dds_format(dxgi_format);
} break;
default: {
ERR_FAIL_V_MSG(Vector<Ref<Image>>(), vformat("Unrecognized or unsupported FourCC in DDS '%s'.", p_path));
}
}
} else if (format_flags & DDPF_RGB) {
// Channel-bitmasked formats.
if (format_flags & DDPF_ALPHAPIXELS) {
// With alpha.
if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff && format_alpha_mask == 0xff000000) {
r_dds_format = DDS_BGRA8;
} else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000 && format_alpha_mask == 0xff000000) {
r_dds_format = DDS_RGBA8;
} else if (format_rgb_bits == 16 && format_red_mask == 0x00007c00 && format_green_mask == 0x000003e0 && format_blue_mask == 0x0000001f && format_alpha_mask == 0x00008000) {
r_dds_format = DDS_BGR5A1;
} else if (format_rgb_bits == 32 && format_red_mask == 0x3ff00000 && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff && format_alpha_mask == 0xc0000000) {
r_dds_format = DDS_BGR10A2;
} else if (format_rgb_bits == 32 && format_red_mask == 0x3ff && format_green_mask == 0xffc00 && format_blue_mask == 0x3ff00000 && format_alpha_mask == 0xc0000000) {
r_dds_format = DDS_RGB10A2;
} else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) {
r_dds_format = DDS_BGRA4;
} else if (format_rgb_bits == 16 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00) {
r_dds_format = DDS_B2GR3A8;
}
} else {
// Without alpha.
if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
r_dds_format = DDS_BGR8;
} else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
r_dds_format = DDS_RGB8;
} else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
r_dds_format = DDS_BGR565;
} else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) {
r_dds_format = DDS_B2GR3;
} else if (format_rgb_bits == 32 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
r_dds_format = DDS_BGRX8;
} else if (format_rgb_bits == 32 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
r_dds_format = DDS_RGBX8;
}
}
} else {
// Other formats.
if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) {
// Alpha only.
r_dds_format = DDS_LUMINANCE;
}
}
// Depending on the writer, luminance formats may or may not have the DDPF_RGB or DDPF_LUMINANCE flags defined,
// so we check for these formats after everything else failed.
if (r_dds_format == DDS_MAX) {
if (format_flags & DDPF_ALPHAPIXELS) {
// With alpha.
if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
r_dds_format = DDS_LUMINANCE_ALPHA;
} else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) {
r_dds_format = DDS_LUMINANCE_ALPHA_4;
}
} else {
// Without alpha.
if (format_rgb_bits == 8 && format_red_mask == 0xff) {
r_dds_format = DDS_LUMINANCE;
}
}
}
// No format detected, error.
if (r_dds_format == DDS_MAX) {
ERR_FAIL_V_MSG(Vector<Ref<Image>>(), vformat("Unrecognized or unsupported color layout in DDS '%s'.", p_path));
}
if (!(r_flags & DDSD_MIPMAPCOUNT)) {
r_mipmaps = 1;
}
return _dds_load_images(p_f, r_dds_format, r_width, r_height, r_mipmaps, r_pitch, r_flags, r_layer_count);
}
static Ref<Resource> _dds_load_from_buffer(Ref<FileAccess> p_f, Error *r_error, const String &p_path = "") {
if (r_error) {
*r_error = ERR_FILE_CORRUPT;
}
DDSFormat dds_format;
uint32_t width = 0, height = 0, mipmaps = 0, pitch = 0, flags = 0, layer_count = 0, dds_type = 0;
Vector<Ref<Image>> images = _dds_load_images_from_buffer(p_f, dds_format, width, height, mipmaps, pitch, flags, layer_count, dds_type, p_path);
return _dds_create_texture_from_images(images, dds_format, width, height, mipmaps, pitch, flags, layer_count, dds_type, r_error);
}
static Ref<Resource> _dds_load_from_file(const String &p_path, Error *r_error) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
}
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
if (f.is_null()) {
return Ref<Resource>();
}
return _dds_load_from_buffer(f, r_error, p_path);
}
Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
return _dds_load_from_file(p_path, r_error);
}
void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("dds");
}
bool ResourceFormatDDS::handles_type(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "Texture");
}
String ResourceFormatDDS::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "dds") {
return "Texture";
}
return "";
}
Ref<Image> load_mem_dds(const uint8_t *p_dds, int p_size) {
ERR_FAIL_NULL_V(p_dds, Ref<Image>());
ERR_FAIL_COND_V(!p_size, Ref<Image>());
Ref<FileAccessMemory> memfile;
memfile.instantiate();
Error open_memfile_error = memfile->open_custom(p_dds, p_size);
ERR_FAIL_COND_V_MSG(open_memfile_error, Ref<Image>(), "Could not create memfile for DDS image buffer.");
DDSFormat dds_format;
uint32_t width, height, mipmaps, pitch, flags, layer_count, dds_type;
Vector<Ref<Image>> images = _dds_load_images_from_buffer(memfile, dds_format, width, height, mipmaps, pitch, flags, layer_count, dds_type);
ERR_FAIL_COND_V_MSG(images.is_empty(), Ref<Image>(), "Failed to load DDS image.");
return images[0];
}
ResourceFormatDDS::ResourceFormatDDS() {
Image::_dds_mem_loader_func = load_mem_dds;
}

View File

@@ -0,0 +1,44 @@
/**************************************************************************/
/* texture_loader_dds.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/resource_loader.h"
class ResourceFormatDDS : public ResourceFormatLoader {
public:
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual bool handles_type(const String &p_type) const override;
virtual String get_resource_type(const String &p_path) const override;
ResourceFormatDDS();
virtual ~ResourceFormatDDS() {}
};

44
modules/enet/SCsub Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
env_enet = env_modules.Clone()
# Thirdparty source files
thirdparty_obj = []
if env["builtin_enet"]:
thirdparty_dir = "#thirdparty/enet/"
thirdparty_sources = [
"enet_godot.cpp",
"callbacks.c",
"compress.c",
"host.c",
"list.c",
"packet.c",
"peer.c",
"protocol.c",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_enet.Prepend(CPPEXTPATH=[thirdparty_dir])
env_enet.Append(CPPDEFINES=["GODOT_ENET"])
env_thirdparty = env_enet.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot source files
module_obj = []
env_enet.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

18
modules/enet/config.py Normal file
View File

@@ -0,0 +1,18 @@
def can_build(env, platform):
return True
def configure(env):
pass
def get_doc_classes():
return [
"ENetMultiplayerPeer",
"ENetConnection",
"ENetPacketPeer",
]
def get_doc_path():
return "doc_classes"

View File

@@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ENetConnection" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
A wrapper class for an [url=http://enet.bespin.org/group__host.html]ENetHost[/url].
</brief_description>
<description>
ENet's purpose is to provide a relatively thin, simple and robust network communication layer on top of UDP (User Datagram Protocol).
</description>
<tutorials>
<link title="API documentation on the ENet website">http://enet.bespin.org/usergroup0.html</link>
</tutorials>
<methods>
<method name="bandwidth_limit">
<return type="void" />
<param index="0" name="in_bandwidth" type="int" default="0" />
<param index="1" name="out_bandwidth" type="int" default="0" />
<description>
Adjusts the bandwidth limits of a host.
</description>
</method>
<method name="broadcast">
<return type="void" />
<param index="0" name="channel" type="int" />
<param index="1" name="packet" type="PackedByteArray" />
<param index="2" name="flags" type="int" />
<description>
Queues a [param packet] to be sent to all peers associated with the host over the specified [param channel]. See [ENetPacketPeer] [code]FLAG_*[/code] constants for available packet flags.
</description>
</method>
<method name="channel_limit">
<return type="void" />
<param index="0" name="limit" type="int" />
<description>
Limits the maximum allowed channels of future incoming connections.
</description>
</method>
<method name="compress">
<return type="void" />
<param index="0" name="mode" type="int" enum="ENetConnection.CompressionMode" />
<description>
Sets the compression method used for network packets. These have different tradeoffs of compression speed versus bandwidth, you may need to test which one works best for your use case if you use compression at all.
[b]Note:[/b] Most games' network design involve sending many small packets frequently (smaller than 4 KB each). If in doubt, it is recommended to keep the default compression algorithm as it works best on these small packets.
[b]Note:[/b] The compression mode must be set to the same value on both the server and all its clients. Clients will fail to connect if the compression mode set on the client differs from the one set on the server.
</description>
</method>
<method name="connect_to_host">
<return type="ENetPacketPeer" />
<param index="0" name="address" type="String" />
<param index="1" name="port" type="int" />
<param index="2" name="channels" type="int" default="0" />
<param index="3" name="data" type="int" default="0" />
<description>
Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer.
[b]Note:[/b] You must call either [method create_host] or [method create_host_bound] on both ends before calling this method.
</description>
</method>
<method name="create_host">
<return type="int" enum="Error" />
<param index="0" name="max_peers" type="int" default="32" />
<param index="1" name="max_channels" type="int" default="0" />
<param index="2" name="in_bandwidth" type="int" default="0" />
<param index="3" name="out_bandwidth" type="int" default="0" />
<description>
Creates an ENetHost that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero).
This method binds a random available dynamic UDP port on the host machine at the [i]unspecified[/i] address. Use [method create_host_bound] to specify the address and port.
[b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection.
</description>
</method>
<method name="create_host_bound">
<return type="int" enum="Error" />
<param index="0" name="bind_address" type="String" />
<param index="1" name="bind_port" type="int" />
<param index="2" name="max_peers" type="int" default="32" />
<param index="3" name="max_channels" type="int" default="0" />
<param index="4" name="in_bandwidth" type="int" default="0" />
<param index="5" name="out_bandwidth" type="int" default="0" />
<description>
Creates an ENetHost bound to the given [param bind_address] and [param bind_port] that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero).
[b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection.
</description>
</method>
<method name="destroy">
<return type="void" />
<description>
Destroys the host and all resources associated with it.
</description>
</method>
<method name="dtls_client_setup">
<return type="int" enum="Error" />
<param index="0" name="hostname" type="String" />
<param index="1" name="client_options" type="TLSOptions" default="null" />
<description>
Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [param hostname]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="dtls_server_setup">
<return type="int" enum="Error" />
<param index="0" name="server_options" type="TLSOptions" />
<description>
Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. See [method TLSOptions.server].
</description>
</method>
<method name="flush">
<return type="void" />
<description>
Sends any queued packets on the host specified to its designated peers.
</description>
</method>
<method name="get_local_port" qualifiers="const">
<return type="int" />
<description>
Returns the local port to which this peer is bound.
</description>
</method>
<method name="get_max_channels" qualifiers="const">
<return type="int" />
<description>
Returns the maximum number of channels allowed for connected peers.
</description>
</method>
<method name="get_peers">
<return type="ENetPacketPeer[]" />
<description>
Returns the list of peers associated with this host.
[b]Note:[/b] This list might include some peers that are not fully connected or are still being disconnected.
</description>
</method>
<method name="pop_statistic">
<return type="float" />
<param index="0" name="statistic" type="int" enum="ENetConnection.HostStatistic" />
<description>
Returns and resets host statistics.
</description>
</method>
<method name="refuse_new_connections">
<return type="void" />
<param index="0" name="refuse" type="bool" />
<description>
Configures the DTLS server to automatically drop new connections.
[b]Note:[/b] This method is only relevant after calling [method dtls_server_setup].
</description>
</method>
<method name="service">
<return type="Array" />
<param index="0" name="timeout" type="int" default="0" />
<description>
Waits for events on this connection and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer].
Call this function regularly to handle connections, disconnections, and to receive new packets.
[b]Note:[/b] This method must be called on both ends involved in the event (sending and receiving hosts).
</description>
</method>
<method name="socket_send">
<return type="void" />
<param index="0" name="destination_address" type="String" />
<param index="1" name="destination_port" type="int" />
<param index="2" name="packet" type="PackedByteArray" />
<description>
Sends a [param packet] toward a destination from the address and port currently bound by this ENetConnection instance.
This is useful as it serves to establish entries in NAT routing tables on all devices between this bound instance and the public facing internet, allowing a prospective client's connection packets to be routed backward through the NAT device(s) between the public internet and this host.
This requires forward knowledge of a prospective client's address and communication port as seen by the public internet - after any NAT devices have handled their connection request. This information can be obtained by a [url=https://en.wikipedia.org/wiki/STUN]STUN[/url] service, and must be handed off to your host by an entity that is not the prospective client. This will never work for a client behind a Symmetric NAT due to the nature of the Symmetric NAT routing algorithm, as their IP and Port cannot be known beforehand.
</description>
</method>
</methods>
<constants>
<constant name="COMPRESS_NONE" value="0" enum="CompressionMode">
No compression. This uses the most bandwidth, but has the upside of requiring the fewest CPU resources. This option may also be used to make network debugging using tools like Wireshark easier.
</constant>
<constant name="COMPRESS_RANGE_CODER" value="1" enum="CompressionMode">
ENet's built-in range encoding. Works well on small packets, but is not the most efficient algorithm on packets larger than 4 KB.
</constant>
<constant name="COMPRESS_FASTLZ" value="2" enum="CompressionMode">
[url=https://fastlz.org/]FastLZ[/url] compression. This option uses less CPU resources compared to [constant COMPRESS_ZLIB], at the expense of using more bandwidth.
</constant>
<constant name="COMPRESS_ZLIB" value="3" enum="CompressionMode">
[url=https://www.zlib.net/]Zlib[/url] compression. This option uses less bandwidth compared to [constant COMPRESS_FASTLZ], at the expense of using more CPU resources.
</constant>
<constant name="COMPRESS_ZSTD" value="4" enum="CompressionMode">
[url=https://facebook.github.io/zstd/]Zstandard[/url] compression. Note that this algorithm is not very efficient on packets smaller than 4 KB. Therefore, it's recommended to use other compression algorithms in most cases.
</constant>
<constant name="EVENT_ERROR" value="-1" enum="EventType">
An error occurred during [method service]. You will likely need to [method destroy] the host and recreate it.
</constant>
<constant name="EVENT_NONE" value="0" enum="EventType">
No event occurred within the specified time limit.
</constant>
<constant name="EVENT_CONNECT" value="1" enum="EventType">
A connection request initiated by enet_host_connect has completed. The array will contain the peer which successfully connected.
</constant>
<constant name="EVENT_DISCONNECT" value="2" enum="EventType">
A peer has disconnected. This event is generated on a successful completion of a disconnect initiated by [method ENetPacketPeer.peer_disconnect], if a peer has timed out, or if a connection request initialized by [method connect_to_host] has timed out. The array will contain the peer which disconnected. The data field contains user supplied data describing the disconnection, or 0, if none is available.
</constant>
<constant name="EVENT_RECEIVE" value="3" enum="EventType">
A packet has been received from a peer. The array will contain the peer which sent the packet and the channel number upon which the packet was received. The received packet will be queued to the associated [ENetPacketPeer].
</constant>
<constant name="HOST_TOTAL_SENT_DATA" value="0" enum="HostStatistic">
Total data sent.
</constant>
<constant name="HOST_TOTAL_SENT_PACKETS" value="1" enum="HostStatistic">
Total UDP packets sent.
</constant>
<constant name="HOST_TOTAL_RECEIVED_DATA" value="2" enum="HostStatistic">
Total data received.
</constant>
<constant name="HOST_TOTAL_RECEIVED_PACKETS" value="3" enum="HostStatistic">
Total UDP packets received.
</constant>
</constants>
</class>

Some files were not shown because too many files have changed in this diff Show More