feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
import os
|
||||
|
||||
|
|
@ -62,7 +63,6 @@ register_module_types = env.CommandNoCache(
|
|||
)
|
||||
|
||||
|
||||
vs_sources = []
|
||||
test_headers = []
|
||||
# libmodule_<name>.a for each active module.
|
||||
for name, path in env.module_list.items():
|
||||
|
|
@ -74,8 +74,6 @@ for name, path in env.module_list.items():
|
|||
|
||||
lib = env_modules.add_library("module_%s" % name, env.modules_sources)
|
||||
env.Prepend(LIBS=[lib])
|
||||
if env["vsproj"]:
|
||||
vs_sources += env.modules_sources
|
||||
|
||||
if env["tests"]:
|
||||
# Lookup potential headers in `tests` subfolder.
|
||||
|
|
@ -103,5 +101,3 @@ 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])
|
||||
if env["vsproj"]:
|
||||
env.modules_sources += vs_sources
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
@ -40,6 +41,13 @@ env_astcenc.Prepend(CPPPATH=[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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
def can_build(env, platform):
|
||||
# Godot only uses it in the editor, but ANGLE depends on it and we had
|
||||
# to remove the copy from prebuilt ANGLE libs to solve symbol clashes.
|
||||
return env.editor_build or env.get("angle_libs")
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
|||
|
|
@ -35,42 +35,41 @@
|
|||
|
||||
#include <astcenc.h>
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
const uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
// TODO: See how to handle lossy quality.
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
if (Image::is_format_compressed(img_format)) {
|
||||
if (r_img->is_compressed()) {
|
||||
return; // Do not compress, already compressed.
|
||||
}
|
||||
|
||||
bool is_hdr = false;
|
||||
if ((img_format >= Image::FORMAT_RH) && (img_format <= Image::FORMAT_RGBE9995)) {
|
||||
is_hdr = true;
|
||||
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_RGBA8;
|
||||
astcenc_profile profile = 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;
|
||||
profile = ASTCENC_PRF_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;
|
||||
profile = ASTCENC_PRF_HDR;
|
||||
} else {
|
||||
target_format = Image::FORMAT_ASTC_8x8;
|
||||
}
|
||||
|
|
@ -79,8 +78,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
|||
}
|
||||
|
||||
// Compress image data and (if required) mipmaps.
|
||||
|
||||
const bool mipmaps = r_img->has_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;
|
||||
|
|
@ -93,11 +91,10 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
|||
height = required_height;
|
||||
}
|
||||
|
||||
print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
|
||||
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.
|
||||
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
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();
|
||||
|
|
@ -113,42 +110,44 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
|||
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)));
|
||||
|
||||
Vector<uint8_t> image_data = r_img->get_data();
|
||||
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
const uint8_t *src_data = r_img->ptr();
|
||||
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
for (int i = 0; i < mip_count + 1; i++) {
|
||||
int src_mip_w, src_mip_h;
|
||||
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 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 uint8_t *slices = &image_data.ptr()[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];
|
||||
|
||||
int dst_mip_w, dst_mip_h;
|
||||
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).
|
||||
if (unlikely(dst_ofs % 8 != 0)) {
|
||||
astcenc_context_free(context);
|
||||
ERR_FAIL_MSG("astcenc: Mip offset is not a multiple of 8.");
|
||||
}
|
||||
uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs];
|
||||
|
||||
// Compress image.
|
||||
|
||||
astcenc_image image;
|
||||
image.dim_x = src_mip_w;
|
||||
image.dim_y = src_mip_h;
|
||||
image.dim_z = 1;
|
||||
image.data_type = ASTCENC_TYPE_U8;
|
||||
if (is_hdr) {
|
||||
|
||||
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 **)(&slices);
|
||||
|
||||
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;
|
||||
|
|
@ -160,65 +159,69 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) {
|
|||
};
|
||||
|
||||
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, mipmaps, target_format, dest_data);
|
||||
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) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
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();
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
bool is_hdr = false;
|
||||
unsigned int block_x = 0;
|
||||
unsigned int block_y = 0;
|
||||
if (img_format == Image::FORMAT_ASTC_4x4) {
|
||||
block_x = 4;
|
||||
block_y = 4;
|
||||
is_hdr = false;
|
||||
} else if (img_format == Image::FORMAT_ASTC_4x4_HDR) {
|
||||
block_x = 4;
|
||||
block_y = 4;
|
||||
is_hdr = true;
|
||||
} else if (img_format == Image::FORMAT_ASTC_8x8) {
|
||||
block_x = 8;
|
||||
block_y = 8;
|
||||
is_hdr = false;
|
||||
} else if (img_format == Image::FORMAT_ASTC_8x8_HDR) {
|
||||
block_x = 8;
|
||||
block_y = 8;
|
||||
is_hdr = true;
|
||||
} else {
|
||||
ERR_FAIL_MSG("astcenc: Cannot decompress Image with a non-ASTC format.");
|
||||
|
||||
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_profile profile = ASTCENC_PRF_LDR;
|
||||
if (is_hdr) {
|
||||
profile = ASTCENC_PRF_HDR;
|
||||
}
|
||||
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, 0, &config);
|
||||
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;
|
||||
|
||||
|
|
@ -226,49 +229,44 @@ void _decompress_astc(Image *r_img) {
|
|||
ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
|
||||
vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
|
||||
|
||||
Image::Format target_format = is_hdr ? Image::FORMAT_RGBAF : Image::FORMAT_RGBA8;
|
||||
const Image::Format target_format = is_hdr ? Image::FORMAT_RGBAH : Image::FORMAT_RGBA8;
|
||||
|
||||
const bool mipmaps = r_img->has_mipmaps();
|
||||
const bool has_mipmaps = r_img->has_mipmaps();
|
||||
int width = r_img->get_width();
|
||||
int height = r_img->get_height();
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
|
||||
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.
|
||||
|
||||
Vector<uint8_t> image_data = r_img->get_data();
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
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(width, height, src_format, i);
|
||||
const uint8_t *mip_data = &src_data[src_ofs];
|
||||
|
||||
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 *src_data = &image_data.ptr()[src_ofs];
|
||||
int64_t src_size;
|
||||
if (i == mip_count) {
|
||||
src_size = image_data.size() - src_ofs;
|
||||
src_size = r_img->get_data_size() - src_ofs;
|
||||
} else {
|
||||
int auxw, auxh;
|
||||
src_size = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i + 1, auxw, auxh) - src_ofs;
|
||||
src_size = Image::get_image_mipmap_offset(width, height, src_format, i + 1) - src_ofs;
|
||||
}
|
||||
|
||||
int dst_mip_w, dst_mip_h;
|
||||
int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, 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 = (uint8_t *)&dest_write[dst_ofs];
|
||||
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 = ASTCENC_TYPE_U8;
|
||||
if (is_hdr) {
|
||||
target_format = Image::FORMAT_RGBAF;
|
||||
image.data_type = ASTCENC_TYPE_F32;
|
||||
}
|
||||
image.data_type = is_hdr ? ASTCENC_TYPE_F16 : ASTCENC_TYPE_U8;
|
||||
|
||||
image.data = (void **)(&dest_mip_write);
|
||||
|
||||
|
|
@ -276,18 +274,17 @@ void _decompress_astc(Image *r_img) {
|
|||
ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
|
||||
};
|
||||
|
||||
status = astcenc_decompress_image(context, src_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.");
|
||||
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, mipmaps, target_format, dest_data);
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@
|
|||
|
||||
#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);
|
||||
|
||||
#endif // IMAGE_COMPRESS_ASTCENC_H
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ void initialize_astcenc_module(ModuleInitializationLevel p_level) {
|
|||
return;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Image::_image_compress_astc_func = _compress_astc;
|
||||
#endif
|
||||
|
||||
Image::_image_decompress_astc = _decompress_astc;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
@ -11,47 +12,74 @@ 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
|
||||
encoder_sources = [
|
||||
"basisu_backend.cpp",
|
||||
"basisu_basis_file.cpp",
|
||||
"basisu_bc7enc.cpp",
|
||||
"basisu_opencl.cpp",
|
||||
"basisu_comp.cpp",
|
||||
"basisu_enc.cpp",
|
||||
"basisu_etc.cpp",
|
||||
"basisu_frontend.cpp",
|
||||
"basisu_gpu_texture.cpp",
|
||||
"basisu_kernels_sse.cpp",
|
||||
"basisu_pvrtc1_4.cpp",
|
||||
"basisu_resampler.cpp",
|
||||
"basisu_resample_filters.cpp",
|
||||
"basisu_ssim.cpp",
|
||||
"basisu_uastc_enc.cpp",
|
||||
"pvpngreader.cpp",
|
||||
]
|
||||
encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources]
|
||||
if basisu_encoder:
|
||||
encoder_sources = [
|
||||
"3rdparty/android_astc_decomp.cpp",
|
||||
"basisu_astc_hdr_enc.cpp",
|
||||
"basisu_backend.cpp",
|
||||
"basisu_basis_file.cpp",
|
||||
"basisu_bc7enc.cpp",
|
||||
"basisu_opencl.cpp",
|
||||
"basisu_comp.cpp",
|
||||
"basisu_enc.cpp",
|
||||
"basisu_etc.cpp",
|
||||
"basisu_frontend.cpp",
|
||||
"basisu_gpu_texture.cpp",
|
||||
"basisu_kernels_sse.cpp",
|
||||
"basisu_pvrtc1_4.cpp",
|
||||
"basisu_resampler.cpp",
|
||||
"basisu_resample_filters.cpp",
|
||||
"basisu_ssim.cpp",
|
||||
"basisu_uastc_enc.cpp",
|
||||
"pvpngreader.cpp",
|
||||
]
|
||||
encoder_sources = [thirdparty_dir + "encoder/" + file for file in encoder_sources]
|
||||
|
||||
transcoder_sources = [thirdparty_dir + "transcoder/basisu_transcoder.cpp"]
|
||||
|
||||
# Treat Basis headers as system headers to avoid raising warnings. Not supported on MSVC.
|
||||
if not env.msvc:
|
||||
env_basisu.Append(
|
||||
CPPFLAGS=["-isystem", Dir(thirdparty_dir).path, "-isystem", Dir("#thirdparty/jpeg-compressor").path]
|
||||
)
|
||||
env_basisu.Append(CPPFLAGS=["-isystem", Dir(thirdparty_dir).path])
|
||||
else:
|
||||
env_basisu.Prepend(CPPPATH=[thirdparty_dir, "#thirdparty/jpeg-compressor"])
|
||||
env_basisu.Prepend(CPPPATH=[thirdparty_dir])
|
||||
|
||||
if basisu_encoder:
|
||||
env_basisu.Prepend(CPPPATH=["#thirdparty/jpeg-compressor"])
|
||||
env_basisu.Prepend(CPPPATH=["#thirdparty/tinyexr"])
|
||||
|
||||
if env["builtin_zstd"]:
|
||||
env_basisu.Prepend(CPPPATH=["#thirdparty/zstd"])
|
||||
|
||||
if env.dev_build:
|
||||
env_basisu.Append(CPPDEFINES=[("BASISU_DEVEL_MESSAGES", 1), ("BASISD_ENABLE_DEBUG_FLAGS", 1)])
|
||||
|
||||
env_thirdparty = env_basisu.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
if env.editor_build:
|
||||
env_thirdparty.Append(CPPDEFINES=["BASISU_NO_IMG_LOADERS"])
|
||||
|
||||
# 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=[
|
||||
# Storage formats.
|
||||
# Godot only implements `.basis` support through basis_universal.
|
||||
# Support for `.ktx` files are implemented with a direct libktx implementation.
|
||||
# Building the encoder requires `BASISD_SUPPORT_KTX2` to be enabled,
|
||||
# so we can only disable Zstandard compression for `.ktx` files
|
||||
# (this is not used in `.basis` files).
|
||||
("BASISD_SUPPORT_KTX2_ZSTD", 0),
|
||||
# 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
env.module_add_dependencies("basis_universal", ["jpg"])
|
||||
if env.editor_build: # Encoder dependencies
|
||||
env.module_add_dependencies("basis_universal", ["jpg", "tinyexr"])
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,25 +30,73 @@
|
|||
|
||||
#include "image_compress_basisu.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() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
basisu::basisu_encoder_init();
|
||||
#endif
|
||||
|
||||
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) {
|
||||
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();
|
||||
image->convert(Image::FORMAT_RGBA8);
|
||||
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;
|
||||
}
|
||||
|
||||
basisu::basis_compressor_params params;
|
||||
|
||||
|
|
@ -74,34 +122,42 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
|
|||
basisu::job_pool job_pool(OS::get_singleton()->get_processor_count());
|
||||
params.m_pJob_pool = &job_pool;
|
||||
|
||||
BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_RG;
|
||||
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_RGB;
|
||||
} break;
|
||||
case Image::USED_CHANNELS_RG: {
|
||||
// Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle,
|
||||
// as BasisUniversal didn't use to support ETC2_RG11 transcoding.
|
||||
params.m_force_alpha = true;
|
||||
image->convert_rg_to_ra_rgba8();
|
||||
decompress_format = BASIS_DECOMPRESS_RG_AS_RA;
|
||||
} 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;
|
||||
BasisDecompressFormat decompress_format = BASIS_DECOMPRESS_MAX;
|
||||
|
||||
if (is_hdr) {
|
||||
decompress_format = BASIS_DECOMPRESS_HDR_RGB;
|
||||
params.m_hdr = true;
|
||||
params.m_uastc_hdr_options.set_quality_level(0);
|
||||
|
||||
} 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();
|
||||
|
|
@ -115,9 +171,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
|
|||
|
||||
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<uint32_t> mip_data_padded;
|
||||
Vector<uint8_t> mip_data_padded;
|
||||
|
||||
for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
|
||||
int64_t ofs, size;
|
||||
|
|
@ -128,31 +185,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
|
|||
|
||||
// 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)) {
|
||||
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
|
||||
const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);
|
||||
|
||||
// Reserve space in the padded buffer.
|
||||
mip_data_padded.resize(next_width * next_height);
|
||||
uint32_t *data_padded_ptr = mip_data_padded.ptrw();
|
||||
|
||||
// Pad mipmap to the nearest block by smearing.
|
||||
int x = 0, y = 0;
|
||||
for (y = 0; y < height; y++) {
|
||||
for (x = 0; x < width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
|
||||
}
|
||||
|
||||
// First, smear in x.
|
||||
for (; x < next_width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Then, smear in y.
|
||||
for (; y < next_height; y++) {
|
||||
for (x = 0; x < next_width; x++) {
|
||||
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
|
||||
}
|
||||
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.
|
||||
|
|
@ -161,7 +197,7 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
|
|||
// Override the mipmap's properties.
|
||||
width = next_width;
|
||||
height = next_height;
|
||||
size = mip_data_padded.size() * 4;
|
||||
size = mip_data_padded.size();
|
||||
}
|
||||
|
||||
// Get the next mipmap's resolution.
|
||||
|
|
@ -169,44 +205,61 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
|
|||
next_height /= 2;
|
||||
|
||||
// Copy the source mipmap's data to a BasisU image.
|
||||
basisu::image basisu_image(width, height);
|
||||
memcpy(basisu_image.get_ptr(), image_mip_data, size);
|
||||
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);
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
params.m_source_images.push_back(basisu_image);
|
||||
} else {
|
||||
basisu_mipmaps.push_back(basisu_image);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.m_source_mipmap_images.push_back(basisu_mipmaps);
|
||||
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.
|
||||
Vector<uint8_t> basisu_data;
|
||||
|
||||
basisu::basis_compressor compressor;
|
||||
compressor.init(params);
|
||||
|
||||
int basisu_err = compressor.process();
|
||||
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, basisu_data);
|
||||
ERR_FAIL_COND_V(basisu_err != basisu::basis_compressor::cECSuccess, Vector<uint8_t>());
|
||||
|
||||
const basisu::uint8_vec &basisu_out = compressor.get_output_basis_file();
|
||||
basisu_data.resize(basisu_out.size() + 4);
|
||||
const basisu::uint8_vec &basisu_encoded = compressor.get_output_basis_file();
|
||||
|
||||
// Copy the encoded data to the buffer.
|
||||
{
|
||||
uint8_t *wb = basisu_data.ptrw();
|
||||
*(uint32_t *)wb = decompress_format;
|
||||
Vector<uint8_t> basisu_data;
|
||||
basisu_data.resize(basisu_encoded.size() + 4);
|
||||
uint8_t *basisu_data_ptr = basisu_data.ptrw();
|
||||
|
||||
memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
|
||||
}
|
||||
// Copy the encoded BasisU data into the output buffer.
|
||||
*(uint32_t *)basisu_data_ptr = decompress_format;
|
||||
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.");
|
||||
|
||||
|
|
@ -219,15 +272,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
|
|||
// 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 needs_ra_rg_swap = false;
|
||||
bool needs_rg_trim = false;
|
||||
|
||||
switch (*(uint32_t *)(src_ptr)) {
|
||||
BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr));
|
||||
|
||||
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: {
|
||||
// RGTC transcoding is currently performed with RG_AS_RA, fail.
|
||||
ERR_FAIL_V(image);
|
||||
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) {
|
||||
|
|
@ -267,20 +373,24 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
|
|||
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
|
||||
image_format = Image::FORMAT_RGBA8;
|
||||
}
|
||||
|
||||
} 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;
|
||||
case BASIS_DECOMPRESS_HDR_RGB: {
|
||||
if (bptc_supported) {
|
||||
basisu_format = basist::transcoder_texture_format::cTFBC6H;
|
||||
image_format = Image::FORMAT_BPTC_RGBFU;
|
||||
} else if (astc_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::cTFRGBA32;
|
||||
image_format = Image::FORMAT_RGBA8;
|
||||
needs_ra_rg_swap = true;
|
||||
basisu_format = basist::transcoder_texture_format::cTFRGB_9E5;
|
||||
image_format = Image::FORMAT_RGBE9995;
|
||||
}
|
||||
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V(image);
|
||||
} break;
|
||||
}
|
||||
|
||||
|
|
@ -324,6 +434,18 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,11 +38,21 @@ enum BasisDecompressFormat {
|
|||
BASIS_DECOMPRESS_RGB,
|
||||
BASIS_DECOMPRESS_RGBA,
|
||||
BASIS_DECOMPRESS_RG_AS_RA,
|
||||
BASIS_DECOMPRESS_R,
|
||||
BASIS_DECOMPRESS_HDR_RGB,
|
||||
BASIS_DECOMPRESS_MAX
|
||||
};
|
||||
|
||||
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);
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
index c431ceaf12..e87dd636a2 100644
|
||||
--- a/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
+++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp
|
||||
@@ -409,7 +409,7 @@ namespace basisu
|
||||
bool load_jpg(const char *pFilename, image& img)
|
||||
{
|
||||
int width = 0, height = 0, actual_comps = 0;
|
||||
- uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagLinearChromaFiltering);
|
||||
+ uint8_t *pImage_data = jpgd::decompress_jpeg_image_from_file(pFilename, &width, &height, &actual_comps, 4, jpgd::jpeg_decoder::cFlagBoxChromaFiltering);
|
||||
if (!pImage_data)
|
||||
return false;
|
||||
|
||||
10
engine/modules/bcdec/SCsub
Normal file
10
engine/modules/bcdec/SCsub
Normal 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")
|
||||
251
engine/modules/bcdec/image_decompress_bcdec.cpp
Normal file
251
engine/modules/bcdec/image_decompress_bcdec.cpp
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#define DECOMPRESS_LOOP(func, block_size, color_bytesize, color_components) \
|
||||
for (uint64_t y = 0; y < height; y += 4) { \
|
||||
for (uint64_t x = 0; x < width; x += 4) { \
|
||||
func(&src_blocks[src_pos], &dec_blocks[dst_pos], width *color_components); \
|
||||
src_pos += block_size; \
|
||||
dst_pos += 4 * color_bytesize; \
|
||||
} \
|
||||
dst_pos += 3 * width * color_bytesize; \
|
||||
}
|
||||
|
||||
#define DECOMPRESS_LOOP_SAFE(func, block_size, color_bytesize, color_components, output) \
|
||||
for (uint64_t y = 0; y < height; y += 4) { \
|
||||
for (uint64_t x = 0; x < width; x += 4) { \
|
||||
const uint32_t yblock = MIN(height - y, 4ul); \
|
||||
const uint32_t xblock = MIN(width - x, 4ul); \
|
||||
\
|
||||
const bool incomplete = yblock < 4 || xblock < 4; \
|
||||
uint8_t *dec_out = incomplete ? output : &dec_blocks[y * 4 * width + x * color_bytesize]; \
|
||||
\
|
||||
func(&src_blocks[src_pos], dec_out, 4 * color_components); \
|
||||
src_pos += block_size; \
|
||||
\
|
||||
if (incomplete) { \
|
||||
for (uint32_t cy = 0; cy < yblock; cy++) { \
|
||||
for (uint32_t cx = 0; cx < xblock; cx++) { \
|
||||
memcpy(&dec_blocks[(y + cy) * 4 * width + (x + cx) * color_bytesize], &output[cy * 4 + cx * color_bytesize], color_bytesize); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
uint64_t src_pos = 0;
|
||||
|
||||
uint8_t r8_output[4 * 4];
|
||||
uint8_t rg8_output[4 * 4 * 2];
|
||||
uint8_t rgba8_output[4 * 4 * 4];
|
||||
uint8_t rgbh_output[4 * 4 * 6];
|
||||
|
||||
switch (format) {
|
||||
case BCdec_BC1: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4, rgba8_output)
|
||||
} break;
|
||||
case BCdec_BC2: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4, rgba8_output)
|
||||
} break;
|
||||
case BCdec_BC3: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4, rgba8_output)
|
||||
} break;
|
||||
case BCdec_BC4: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1, r8_output)
|
||||
} break;
|
||||
case BCdec_BC5: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2, rg8_output)
|
||||
} break;
|
||||
case BCdec_BC6U: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3, rgbh_output)
|
||||
} break;
|
||||
case BCdec_BC6S: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3, rgbh_output)
|
||||
} break;
|
||||
case BCdec_BC7: {
|
||||
DECOMPRESS_LOOP_SAFE(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4, rgba8_output)
|
||||
} break;
|
||||
}
|
||||
|
||||
} else {
|
||||
uint64_t src_pos = 0, dst_pos = 0;
|
||||
|
||||
switch (format) {
|
||||
case BCdec_BC1: {
|
||||
DECOMPRESS_LOOP(bcdec_bc1, BCDEC_BC1_BLOCK_SIZE, 4, 4)
|
||||
} break;
|
||||
case BCdec_BC2: {
|
||||
DECOMPRESS_LOOP(bcdec_bc2, BCDEC_BC2_BLOCK_SIZE, 4, 4)
|
||||
} break;
|
||||
case BCdec_BC3: {
|
||||
DECOMPRESS_LOOP(bcdec_bc3, BCDEC_BC3_BLOCK_SIZE, 4, 4)
|
||||
} break;
|
||||
case BCdec_BC4: {
|
||||
DECOMPRESS_LOOP(bcdec_bc4, BCDEC_BC4_BLOCK_SIZE, 1, 1)
|
||||
} break;
|
||||
case BCdec_BC5: {
|
||||
DECOMPRESS_LOOP(bcdec_bc5, BCDEC_BC5_BLOCK_SIZE, 2, 2)
|
||||
} break;
|
||||
case BCdec_BC6U: {
|
||||
DECOMPRESS_LOOP(bcdec_bc6h_half_u, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
|
||||
} break;
|
||||
case BCdec_BC6S: {
|
||||
DECOMPRESS_LOOP(bcdec_bc6h_half_s, BCDEC_BC6H_BLOCK_SIZE, 6, 3)
|
||||
} break;
|
||||
case BCdec_BC7: {
|
||||
DECOMPRESS_LOOP(bcdec_bc7, BCDEC_BC7_BLOCK_SIZE, 4, 4)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECOMPRESS_LOOP
|
||||
#undef DECOMPRESS_LOOP_SAFE
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Compressed images' dimensions should be padded to the upper multiple of 4.
|
||||
// If they aren't, they need to be realigned (the actual data is correctly padded though).
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
int new_width = width + (4 - (width % 4));
|
||||
int new_height = height + (4 - (height % 4));
|
||||
|
||||
print_verbose(vformat("Compressed image's dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", width, height, new_width, new_height));
|
||||
|
||||
width = new_width;
|
||||
height = new_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_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h);
|
||||
int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
|
||||
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));
|
||||
}
|
||||
49
engine/modules/bcdec/image_decompress_bcdec.h
Normal file
49
engine/modules/bcdec/image_decompress_bcdec.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IMAGE_DECOMPRESS_BCDEC_H
|
||||
#define IMAGE_DECOMPRESS_BCDEC_H
|
||||
|
||||
#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);
|
||||
|
||||
#endif // IMAGE_DECOMPRESS_BCDEC_H
|
||||
|
|
@ -30,17 +30,18 @@
|
|||
|
||||
#include "register_types.h"
|
||||
|
||||
#include "image_decompress_squish.h"
|
||||
#include "image_decompress_bcdec.h"
|
||||
|
||||
void initialize_squish_module(ModuleInitializationLevel p_level) {
|
||||
void initialize_bcdec_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
Image::_image_decompress_bc = image_decompress_squish;
|
||||
Image::_image_decompress_bc = image_decompress_bcdec;
|
||||
Image::_image_decompress_bptc = image_decompress_bcdec;
|
||||
}
|
||||
|
||||
void uninitialize_squish_module(ModuleInitializationLevel p_level) {
|
||||
void uninitialize_bcdec_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -28,12 +28,12 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef SQUISH_REGISTER_TYPES_H
|
||||
#define SQUISH_REGISTER_TYPES_H
|
||||
#ifndef BCDEC_REGISTER_TYPES_H
|
||||
#define BCDEC_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_squish_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_squish_module(ModuleInitializationLevel p_level);
|
||||
void initialize_bcdec_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_bcdec_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // SQUISH_REGISTER_TYPES_H
|
||||
#endif // BCDEC_REGISTER_TYPES_H
|
||||
75
engine/modules/betsy/CrossPlatformSettings_piece_all.glsl
Normal file
75
engine/modules/betsy/CrossPlatformSettings_piece_all.glsl
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#define min3(a, b, c) min(a, min(b, c))
|
||||
#define max3(a, b, c) max(a, max(b, c))
|
||||
|
||||
#define float2 vec2
|
||||
#define float3 vec3
|
||||
#define float4 vec4
|
||||
|
||||
#define int2 ivec2
|
||||
#define int3 ivec3
|
||||
#define int4 ivec4
|
||||
|
||||
#define uint2 uvec2
|
||||
#define uint3 uvec3
|
||||
#define uint4 uvec4
|
||||
|
||||
#define float2x2 mat2
|
||||
#define float3x3 mat3
|
||||
#define float4x4 mat4
|
||||
#define ogre_float4x3 mat3x4
|
||||
|
||||
#define ushort uint
|
||||
#define ushort3 uint3
|
||||
#define ushort4 uint4
|
||||
|
||||
//Short used for read operations. It's an int in GLSL & HLSL. An ushort in Metal
|
||||
#define rshort int
|
||||
#define rshort2 int2
|
||||
#define rint int
|
||||
//Short used for write operations. It's an int in GLSL. An ushort in HLSL & Metal
|
||||
#define wshort2 int2
|
||||
#define wshort3 int3
|
||||
|
||||
#define toFloat3x3(x) mat3(x)
|
||||
#define buildFloat3x3(row0, row1, row2) mat3(row0, row1, row2)
|
||||
|
||||
#define mul(x, y) ((x) * (y))
|
||||
#define saturate(x) clamp((x), 0.0, 1.0)
|
||||
#define lerp mix
|
||||
#define rsqrt inversesqrt
|
||||
#define INLINE
|
||||
#define NO_INTERPOLATION_PREFIX flat
|
||||
#define NO_INTERPOLATION_SUFFIX
|
||||
|
||||
#define PARAMS_ARG_DECL
|
||||
#define PARAMS_ARG
|
||||
|
||||
#define reversebits bitfieldReverse
|
||||
|
||||
#define OGRE_Sample(tex, sampler, uv) texture(tex, uv)
|
||||
#define OGRE_SampleLevel(tex, sampler, uv, lod) textureLod(tex, uv, lod)
|
||||
#define OGRE_SampleArray2D(tex, sampler, uv, arrayIdx) texture(tex, vec3(uv, arrayIdx))
|
||||
#define OGRE_SampleArray2DLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec3(uv, arrayIdx), lod)
|
||||
#define OGRE_SampleArrayCubeLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec4(uv, arrayIdx), lod)
|
||||
#define OGRE_SampleGrad(tex, sampler, uv, ddx, ddy) textureGrad(tex, uv, ddx, ddy)
|
||||
#define OGRE_SampleArray2DGrad(tex, sampler, uv, arrayIdx, ddx, ddy) textureGrad(tex, vec3(uv, arrayIdx), ddx, ddy)
|
||||
#define OGRE_ddx(val) dFdx(val)
|
||||
#define OGRE_ddy(val) dFdy(val)
|
||||
#define OGRE_Load2D(tex, iuv, lod) texelFetch(tex, iuv, lod)
|
||||
#define OGRE_LoadArray2D(tex, iuv, arrayIdx, lod) texelFetch(tex, ivec3(iuv, arrayIdx), lod)
|
||||
#define OGRE_Load2DMS(tex, iuv, subsample) texelFetch(tex, iuv, subsample)
|
||||
|
||||
#define OGRE_Load3D(tex, iuv, lod) texelFetch(tex, ivec3(iuv), lod)
|
||||
|
||||
#define OGRE_GatherRed(tex, sampler, uv) textureGather(tex, uv, 0)
|
||||
#define OGRE_GatherGreen(tex, sampler, uv) textureGather(tex, uv, 1)
|
||||
#define OGRE_GatherBlue(tex, sampler, uv) textureGather(tex, uv, 2)
|
||||
|
||||
#define bufferFetch1(buffer, idx) texelFetch(buffer, idx).x
|
||||
|
||||
#define OGRE_SAMPLER_ARG_DECL(samplerName)
|
||||
#define OGRE_SAMPLER_ARG(samplerName)
|
||||
|
||||
#define OGRE_Texture3D_float4 sampler3D
|
||||
#define OGRE_OUT_REF(declType, variableName) out declType variableName
|
||||
#define OGRE_INOUT_REF(declType, variableName) inout declType variableName
|
||||
18
engine/modules/betsy/LICENSE.Betsy.md
Normal file
18
engine/modules/betsy/LICENSE.Betsy.md
Normal 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
engine/modules/betsy/SCsub
Normal file
18
engine/modules/betsy/SCsub
Normal 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")
|
||||
23
engine/modules/betsy/alpha_stitch.glsl
Normal file
23
engine/modules/betsy/alpha_stitch.glsl
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// RGB and Alpha components of ETC2 RGBA are computed separately.
|
||||
// This compute shader merely stitches them together to form the final result
|
||||
// It's also used by RG11 driver to stitch two R11 into one RG11
|
||||
|
||||
#[compute]
|
||||
#version 450
|
||||
|
||||
#include "CrossPlatformSettings_piece_all.glsl"
|
||||
|
||||
layout(local_size_x = 8, //
|
||||
local_size_y = 8, //
|
||||
local_size_z = 1) in;
|
||||
|
||||
layout(binding = 0) uniform usampler2D srcRGB;
|
||||
layout(binding = 1) uniform usampler2D srcAlpha;
|
||||
layout(binding = 2, rgba32ui) uniform restrict writeonly uimage2D dstTexture;
|
||||
|
||||
void main() {
|
||||
uint2 rgbBlock = OGRE_Load2D(srcRGB, int2(gl_GlobalInvocationID.xy), 0).xy;
|
||||
uint2 alphaBlock = OGRE_Load2D(srcAlpha, int2(gl_GlobalInvocationID.xy), 0).xy;
|
||||
|
||||
imageStore(dstTexture, int2(gl_GlobalInvocationID.xy), uint4(rgbBlock.xy, alphaBlock.xy));
|
||||
}
|
||||
491
engine/modules/betsy/bc1.glsl
Normal file
491
engine/modules/betsy/bc1.glsl
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
#[versions]
|
||||
|
||||
standard = "";
|
||||
dithered = "#define BC1_DITHER";
|
||||
|
||||
#[compute]
|
||||
#version 450
|
||||
|
||||
#include "CrossPlatformSettings_piece_all.glsl"
|
||||
|
||||
#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 {
|
||||
float2 c_oMatch5[256];
|
||||
float2 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;
|
||||
|
||||
float3 rgb565to888(float rgb565) {
|
||||
float3 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 / float3( 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 * float3(8.25f, 4.0625f, 8.25f));
|
||||
}
|
||||
|
||||
float rgb888to565(float3 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
|
||||
float3 lerp13(float3 a, float3 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 float3 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
|
||||
float3 avgColor;
|
||||
float3 minColor;
|
||||
float3 maxColor;
|
||||
|
||||
avgColor = minColor = maxColor = unpackUnorm4x8(srcPixelsBlock[0]).xyz;
|
||||
for (int i = 1; i < 16; ++i) {
|
||||
const float3 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 float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 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 = max3(abs(vF.r), abs(vF.g), abs(vF.b));
|
||||
float3 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
|
||||
float3 minEndpoint, maxEndpoint;
|
||||
float minDot = FLT_MAX;
|
||||
float maxDot = -FLT_MAX;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
const float3 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], float3 color[4]) {
|
||||
uint mask = 0u;
|
||||
float3 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 float3 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
|
||||
float4 ep1 = float4(0, 0, 0, 0);
|
||||
float4 ep2 = float4(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;
|
||||
|
||||
float3 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);
|
||||
{
|
||||
float4 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
|
||||
float3 rgbVal = float3(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;
|
||||
float3 at1 = float3(0, 0, 0);
|
||||
float3 at2 = float3(0, 0, 0);
|
||||
for (int i = 0; i < 16; ++i, cm >>= 2u) {
|
||||
const float3 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);
|
||||
|
||||
float2 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 float3 newMaxVal = clamp(floor((at1 * yy - at2 * xy) * f_rb_g.xyx + 0.5f),
|
||||
float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31));
|
||||
newMax16 = newMaxVal.x * 2048.0f + newMaxVal.y * 32.0f + newMaxVal.z;
|
||||
|
||||
const float3 newMinVal = clamp(floor((at2 * xx - at1 * xy) * f_rb_g.xyx + 0.5f),
|
||||
float3(0.0f, 0.0f, 0.0f), float3(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)
|
||||
float3 quant(float3 srcValue) {
|
||||
srcValue = clamp(srcValue, 0.0f, 255.0f);
|
||||
// Convert 888 -> 565
|
||||
srcValue = floor(srcValue * float3(31.0f / 255.0f, 63.0f / 255.0f, 31.0f / 255.0f) + 0.5f);
|
||||
// Convert 565 -> 888 back
|
||||
srcValue = floor(srcValue * float3(8.25f, 4.0625f, 8.25f));
|
||||
|
||||
return srcValue;
|
||||
}
|
||||
|
||||
void DitherBlock(const uint srcPixBlck[16], out uint dthPixBlck[16]) {
|
||||
float3 ep1[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) };
|
||||
float3 ep2[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) };
|
||||
|
||||
for (uint y = 0u; y < 16u; y += 4u) {
|
||||
float3 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(float4(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(float4(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(float4(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(float4(dithPixel * (1.0f / 255.0f), 1.0f));
|
||||
|
||||
// swap( ep1, ep2 )
|
||||
for (uint i = 0u; i < 4u; ++i) {
|
||||
float3 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 uint2 pixelsToLoadBase = gl_GlobalInvocationID.xy << 2u;
|
||||
for (uint i = 0u; i < 16u; ++i) {
|
||||
const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i & 0x03u, i >> 2u);
|
||||
const float3 srcPixels0 = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyz;
|
||||
srcPixelsBlock[i] = packUnorm4x8(float4(srcPixels0, 1.0f));
|
||||
bAllColorsEqual = bAllColorsEqual && srcPixelsBlock[0] == srcPixelsBlock[i];
|
||||
}
|
||||
|
||||
float maxEndp16, minEndp16;
|
||||
uint mask = 0u;
|
||||
|
||||
if (bAllColorsEqual) {
|
||||
const uint3 rgbVal = uint3(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) {
|
||||
float3 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) {
|
||||
float3 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;
|
||||
}
|
||||
|
||||
uint2 outputBytes;
|
||||
outputBytes.x = uint(maxEndp16) | (uint(minEndp16) << 16u);
|
||||
outputBytes.y = mask;
|
||||
|
||||
uint2 dstUV = gl_GlobalInvocationID.xy;
|
||||
imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u));
|
||||
}
|
||||
153
engine/modules/betsy/bc4.glsl
Normal file
153
engine/modules/betsy/bc4.glsl
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#[versions]
|
||||
|
||||
unsigned = "";
|
||||
signed = "#define SNORM";
|
||||
|
||||
#[compute]
|
||||
#version 450
|
||||
|
||||
#include "CrossPlatformSettings_piece_all.glsl"
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
shared float2 g_minMaxValues[4u * 4u * 4u];
|
||||
shared uint2 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;
|
||||
float4 srcPixel;
|
||||
|
||||
const uint blockThreadId = gl_LocalInvocationID.x;
|
||||
|
||||
const uint2 pixelsToLoadBase = gl_GlobalInvocationID.yz << 2u;
|
||||
|
||||
for (uint i = 0u; i < 4u; ++i) {
|
||||
const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i, blockThreadId);
|
||||
|
||||
const float4 value = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyzw;
|
||||
srcPixel[i] = params.p_channelIdx == 0 ? value.x : (params.p_channelIdx == 1 ? value.y : value.w);
|
||||
srcPixel[i] *= 255.0f;
|
||||
}
|
||||
|
||||
minVal = min3(srcPixel.x, srcPixel.y, srcPixel.z);
|
||||
maxVal = max3(srcPixel.x, 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] = float2(minVal, maxVal);
|
||||
g_mask[maskIdxBase] = uint2(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
|
||||
uint2 outputBytes;
|
||||
|
||||
#ifdef SNORM
|
||||
outputBytes.x =
|
||||
packSnorm4x8(float4(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(
|
||||
float4(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;
|
||||
|
||||
uint2 dstUV = gl_GlobalInvocationID.yz;
|
||||
imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u));
|
||||
}
|
||||
}
|
||||
718
engine/modules/betsy/bc6h.glsl
Normal file
718
engine/modules/betsy/bc6h.glsl
Normal file
|
|
@ -0,0 +1,718 @@
|
|||
#[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
|
||||
|
||||
#include "CrossPlatformSettings_piece_all.glsl"
|
||||
|
||||
#VERSION_DEFINES
|
||||
|
||||
float3 f32tof16(float3 value) {
|
||||
return float3(packHalf2x16(float2(value.x, 0.0)),
|
||||
packHalf2x16(float2(value.y, 0.0)),
|
||||
packHalf2x16(float2(value.z, 0.0)));
|
||||
}
|
||||
|
||||
float3 f16tof32(uint3 value) {
|
||||
return float3(unpackHalf2x16(value.x).x,
|
||||
unpackHalf2x16(value.y).x,
|
||||
unpackHalf2x16(value.z).x);
|
||||
}
|
||||
|
||||
float f32tof16(float value) {
|
||||
return packHalf2x16(float2(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 {
|
||||
float2 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(float3 a, float3 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;
|
||||
}
|
||||
#else
|
||||
float CalcMSLE(float3 a, float3 b) {
|
||||
float3 err = log2((b + 1.0f) / (a + 1.0f));
|
||||
err = err * err;
|
||||
return err.x + err.y + err.z;
|
||||
}
|
||||
#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
|
||||
float3 Quantize7(float3 x) {
|
||||
return (f32tof16(x) * 128.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Quantize9(float3 x) {
|
||||
return (f32tof16(x) * 512.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Quantize10(float3 x) {
|
||||
return (f32tof16(x) * 1024.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Unquantize7(float3 x) {
|
||||
return (x * 65536.0f + 0x8000) / 128.0f;
|
||||
}
|
||||
|
||||
float3 Unquantize9(float3 x) {
|
||||
return (x * 65536.0f + 0x8000) / 512.0f;
|
||||
}
|
||||
|
||||
float3 Unquantize10(float3 x) {
|
||||
return (x * 65536.0f + 0x8000) / 1024.0f;
|
||||
}
|
||||
|
||||
float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) {
|
||||
float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 4096.0f);
|
||||
return f16tof32(uint3(comp));
|
||||
}
|
||||
#else
|
||||
//SF
|
||||
|
||||
float3 cmpSign(float3 value) {
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 Quantize7(float3 x) {
|
||||
float3 signVal = cmpSign(x);
|
||||
return signVal * (f32tof16(abs(x)) * 64.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Quantize9(float3 x) {
|
||||
float3 signVal = cmpSign(x);
|
||||
return signVal * (f32tof16(abs(x)) * 256.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Quantize10(float3 x) {
|
||||
float3 signVal = cmpSign(x);
|
||||
return signVal * (f32tof16(abs(x)) * 512.0f) / (0x7bff + 1.0f);
|
||||
}
|
||||
|
||||
float3 Unquantize7(float3 x) {
|
||||
float3 signVal = sign(x);
|
||||
x = abs(x);
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 Unquantize9(float3 x) {
|
||||
float3 signVal = sign(x);
|
||||
x = abs(x);
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 Unquantize10(float3 x) {
|
||||
float3 signVal = sign(x);
|
||||
x = abs(x);
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) {
|
||||
float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f);
|
||||
return f16tof32(uint3(comp));
|
||||
}
|
||||
#endif
|
||||
|
||||
void Swap(inout float3 a, inout float3 b) {
|
||||
float3 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 float3 v1, uint mask, uint signFlag) {
|
||||
int3 v = int3(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 uint4 block, inout float blockMSLE, float3 texels[16]) {
|
||||
// compute endpoints (min/max RGB bbox)
|
||||
float3 blockMin = texels[0];
|
||||
float3 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
|
||||
float3 refinedBlockMin = blockMax;
|
||||
float3 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]);
|
||||
}
|
||||
|
||||
float3 logBlockMax = log2(blockMax + 1.0f);
|
||||
float3 logBlockMin = log2(blockMin + 1.0f);
|
||||
float3 logRefinedBlockMax = log2(refinedBlockMax + 1.0f);
|
||||
float3 logRefinedBlockMin = log2(refinedBlockMin + 1.0f);
|
||||
float3 logBlockMaxExt = (logBlockMax - logBlockMin) * (1.0f / 32.0f);
|
||||
logBlockMin += min(logRefinedBlockMin - logBlockMin, logBlockMaxExt);
|
||||
logBlockMax -= min(logBlockMax - logRefinedBlockMax, logBlockMaxExt);
|
||||
blockMin = exp2(logBlockMin) - 1.0f;
|
||||
blockMax = exp2(logBlockMax) - 1.0f;
|
||||
|
||||
float3 blockDir = blockMax - blockMin;
|
||||
blockDir = blockDir / (blockDir.x + blockDir.y + blockDir.z);
|
||||
|
||||
float3 endpoint0 = Quantize10(blockMin);
|
||||
float3 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)
|
||||
float3 endpoint0Unq = Unquantize10(endpoint0);
|
||||
float3 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);
|
||||
float3 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(float3 PointOnLine, float3 LineDirection, float3 Point) {
|
||||
float3 w = Point - PointOnLine;
|
||||
float3 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, float3 texels[16]) {
|
||||
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
|
||||
float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
|
||||
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
|
||||
float3 p1BlockMax = float3(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]);
|
||||
}
|
||||
}
|
||||
|
||||
float3 p0BlockDir = normalize(p0BlockMax - p0BlockMin);
|
||||
float3 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 uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) {
|
||||
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
|
||||
float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
|
||||
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
|
||||
float3 p1BlockMax = float3(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]);
|
||||
}
|
||||
}
|
||||
|
||||
float3 p0BlockDir = p0BlockMax - p0BlockMin;
|
||||
float3 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;
|
||||
}
|
||||
|
||||
float3 endpoint760 = floor(Quantize7(p0BlockMin));
|
||||
float3 endpoint761 = floor(Quantize7(p0BlockMax));
|
||||
float3 endpoint762 = floor(Quantize7(p1BlockMin));
|
||||
float3 endpoint763 = floor(Quantize7(p1BlockMax));
|
||||
|
||||
float3 endpoint950 = floor(Quantize9(p0BlockMin));
|
||||
float3 endpoint951 = floor(Quantize9(p0BlockMax));
|
||||
float3 endpoint952 = floor(Quantize9(p1BlockMin));
|
||||
float3 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
|
||||
|
||||
float3 endpoint760Unq = Unquantize7(endpoint760);
|
||||
float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761);
|
||||
float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762);
|
||||
float3 endpoint763Unq = Unquantize7(endpoint760 + endpoint763);
|
||||
float3 endpoint950Unq = Unquantize9(endpoint950);
|
||||
float3 endpoint951Unq = Unquantize9(endpoint950 + endpoint951);
|
||||
float3 endpoint952Unq = Unquantize9(endpoint950 + endpoint952);
|
||||
float3 endpoint953Unq = Unquantize9(endpoint950 + endpoint953);
|
||||
|
||||
float msle76 = 0.0f;
|
||||
float msle95 = 0.0f;
|
||||
for (uint i = 0u; i < 16u; ++i) {
|
||||
uint paletteID = Pattern(pattern, i);
|
||||
|
||||
float3 tmp760Unq = paletteID == 0u ? endpoint760Unq : endpoint762Unq;
|
||||
float3 tmp761Unq = paletteID == 0u ? endpoint761Unq : endpoint763Unq;
|
||||
float3 tmp950Unq = paletteID == 0u ? endpoint950Unq : endpoint952Unq;
|
||||
float3 tmp951Unq = paletteID == 0u ? endpoint951Unq : endpoint953Unq;
|
||||
|
||||
float weight = floor((indices[i] * 64.0f) / 7.0f + 0.5f);
|
||||
float3 texelUnc76 = FinishUnquantize(tmp760Unq, tmp761Unq, weight);
|
||||
float3 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 = uint4(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
|
||||
float2 uv = gl_GlobalInvocationID.xy * params.p_textureSizeRcp * 4.0f + params.p_textureSizeRcp;
|
||||
float2 block0UV = uv;
|
||||
float2 block1UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 0.0f);
|
||||
float2 block2UV = uv + float2(0.0f, 2.0f * params.p_textureSizeRcp.y);
|
||||
float2 block3UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 2.0f * params.p_textureSizeRcp.y);
|
||||
float4 block0X = OGRE_GatherRed(srcTexture, pointSampler, block0UV);
|
||||
float4 block1X = OGRE_GatherRed(srcTexture, pointSampler, block1UV);
|
||||
float4 block2X = OGRE_GatherRed(srcTexture, pointSampler, block2UV);
|
||||
float4 block3X = OGRE_GatherRed(srcTexture, pointSampler, block3UV);
|
||||
float4 block0Y = OGRE_GatherGreen(srcTexture, pointSampler, block0UV);
|
||||
float4 block1Y = OGRE_GatherGreen(srcTexture, pointSampler, block1UV);
|
||||
float4 block2Y = OGRE_GatherGreen(srcTexture, pointSampler, block2UV);
|
||||
float4 block3Y = OGRE_GatherGreen(srcTexture, pointSampler, block3UV);
|
||||
float4 block0Z = OGRE_GatherBlue(srcTexture, pointSampler, block0UV);
|
||||
float4 block1Z = OGRE_GatherBlue(srcTexture, pointSampler, block1UV);
|
||||
float4 block2Z = OGRE_GatherBlue(srcTexture, pointSampler, block2UV);
|
||||
float4 block3Z = OGRE_GatherBlue(srcTexture, pointSampler, block3UV);
|
||||
|
||||
float3 texels[16];
|
||||
texels[0] = float3(block0X.w, block0Y.w, block0Z.w);
|
||||
texels[1] = float3(block0X.z, block0Y.z, block0Z.z);
|
||||
texels[2] = float3(block1X.w, block1Y.w, block1Z.w);
|
||||
texels[3] = float3(block1X.z, block1Y.z, block1Z.z);
|
||||
texels[4] = float3(block0X.x, block0Y.x, block0Z.x);
|
||||
texels[5] = float3(block0X.y, block0Y.y, block0Z.y);
|
||||
texels[6] = float3(block1X.x, block1Y.x, block1Z.x);
|
||||
texels[7] = float3(block1X.y, block1Y.y, block1Z.y);
|
||||
texels[8] = float3(block2X.w, block2Y.w, block2Z.w);
|
||||
texels[9] = float3(block2X.z, block2Y.z, block2Z.z);
|
||||
texels[10] = float3(block3X.w, block3Y.w, block3Z.w);
|
||||
texels[11] = float3(block3X.z, block3Y.z, block3Z.z);
|
||||
texels[12] = float3(block2X.x, block2Y.x, block2Z.x);
|
||||
texels[13] = float3(block2X.y, block2Y.y, block2Z.y);
|
||||
texels[14] = float3(block3X.x, block3Y.x, block3Z.x);
|
||||
texels[15] = float3(block3X.y, block3Y.y, block3Z.y);
|
||||
|
||||
uint4 block = uint4(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, int2(gl_GlobalInvocationID.xy), block);
|
||||
}
|
||||
1061
engine/modules/betsy/betsy_bc1.h
Normal file
1061
engine/modules/betsy/betsy_bc1.h
Normal file
File diff suppressed because it is too large
Load diff
6
engine/modules/betsy/config.py
Normal file
6
engine/modules/betsy/config.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def can_build(env, platform):
|
||||
return env.editor_build
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
742
engine/modules/betsy/image_compress_betsy.cpp
Normal file
742
engine/modules/betsy/image_compress_betsy.cpp
Normal file
|
|
@ -0,0 +1,742 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (compress_rd != nullptr) {
|
||||
// Free the RD (and RCD if necessary).
|
||||
memdelete(compress_rd);
|
||||
compress_rd = nullptr;
|
||||
if (compress_rcd != nullptr) {
|
||||
memdelete(compress_rcd);
|
||||
compress_rcd = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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()) {
|
||||
Vector<uint8_t> data;
|
||||
data.resize(1024 * 4);
|
||||
memcpy(data.ptrw(), dxt1_encoding_table, 1024 * 4);
|
||||
|
||||
dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, data);
|
||||
}
|
||||
|
||||
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(r_img->get_width(), r_img->get_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++) {
|
||||
int64_t ofs, size;
|
||||
int width, height;
|
||||
r_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);
|
||||
|
||||
// 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;
|
||||
|
||||
// Create a buffer filled with the source mip layer data.
|
||||
src_image_ptr[0].resize(size);
|
||||
memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + ofs, 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(r_img->get_width(), r_img->get_height(), dest_format, i);
|
||||
|
||||
memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());
|
||||
|
||||
// Free the source and dest texture.
|
||||
compress_rd->free(src_texture);
|
||||
compress_rd->free(dst_texture_rid);
|
||||
}
|
||||
|
||||
src_images.clear();
|
||||
|
||||
// Set the compressed data to the image.
|
||||
r_img->set_data(r_img->get_width(), r_img->get_height(), r_img->has_mipmaps(), dest_format, dst_data);
|
||||
|
||||
print_verbose(
|
||||
vformat("Betsy: Encoding a %dx%d image with %d mipmaps as %s took %d ms.",
|
||||
r_img->get_width(),
|
||||
r_img->get_height(),
|
||||
r_img->get_mipmap_count(),
|
||||
Image::get_format_name(dest_format),
|
||||
OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
132
engine/modules/betsy/image_compress_betsy.h
Normal file
132
engine/modules/betsy/image_compress_betsy.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IMAGE_COMPRESS_BETSY_H
|
||||
#define IMAGE_COMPRESS_BETSY_H
|
||||
|
||||
#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;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // IMAGE_COMPRESS_BETSY_H
|
||||
50
engine/modules/betsy/register_types.cpp
Normal file
50
engine/modules/betsy/register_types.cpp
Normal 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();
|
||||
}
|
||||
39
engine/modules/betsy/register_types.h
Normal file
39
engine/modules/betsy/register_types.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef BETSY_REGISTER_TYPES_H
|
||||
#define BETSY_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_betsy_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_betsy_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // BETSY_REGISTER_TYPES_H
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
|
|||
|
|
@ -59,30 +59,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
|
|||
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;
|
||||
|
||||
// Check whether we can load it
|
||||
|
||||
if (bits_per_pixel == 1) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE,
|
||||
vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE,
|
||||
vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height)));
|
||||
|
||||
} else if (bits_per_pixel == 2) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 4 != 0, ERR_UNAVAILABLE,
|
||||
vformat("2-bpp BMP images must have a width that is a multiple of 4, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 4 != 0, ERR_UNAVAILABLE,
|
||||
vformat("2-bpp BMP images must have a height that is a multiple of 4, but the imported BMP is %d pixels tall.", int(height)));
|
||||
|
||||
} else if (bits_per_pixel == 4) {
|
||||
// Requires bit unpacking...
|
||||
ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE,
|
||||
vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width)));
|
||||
ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE,
|
||||
vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height)));
|
||||
}
|
||||
|
||||
// Image data (might be indexed)
|
||||
Vector<uint8_t> data;
|
||||
int data_len = 0;
|
||||
|
|
@ -98,55 +74,32 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
|
|||
uint8_t *data_w = data.ptrw();
|
||||
uint8_t *write_buffer = data_w;
|
||||
|
||||
const uint32_t width_bytes = width * bits_per_pixel / 8;
|
||||
const uint32_t line_width = (width_bytes + 3) & ~3;
|
||||
const uint32_t width_bytes = (width * bits_per_pixel + 7) / 8;
|
||||
const uint32_t line_width = (width_bytes + 3) & ~3; // Padded to 4 bytes.
|
||||
|
||||
// The actual data traversal is determined by
|
||||
// the data width in case of 8/4/2/1 bit images
|
||||
const uint32_t w = bits_per_pixel >= 16 ? width : width_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 < w; j++) {
|
||||
ERR_FAIL_COND_V(line_ptr >= end_buffer, ERR_FILE_CORRUPT);
|
||||
for (unsigned int j = 0; j < width; j++) {
|
||||
switch (bits_per_pixel) {
|
||||
case 1: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 8))) & 0x01;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 7) & 1;
|
||||
write_buffer[index + 1] = (color_index >> 6) & 1;
|
||||
write_buffer[index + 2] = (color_index >> 5) & 1;
|
||||
write_buffer[index + 3] = (color_index >> 4) & 1;
|
||||
write_buffer[index + 4] = (color_index >> 3) & 1;
|
||||
write_buffer[index + 5] = (color_index >> 2) & 1;
|
||||
write_buffer[index + 6] = (color_index >> 1) & 1;
|
||||
write_buffer[index + 7] = (color_index >> 0) & 1;
|
||||
|
||||
index += 8;
|
||||
line_ptr += 1;
|
||||
index++;
|
||||
} break;
|
||||
case 2: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 4))) & 0x03;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 6) & 3;
|
||||
write_buffer[index + 1] = (color_index >> 4) & 3;
|
||||
write_buffer[index + 2] = (color_index >> 2) & 3;
|
||||
write_buffer[index + 3] = color_index & 3;
|
||||
|
||||
index += 4;
|
||||
line_ptr += 1;
|
||||
index++;
|
||||
} break;
|
||||
case 4: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 2))) & 0x0f;
|
||||
|
||||
write_buffer[index + 0] = (color_index >> 4) & 0x0f;
|
||||
write_buffer[index + 1] = color_index & 0x0f;
|
||||
|
||||
index += 2;
|
||||
line_ptr += 1;
|
||||
index++;
|
||||
} break;
|
||||
case 8: {
|
||||
uint8_t color_index = *line_ptr;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_camera = env_modules.Clone()
|
||||
|
||||
if env["platform"] == "windows":
|
||||
if env["platform"] in ["windows", "macos", "linuxbsd"]:
|
||||
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, "register_types.cpp")
|
||||
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")
|
||||
|
||||
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")
|
||||
|
|
|
|||
212
engine/modules/camera/buffer_decoder.cpp
Normal file
212
engine/modules/camera/buffer_decoder.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
116
engine/modules/camera/buffer_decoder.h
Normal file
116
engine/modules/camera/buffer_decoder.h
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef BUFFER_DECODER_H
|
||||
#define BUFFER_DECODER_H
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
#endif // BUFFER_DECODER_H
|
||||
365
engine/modules/camera/camera_feed_linux.cpp
Normal file
365
engine/modules/camera/camera_feed_linux.cpp
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
/**************************************************************************/
|
||||
/* 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(), 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));
|
||||
}
|
||||
|
||||
emit_signal(SNAME("frame_changed"));
|
||||
}
|
||||
|
||||
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(), 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(), 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(¶m, 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, ¶m) == -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();
|
||||
}
|
||||
}
|
||||
78
engine/modules/camera/camera_feed_linux.h
Normal file
78
engine/modules/camera/camera_feed_linux.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CAMERA_FEED_LINUX_H
|
||||
#define CAMERA_FEED_LINUX_H
|
||||
|
||||
#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 {
|
||||
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();
|
||||
void deactivate_feed();
|
||||
bool set_format(int p_index, const Dictionary &p_parameters);
|
||||
Array get_formats() const;
|
||||
FeedFormat get_format() const;
|
||||
|
||||
CameraFeedLinux(const String &p_device_name);
|
||||
virtual ~CameraFeedLinux();
|
||||
};
|
||||
|
||||
#endif // CAMERA_FEED_LINUX_H
|
||||
172
engine/modules/camera/camera_linux.cpp
Normal file
172
engine/modules/camera/camera_linux.cpp
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
DIR *devices = opendir("/dev");
|
||||
|
||||
if (devices) {
|
||||
struct dirent *device;
|
||||
|
||||
while ((device = readdir(devices)) != nullptr) {
|
||||
if (strncmp(device->d_name, "video", 5) != 0) {
|
||||
continue;
|
||||
}
|
||||
String device_name = String("/dev/") + String(device->d_name);
|
||||
if (!_has_device(device_name)) {
|
||||
_add_device(device_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(devices);
|
||||
}
|
||||
|
||||
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(), &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!S_ISCHR(s.st_mode)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return open(p_device_name.ascii(), 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;
|
||||
}
|
||||
|
||||
CameraLinux::CameraLinux() {
|
||||
camera_thread.start(CameraLinux::camera_thread_func, this);
|
||||
}
|
||||
|
||||
CameraLinux::~CameraLinux() {
|
||||
exit_flag.set();
|
||||
camera_thread.wait_to_finish();
|
||||
}
|
||||
60
engine/modules/camera/camera_linux.h
Normal file
60
engine/modules/camera/camera_linux.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef CAMERA_LINUX_H
|
||||
#define CAMERA_LINUX_H
|
||||
|
||||
#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();
|
||||
~CameraLinux();
|
||||
};
|
||||
|
||||
#endif // CAMERA_LINUX_H
|
||||
|
|
@ -182,7 +182,7 @@
|
|||
}
|
||||
|
||||
// set our texture...
|
||||
feed->set_YCbCr_imgs(img[0], img[1]);
|
||||
feed->set_ycbcr_images(img[0], img[1]);
|
||||
}
|
||||
|
||||
// and unlock
|
||||
|
|
@ -212,12 +212,12 @@ public:
|
|||
|
||||
AVCaptureDevice *CameraFeedMacOS::get_device() const {
|
||||
return device;
|
||||
};
|
||||
}
|
||||
|
||||
CameraFeedMacOS::CameraFeedMacOS() {
|
||||
device = nullptr;
|
||||
capture_session = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
|
||||
device = p_device;
|
||||
|
|
@ -231,7 +231,7 @@ void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
|
|||
} else if ([p_device position] == AVCaptureDevicePositionFront) {
|
||||
position = CameraFeed::FEED_FRONT;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
bool CameraFeedMacOS::activate_feed() {
|
||||
if (capture_session) {
|
||||
|
|
@ -257,7 +257,7 @@ bool CameraFeedMacOS::activate_feed() {
|
|||
};
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
void CameraFeedMacOS::deactivate_feed() {
|
||||
// end camera capture if we have one
|
||||
|
|
@ -265,7 +265,7 @@ void CameraFeedMacOS::deactivate_feed() {
|
|||
[capture_session cleanup];
|
||||
capture_session = nullptr;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyDeviceNotifications - This is a little helper class gets notifications
|
||||
|
|
@ -307,16 +307,25 @@ MyDeviceNotifications *device_notifications = nil;
|
|||
// CameraMacOS - Subclass for our camera server on macOS
|
||||
|
||||
void CameraMacOS::update_feeds() {
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
|
||||
AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
NSArray *devices = session.devices;
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
|
||||
AVCaptureDeviceDiscoverySession *session;
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 140000
|
||||
// Avoid deprecated warning if the minimum SDK is 14.0.
|
||||
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
#else
|
||||
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
#endif
|
||||
NSArray<AVCaptureDevice *> *devices = session.devices;
|
||||
#else
|
||||
NSArray<AVCaptureDevice *> *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 ;)
|
||||
|
|
@ -328,6 +337,9 @@ void CameraMacOS::update_feeds() {
|
|||
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;
|
||||
};
|
||||
|
|
@ -345,7 +357,7 @@ void CameraMacOS::update_feeds() {
|
|||
add_feed(newfeed);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
CameraMacOS::CameraMacOS() {
|
||||
// Find available cameras we have at this time
|
||||
|
|
@ -353,4 +365,4 @@ CameraMacOS::CameraMacOS() {
|
|||
|
||||
// should only have one of these....
|
||||
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ CameraFeedWindows::~CameraFeedWindows() {
|
|||
};
|
||||
|
||||
///@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
|
||||
|
|
@ -91,4 +91,4 @@ CameraWindows::CameraWindows() {
|
|||
add_active_cameras();
|
||||
|
||||
// need to add something that will react to devices being connected/removed...
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
def can_build(env, platform):
|
||||
return platform == "macos" or platform == "windows"
|
||||
import sys
|
||||
|
||||
if sys.platform.startswith("freebsd"):
|
||||
return False
|
||||
return platform == "macos" or platform == "windows" or platform == "linuxbsd"
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@
|
|||
|
||||
#include "register_types.h"
|
||||
|
||||
#if defined(LINUXBSD_ENABLED)
|
||||
#include "camera_linux.h"
|
||||
#endif
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
#include "camera_win.h"
|
||||
#endif
|
||||
|
|
@ -42,6 +45,9 @@ void initialize_camera_module(ModuleInitializationLevel p_level) {
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined(LINUXBSD_ENABLED)
|
||||
CameraServer::make_default<CameraLinux>();
|
||||
#endif
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
CameraServer::make_default<CameraWindows>();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,11 +1,52 @@
|
|||
#!/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/smoothing.cpp",
|
||||
"src/sort.cpp",
|
||||
"src/subdivision.cpp",
|
||||
]
|
||||
|
||||
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
|
||||
env_csg.Prepend(CPPPATH=[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
|
||||
env_csg.add_source_files(env.modules_sources, "*.cpp")
|
||||
|
||||
module_obj = []
|
||||
|
||||
env_csg.add_source_files(module_obj, "*.cpp")
|
||||
|
||||
if env.editor_build:
|
||||
env_csg.add_source_files(env.modules_sources, "editor/*.cpp")
|
||||
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)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -32,13 +32,10 @@
|
|||
#define CSG_H
|
||||
|
||||
#include "core/math/aabb.h"
|
||||
#include "core/math/plane.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/list.h"
|
||||
#include "core/templates/oa_hash_map.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
|
|
@ -55,150 +52,18 @@ struct CSGBrush {
|
|||
Vector<Face> faces;
|
||||
Vector<Ref<Material>> materials;
|
||||
|
||||
inline void _regen_face_aabbs();
|
||||
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);
|
||||
};
|
||||
|
||||
struct CSGBrushOperation {
|
||||
enum Operation {
|
||||
OPERATION_UNION,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_SUBTRACTION,
|
||||
};
|
||||
|
||||
void merge_brushes(Operation p_operation, const CSGBrush &p_brush_a, const CSGBrush &p_brush_b, CSGBrush &r_merged_brush, float p_vertex_snap);
|
||||
|
||||
struct MeshMerge {
|
||||
struct Face {
|
||||
bool from_b = false;
|
||||
bool inside = false;
|
||||
int points[3] = {};
|
||||
Vector2 uvs[3];
|
||||
bool smooth = false;
|
||||
bool invert = false;
|
||||
int material_idx = 0;
|
||||
};
|
||||
|
||||
struct FaceBVH {
|
||||
int face = 0;
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
int next = 0;
|
||||
Vector3 center;
|
||||
AABB aabb;
|
||||
};
|
||||
|
||||
struct FaceBVHCmpX {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.x < p_right->center.x;
|
||||
}
|
||||
};
|
||||
|
||||
struct FaceBVHCmpY {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.y < p_right->center.y;
|
||||
}
|
||||
};
|
||||
struct FaceBVHCmpZ {
|
||||
_FORCE_INLINE_ bool operator()(const FaceBVH *p_left, const FaceBVH *p_right) const {
|
||||
return p_left->center.z < p_right->center.z;
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexKey {
|
||||
int32_t x, y, z;
|
||||
_FORCE_INLINE_ bool operator<(const VertexKey &p_key) const {
|
||||
if (x == p_key.x) {
|
||||
if (y == p_key.y) {
|
||||
return z < p_key.z;
|
||||
} else {
|
||||
return y < p_key.y;
|
||||
}
|
||||
} else {
|
||||
return x < p_key.x;
|
||||
}
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const VertexKey &p_key) const {
|
||||
return (x == p_key.x && y == p_key.y && z == p_key.z);
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexKeyHash {
|
||||
static _FORCE_INLINE_ uint32_t hash(const VertexKey &p_vk) {
|
||||
uint32_t h = hash_murmur3_one_32(p_vk.x);
|
||||
h = hash_murmur3_one_32(p_vk.y, h);
|
||||
h = hash_murmur3_one_32(p_vk.z, h);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
struct Intersection {
|
||||
bool found = false;
|
||||
real_t conormal = FLT_MAX;
|
||||
real_t distance_squared = FLT_MAX;
|
||||
real_t origin_angle = FLT_MAX;
|
||||
};
|
||||
|
||||
struct IntersectionDistance {
|
||||
bool is_conormal;
|
||||
real_t distance_squared;
|
||||
};
|
||||
|
||||
Vector<Vector3> points;
|
||||
Vector<Face> faces;
|
||||
HashMap<Ref<Material>, int> materials;
|
||||
HashMap<Vector3, int> vertex_map;
|
||||
OAHashMap<VertexKey, int, VertexKeyHash> snap_cache;
|
||||
float vertex_snap = 0.0;
|
||||
|
||||
inline void _add_distance(List<IntersectionDistance> &r_intersectionsA, List<IntersectionDistance> &r_intersectionsB, bool p_from_B, real_t p_distance, bool p_is_conormal) const;
|
||||
inline bool _bvh_inside(FaceBVH *r_facebvhptr, int p_max_depth, int p_bvh_first, int p_face_idx) const;
|
||||
inline int _create_bvh(FaceBVH *r_facebvhptr, FaceBVH **r_facebvhptrptr, int p_from, int p_size, int p_depth, int &r_max_depth, int &r_max_alloc);
|
||||
|
||||
void add_face(const Vector3 p_points[3], const Vector2 p_uvs[3], bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
void mark_inside_faces();
|
||||
};
|
||||
|
||||
struct Build2DFaces {
|
||||
struct Vertex2D {
|
||||
Vector2 point;
|
||||
Vector2 uv;
|
||||
};
|
||||
|
||||
struct Face2D {
|
||||
int vertex_idx[3] = {};
|
||||
};
|
||||
|
||||
Vector<Vertex2D> vertices;
|
||||
Vector<Face2D> faces;
|
||||
Plane plane;
|
||||
Transform3D to_2D;
|
||||
Transform3D to_3D;
|
||||
float vertex_snap2 = 0.0;
|
||||
|
||||
inline int _get_point_idx(const Vector2 &p_point);
|
||||
inline int _add_vertex(const Vertex2D &p_vertex);
|
||||
inline void _add_vertex_idx_sorted(Vector<int> &r_vertex_indices, int p_new_vertex_index);
|
||||
inline void _merge_faces(const Vector<int> &p_segment_indices);
|
||||
inline void _find_edge_intersections(const Vector2 p_segment_points[2], Vector<int> &r_segment_indices);
|
||||
inline int _insert_point(const Vector2 &p_point);
|
||||
|
||||
void insert(const CSGBrush &p_brush, int p_brush_face);
|
||||
void addFacesToMesh(MeshMerge &r_mesh_merge, bool p_smooth, bool p_invert, const Ref<Material> &p_material, bool p_from_b);
|
||||
|
||||
Build2DFaces() {}
|
||||
Build2DFaces(const CSGBrush &p_brush, int p_brush_face, float p_vertex_snap2);
|
||||
};
|
||||
|
||||
struct Build2DFaceCollection {
|
||||
HashMap<int, Build2DFaces> build2DFacesA;
|
||||
HashMap<int, Build2DFaces> build2DFacesB;
|
||||
};
|
||||
|
||||
void update_faces(const CSGBrush &p_brush_a, const int p_face_idx_a, const CSGBrush &p_brush_b, const int p_face_idx_b, Build2DFaceCollection &p_collection, float p_vertex_snap);
|
||||
};
|
||||
|
||||
#endif // CSG_H
|
||||
|
|
|
|||
|
|
@ -30,7 +30,48 @@
|
|||
|
||||
#include "csg_shape.h"
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
#include "core/io/json.h"
|
||||
#endif // DEV_ENABLED
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h"
|
||||
#include "scene/resources/navigation_mesh.h"
|
||||
#include "servers/navigation_server_3d.h"
|
||||
|
||||
#include <manifold/manifold.h>
|
||||
|
||||
Callable CSGShape3D::_navmesh_source_geometry_parsing_callback;
|
||||
RID CSGShape3D::_navmesh_source_geometry_parser;
|
||||
|
||||
void CSGShape3D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&CSGShape3D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer3D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer3D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShape3D::navmesh_parse_source_geometry(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) {
|
||||
CSGShape3D *csgshape3d = Object::cast_to<CSGShape3D>(p_node);
|
||||
|
||||
if (csgshape3d == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationMesh::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
|
||||
uint32_t parsed_collision_mask = p_navigation_mesh->get_collision_mask();
|
||||
|
||||
if (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_MESH_INSTANCES || (parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS && csgshape3d->is_using_collision() && (csgshape3d->get_collision_layer() & parsed_collision_mask)) || parsed_geometry_type == NavigationMesh::PARSED_GEOMETRY_BOTH) {
|
||||
Array meshes = csgshape3d->get_meshes();
|
||||
if (!meshes.is_empty()) {
|
||||
Ref<Mesh> mesh = meshes[1];
|
||||
if (mesh.is_valid()) {
|
||||
p_source_geometry_data->add_mesh(mesh, csgshape3d->get_global_transform());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSGShape3D::set_use_collision(bool p_enable) {
|
||||
if (use_collision == p_enable) {
|
||||
|
|
@ -125,6 +166,16 @@ bool CSGShape3D::get_collision_mask_value(int p_layer_number) const {
|
|||
return get_collision_mask() & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
RID CSGShape3D::_get_root_collision_instance() const {
|
||||
if (root_collision_instance.is_valid()) {
|
||||
return root_collision_instance;
|
||||
} else if (parent_shape) {
|
||||
return parent_shape->_get_root_collision_instance();
|
||||
}
|
||||
|
||||
return RID();
|
||||
}
|
||||
|
||||
void CSGShape3D::set_collision_priority(real_t p_priority) {
|
||||
collision_priority = p_priority;
|
||||
if (root_collision_instance.is_valid()) {
|
||||
|
|
@ -140,6 +191,7 @@ bool CSGShape3D::is_root_shape() const {
|
|||
return !parent_shape;
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void CSGShape3D::set_snap(float p_snap) {
|
||||
if (snap == p_snap) {
|
||||
return;
|
||||
|
|
@ -152,6 +204,7 @@ void CSGShape3D::set_snap(float p_snap) {
|
|||
float CSGShape3D::get_snap() const {
|
||||
return snap;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void CSGShape3D::_make_dirty(bool p_parent_removing) {
|
||||
if ((p_parent_removing || is_root_shape()) && !dirty) {
|
||||
|
|
@ -167,78 +220,279 @@ void CSGShape3D::_make_dirty(bool p_parent_removing) {
|
|||
dirty = true;
|
||||
}
|
||||
|
||||
CSGBrush *CSGShape3D::_get_brush() {
|
||||
if (dirty) {
|
||||
if (brush) {
|
||||
memdelete(brush);
|
||||
}
|
||||
brush = nullptr;
|
||||
enum ManifoldProperty {
|
||||
MANIFOLD_PROPERTY_POSITION_X = 0,
|
||||
MANIFOLD_PROPERTY_POSITION_Y,
|
||||
MANIFOLD_PROPERTY_POSITION_Z,
|
||||
MANIFOLD_PROPERTY_INVERT,
|
||||
MANIFOLD_PROPERTY_SMOOTH_GROUP,
|
||||
MANIFOLD_PROPERTY_UV_X_0,
|
||||
MANIFOLD_PROPERTY_UV_Y_0,
|
||||
MANIFOLD_PROPERTY_MAX
|
||||
};
|
||||
|
||||
CSGBrush *n = _build_brush();
|
||||
static void _unpack_manifold(
|
||||
const manifold::Manifold &p_manifold,
|
||||
const HashMap<int32_t, Ref<Material>> &p_mesh_materials,
|
||||
CSGBrush *r_mesh_merge) {
|
||||
manifold::MeshGL64 mesh = p_manifold.GetMeshGL64();
|
||||
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
if (!child->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
constexpr int32_t order[3] = { 0, 2, 1 };
|
||||
|
||||
CSGBrush *n2 = child->_get_brush();
|
||||
if (!n2) {
|
||||
continue;
|
||||
}
|
||||
if (!n) {
|
||||
n = memnew(CSGBrush);
|
||||
|
||||
n->copy_from(*n2, child->get_transform());
|
||||
|
||||
} else {
|
||||
CSGBrush *nn = memnew(CSGBrush);
|
||||
CSGBrush *nn2 = memnew(CSGBrush);
|
||||
nn2->copy_from(*n2, child->get_transform());
|
||||
|
||||
CSGBrushOperation bop;
|
||||
|
||||
switch (child->get_operation()) {
|
||||
case CSGShape3D::OPERATION_UNION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_UNION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_INTERSECTION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
bop.merge_brushes(CSGBrushOperation::OPERATION_SUBTRACTION, *n, *nn2, *nn, snap);
|
||||
break;
|
||||
}
|
||||
memdelete(n);
|
||||
memdelete(nn2);
|
||||
n = nn;
|
||||
}
|
||||
for (size_t run_i = 0; run_i < mesh.runIndex.size() - 1; run_i++) {
|
||||
uint32_t original_id = -1;
|
||||
if (run_i < mesh.runOriginalID.size()) {
|
||||
original_id = mesh.runOriginalID[run_i];
|
||||
}
|
||||
|
||||
if (n) {
|
||||
AABB aabb;
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (i == 0 && j == 0) {
|
||||
aabb.position = n->faces[i].vertices[j];
|
||||
} else {
|
||||
aabb.expand_to(n->faces[i].vertices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
node_aabb = aabb;
|
||||
} else {
|
||||
node_aabb = AABB();
|
||||
Ref<Material> material;
|
||||
if (p_mesh_materials.has(original_id)) {
|
||||
material = p_mesh_materials[original_id];
|
||||
}
|
||||
// Find or reserve a material ID in the brush.
|
||||
int32_t material_id = r_mesh_merge->materials.find(material);
|
||||
if (material_id == -1) {
|
||||
material_id = r_mesh_merge->materials.size();
|
||||
r_mesh_merge->materials.push_back(material);
|
||||
}
|
||||
|
||||
brush = n;
|
||||
size_t begin = mesh.runIndex[run_i];
|
||||
size_t end = mesh.runIndex[run_i + 1];
|
||||
for (size_t vert_i = begin; vert_i < end; vert_i += 3) {
|
||||
CSGBrush::Face face;
|
||||
face.material = material_id;
|
||||
int32_t first_property_index = mesh.triVerts[vert_i + order[0]];
|
||||
face.smooth = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_SMOOTH_GROUP] > 0.5f;
|
||||
face.invert = mesh.vertProperties[first_property_index * mesh.numProp + MANIFOLD_PROPERTY_INVERT] > 0.5f;
|
||||
|
||||
dirty = false;
|
||||
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
|
||||
int32_t property_i = mesh.triVerts[vert_i + order[tri_order_i]];
|
||||
ERR_FAIL_COND_MSG(property_i * mesh.numProp >= mesh.vertProperties.size(), "Invalid index into vertex properties");
|
||||
face.vertices[tri_order_i] = Vector3(
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_X],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Y],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_POSITION_Z]);
|
||||
face.uvs[tri_order_i] = Vector2(
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_X_0],
|
||||
mesh.vertProperties[property_i * mesh.numProp + MANIFOLD_PROPERTY_UV_Y_0]);
|
||||
}
|
||||
r_mesh_merge->faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
r_mesh_merge->_regen_face_aabbs();
|
||||
}
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
static String _export_meshgl_as_json(const manifold::MeshGL64 &p_mesh) {
|
||||
Dictionary mesh_dict;
|
||||
mesh_dict["numProp"] = p_mesh.numProp;
|
||||
|
||||
Array vert_properties;
|
||||
for (const double &val : p_mesh.vertProperties) {
|
||||
vert_properties.append(val);
|
||||
}
|
||||
mesh_dict["vertProperties"] = vert_properties;
|
||||
|
||||
Array tri_verts;
|
||||
for (const uint64_t &val : p_mesh.triVerts) {
|
||||
tri_verts.append(val);
|
||||
}
|
||||
mesh_dict["triVerts"] = tri_verts;
|
||||
|
||||
Array merge_from_vert;
|
||||
for (const uint64_t &val : p_mesh.mergeFromVert) {
|
||||
merge_from_vert.append(val);
|
||||
}
|
||||
mesh_dict["mergeFromVert"] = merge_from_vert;
|
||||
|
||||
Array merge_to_vert;
|
||||
for (const uint64_t &val : p_mesh.mergeToVert) {
|
||||
merge_to_vert.append(val);
|
||||
}
|
||||
mesh_dict["mergeToVert"] = merge_to_vert;
|
||||
|
||||
Array run_index;
|
||||
for (const uint64_t &val : p_mesh.runIndex) {
|
||||
run_index.append(val);
|
||||
}
|
||||
mesh_dict["runIndex"] = run_index;
|
||||
|
||||
Array run_original_id;
|
||||
for (const uint32_t &val : p_mesh.runOriginalID) {
|
||||
run_original_id.append(val);
|
||||
}
|
||||
mesh_dict["runOriginalID"] = run_original_id;
|
||||
|
||||
Array run_transform;
|
||||
for (const double &val : p_mesh.runTransform) {
|
||||
run_transform.append(val);
|
||||
}
|
||||
mesh_dict["runTransform"] = run_transform;
|
||||
|
||||
Array face_id;
|
||||
for (const uint64_t &val : p_mesh.faceID) {
|
||||
face_id.append(val);
|
||||
}
|
||||
mesh_dict["faceID"] = face_id;
|
||||
|
||||
Array halfedge_tangent;
|
||||
for (const double &val : p_mesh.halfedgeTangent) {
|
||||
halfedge_tangent.append(val);
|
||||
}
|
||||
mesh_dict["halfedgeTangent"] = halfedge_tangent;
|
||||
|
||||
mesh_dict["tolerance"] = p_mesh.tolerance;
|
||||
|
||||
String json_string = JSON::stringify(mesh_dict);
|
||||
return json_string;
|
||||
}
|
||||
#endif // DEV_ENABLED
|
||||
|
||||
static void _pack_manifold(
|
||||
const CSGBrush *const p_mesh_merge,
|
||||
manifold::Manifold &r_manifold,
|
||||
HashMap<int32_t, Ref<Material>> &p_mesh_materials,
|
||||
CSGShape3D *p_csg_shape) {
|
||||
ERR_FAIL_NULL_MSG(p_mesh_merge, "p_mesh_merge is null");
|
||||
ERR_FAIL_NULL_MSG(p_csg_shape, "p_shape is null");
|
||||
HashMap<uint32_t, Vector<CSGBrush::Face>> faces_by_material;
|
||||
for (int face_i = 0; face_i < p_mesh_merge->faces.size(); face_i++) {
|
||||
const CSGBrush::Face &face = p_mesh_merge->faces[face_i];
|
||||
faces_by_material[face.material].push_back(face);
|
||||
}
|
||||
|
||||
manifold::MeshGL64 mesh;
|
||||
mesh.numProp = MANIFOLD_PROPERTY_MAX;
|
||||
mesh.runOriginalID.reserve(faces_by_material.size());
|
||||
mesh.runIndex.reserve(faces_by_material.size() + 1);
|
||||
mesh.vertProperties.reserve(p_mesh_merge->faces.size() * 3 * MANIFOLD_PROPERTY_MAX);
|
||||
|
||||
// Make a run of triangles for each material.
|
||||
for (const KeyValue<uint32_t, Vector<CSGBrush::Face>> &E : faces_by_material) {
|
||||
const uint32_t material_id = E.key;
|
||||
const Vector<CSGBrush::Face> &faces = E.value;
|
||||
mesh.runIndex.push_back(mesh.triVerts.size());
|
||||
|
||||
// Associate the material with an ID.
|
||||
uint32_t reserved_id = r_manifold.ReserveIDs(1);
|
||||
mesh.runOriginalID.push_back(reserved_id);
|
||||
Ref<Material> material;
|
||||
if (material_id < p_mesh_merge->materials.size()) {
|
||||
material = p_mesh_merge->materials[material_id];
|
||||
}
|
||||
|
||||
p_mesh_materials.insert(reserved_id, material);
|
||||
for (const CSGBrush::Face &face : faces) {
|
||||
for (int32_t tri_order_i = 0; tri_order_i < 3; tri_order_i++) {
|
||||
constexpr int32_t order[3] = { 0, 2, 1 };
|
||||
int i = order[tri_order_i];
|
||||
|
||||
mesh.triVerts.push_back(mesh.vertProperties.size() / MANIFOLD_PROPERTY_MAX);
|
||||
|
||||
size_t begin = mesh.vertProperties.size();
|
||||
mesh.vertProperties.resize(mesh.vertProperties.size() + MANIFOLD_PROPERTY_MAX);
|
||||
// Add the vertex properties.
|
||||
// Use CSGBrush constants rather than push_back for clarity.
|
||||
double *vert = &mesh.vertProperties[begin];
|
||||
vert[MANIFOLD_PROPERTY_POSITION_X] = face.vertices[i].x;
|
||||
vert[MANIFOLD_PROPERTY_POSITION_Y] = face.vertices[i].y;
|
||||
vert[MANIFOLD_PROPERTY_POSITION_Z] = face.vertices[i].z;
|
||||
vert[MANIFOLD_PROPERTY_UV_X_0] = face.uvs[i].x;
|
||||
vert[MANIFOLD_PROPERTY_UV_Y_0] = face.uvs[i].y;
|
||||
vert[MANIFOLD_PROPERTY_SMOOTH_GROUP] = face.smooth ? 1.0f : 0.0f;
|
||||
vert[MANIFOLD_PROPERTY_INVERT] = face.invert ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// runIndex needs an explicit end value.
|
||||
mesh.runIndex.push_back(mesh.triVerts.size());
|
||||
mesh.tolerance = 2 * FLT_EPSILON;
|
||||
ERR_FAIL_COND_MSG(mesh.vertProperties.size() % mesh.numProp != 0, "Invalid vertex properties size.");
|
||||
mesh.Merge();
|
||||
#ifdef DEV_ENABLED
|
||||
print_verbose(_export_meshgl_as_json(mesh));
|
||||
#endif // DEV_ENABLED
|
||||
r_manifold = manifold::Manifold(mesh);
|
||||
}
|
||||
|
||||
struct ManifoldOperation {
|
||||
manifold::Manifold manifold;
|
||||
manifold::OpType operation;
|
||||
static manifold::OpType convert_csg_op(CSGShape3D::Operation op) {
|
||||
switch (op) {
|
||||
case CSGShape3D::OPERATION_SUBTRACTION:
|
||||
return manifold::OpType::Subtract;
|
||||
case CSGShape3D::OPERATION_INTERSECTION:
|
||||
return manifold::OpType::Intersect;
|
||||
default:
|
||||
return manifold::OpType::Add;
|
||||
}
|
||||
}
|
||||
ManifoldOperation() :
|
||||
operation(manifold::OpType::Add) {}
|
||||
ManifoldOperation(const manifold::Manifold &m, manifold::OpType op) :
|
||||
manifold(m), operation(op) {}
|
||||
};
|
||||
|
||||
CSGBrush *CSGShape3D::_get_brush() {
|
||||
if (!dirty) {
|
||||
return brush;
|
||||
}
|
||||
if (brush) {
|
||||
memdelete(brush);
|
||||
}
|
||||
brush = nullptr;
|
||||
CSGBrush *n = _build_brush();
|
||||
HashMap<int32_t, Ref<Material>> mesh_materials;
|
||||
manifold::Manifold root_manifold;
|
||||
_pack_manifold(n, root_manifold, mesh_materials, this);
|
||||
manifold::OpType current_op = ManifoldOperation::convert_csg_op(get_operation());
|
||||
std::vector<manifold::Manifold> manifolds;
|
||||
manifolds.push_back(root_manifold);
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
CSGShape3D *child = Object::cast_to<CSGShape3D>(get_child(i));
|
||||
if (!child || !child->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
CSGBrush *child_brush = child->_get_brush();
|
||||
if (!child_brush) {
|
||||
continue;
|
||||
}
|
||||
CSGBrush transformed_brush;
|
||||
transformed_brush.copy_from(*child_brush, child->get_transform());
|
||||
manifold::Manifold child_manifold;
|
||||
_pack_manifold(&transformed_brush, child_manifold, mesh_materials, child);
|
||||
manifold::OpType child_operation = ManifoldOperation::convert_csg_op(child->get_operation());
|
||||
if (child_operation != current_op) {
|
||||
manifold::Manifold result = manifold::Manifold::BatchBoolean(manifolds, current_op);
|
||||
manifolds.clear();
|
||||
manifolds.push_back(result);
|
||||
current_op = child_operation;
|
||||
}
|
||||
manifolds.push_back(child_manifold);
|
||||
}
|
||||
if (!manifolds.empty()) {
|
||||
manifold::Manifold manifold_result = manifold::Manifold::BatchBoolean(manifolds, current_op);
|
||||
if (n) {
|
||||
memdelete(n);
|
||||
}
|
||||
n = memnew(CSGBrush);
|
||||
_unpack_manifold(manifold_result, mesh_materials, n);
|
||||
}
|
||||
AABB aabb;
|
||||
if (n && !n->faces.is_empty()) {
|
||||
aabb.position = n->faces[0].vertices[0];
|
||||
for (const CSGBrush::Face &face : n->faces) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
aabb.expand_to(face.vertices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
node_aabb = aabb;
|
||||
brush = n;
|
||||
dirty = false;
|
||||
update_configuration_warnings();
|
||||
return brush;
|
||||
}
|
||||
|
||||
|
|
@ -460,27 +714,31 @@ void CSGShape3D::_update_shape() {
|
|||
_update_collision_faces();
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_collision_faces() {
|
||||
if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
|
||||
CSGBrush *n = _get_brush();
|
||||
ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
|
||||
Vector<Vector3> physics_faces;
|
||||
physics_faces.resize(n->faces.size() * 3);
|
||||
Vector3 *physicsw = physics_faces.ptrw();
|
||||
Vector<Vector3> CSGShape3D::_get_brush_collision_faces() {
|
||||
Vector<Vector3> collision_faces;
|
||||
CSGBrush *n = _get_brush();
|
||||
ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush.");
|
||||
collision_faces.resize(n->faces.size() * 3);
|
||||
Vector3 *collision_faces_ptrw = collision_faces.ptrw();
|
||||
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
int order[3] = { 0, 1, 2 };
|
||||
for (int i = 0; i < n->faces.size(); i++) {
|
||||
int order[3] = { 0, 1, 2 };
|
||||
|
||||
if (n->faces[i].invert) {
|
||||
SWAP(order[1], order[2]);
|
||||
}
|
||||
|
||||
physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
|
||||
physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
|
||||
physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
|
||||
if (n->faces[i].invert) {
|
||||
SWAP(order[1], order[2]);
|
||||
}
|
||||
|
||||
root_collision_shape->set_faces(physics_faces);
|
||||
collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]];
|
||||
collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]];
|
||||
collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]];
|
||||
}
|
||||
|
||||
return collision_faces;
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_collision_faces() {
|
||||
if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
|
||||
root_collision_shape->set_faces(_get_brush_collision_faces());
|
||||
|
||||
if (_is_debug_collision_shape_visible()) {
|
||||
_update_debug_collision_shape();
|
||||
|
|
@ -488,12 +746,32 @@ void CSGShape3D::_update_collision_faces() {
|
|||
}
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> CSGShape3D::bake_static_mesh() {
|
||||
Ref<ArrayMesh> baked_mesh;
|
||||
if (is_root_shape() && root_mesh.is_valid()) {
|
||||
baked_mesh = root_mesh;
|
||||
}
|
||||
return baked_mesh;
|
||||
}
|
||||
|
||||
Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
|
||||
Ref<ConcavePolygonShape3D> baked_collision_shape;
|
||||
if (is_root_shape() && root_collision_shape.is_valid()) {
|
||||
baked_collision_shape.instantiate();
|
||||
baked_collision_shape->set_faces(root_collision_shape->get_faces());
|
||||
} else if (is_root_shape()) {
|
||||
baked_collision_shape.instantiate();
|
||||
baked_collision_shape->set_faces(_get_brush_collision_faces());
|
||||
}
|
||||
return baked_collision_shape;
|
||||
}
|
||||
|
||||
bool CSGShape3D::_is_debug_collision_shape_visible() {
|
||||
return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
|
||||
return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint();
|
||||
}
|
||||
|
||||
void CSGShape3D::_update_debug_collision_shape() {
|
||||
if (!use_collision || !is_root_shape() || !root_collision_shape.is_valid() || !_is_debug_collision_shape_visible()) {
|
||||
if (!use_collision || !is_root_shape() || root_collision_shape.is_null() || !_is_debug_collision_shape_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -575,16 +853,15 @@ void CSGShape3D::_notification(int p_what) {
|
|||
parent_shape = nullptr;
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_CHILD_ORDER_CHANGED: {
|
||||
_make_dirty();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (!is_root_shape() && last_visible != is_visible()) {
|
||||
// Update this node's parent only if its own visibility has changed, not the visibility of parent nodes
|
||||
parent_shape->_make_dirty();
|
||||
}
|
||||
if (is_visible()) {
|
||||
_update_debug_collision_shape();
|
||||
} else {
|
||||
_clear_debug_collision_shape();
|
||||
}
|
||||
last_visible = is_visible();
|
||||
} break;
|
||||
|
||||
|
|
@ -671,6 +948,26 @@ Array CSGShape3D::get_meshes() const {
|
|||
return Array();
|
||||
}
|
||||
|
||||
PackedStringArray CSGShape3D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
const CSGShape3D *current_shape = this;
|
||||
while (current_shape) {
|
||||
if (!current_shape->brush || current_shape->brush->faces.is_empty()) {
|
||||
warnings.push_back(RTR("The CSGShape3D has an empty shape.\nCSGShape3D empty shapes typically occur because the mesh is not manifold.\nA manifold mesh forms a solid object without gaps, holes, or loose edges.\nEach edge must be a member of exactly two faces."));
|
||||
break;
|
||||
}
|
||||
current_shape = current_shape->parent_shape;
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
|
||||
Ref<TriangleMesh> CSGShape3D::generate_triangle_mesh() const {
|
||||
if (root_mesh.is_valid()) {
|
||||
return root_mesh->generate_triangle_mesh();
|
||||
}
|
||||
return Ref<TriangleMesh>();
|
||||
}
|
||||
|
||||
void CSGShape3D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_update_shape"), &CSGShape3D::_update_shape);
|
||||
ClassDB::bind_method(D_METHOD("is_root_shape"), &CSGShape3D::is_root_shape);
|
||||
|
|
@ -678,8 +975,10 @@ void CSGShape3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_operation", "operation"), &CSGShape3D::set_operation);
|
||||
ClassDB::bind_method(D_METHOD("get_operation"), &CSGShape3D::get_operation);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_snap", "snap"), &CSGShape3D::set_snap);
|
||||
ClassDB::bind_method(D_METHOD("get_snap"), &CSGShape3D::get_snap);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_collision", "operation"), &CSGShape3D::set_use_collision);
|
||||
ClassDB::bind_method(D_METHOD("is_using_collision"), &CSGShape3D::is_using_collision);
|
||||
|
|
@ -693,6 +992,8 @@ void CSGShape3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_collision_mask_value", "layer_number", "value"), &CSGShape3D::set_collision_mask_value);
|
||||
ClassDB::bind_method(D_METHOD("get_collision_mask_value", "layer_number"), &CSGShape3D::get_collision_mask_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_get_root_collision_instance"), &CSGShape3D::_get_root_collision_instance);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_collision_layer_value", "layer_number", "value"), &CSGShape3D::set_collision_layer_value);
|
||||
ClassDB::bind_method(D_METHOD("get_collision_layer_value", "layer_number"), &CSGShape3D::get_collision_layer_value);
|
||||
|
||||
|
|
@ -704,8 +1005,13 @@ void CSGShape3D::_bind_methods() {
|
|||
|
||||
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh);
|
||||
ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m", PROPERTY_USAGE_NONE), "set_snap", "get_snap");
|
||||
#endif // DISABLE_DEPRECATED
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
|
||||
|
||||
ADD_GROUP("Collision", "collision_");
|
||||
|
|
@ -786,7 +1092,7 @@ CSGPrimitive3D::CSGPrimitive3D() {
|
|||
/////////////////////
|
||||
|
||||
CSGBrush *CSGMesh3D::_build_brush() {
|
||||
if (!mesh.is_valid()) {
|
||||
if (mesh.is_null()) {
|
||||
return memnew(CSGBrush);
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +1240,8 @@ CSGBrush *CSGMesh3D::_build_brush() {
|
|||
|
||||
void CSGMesh3D::_mesh_changed() {
|
||||
_make_dirty();
|
||||
update_gizmos();
|
||||
|
||||
callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred();
|
||||
}
|
||||
|
||||
void CSGMesh3D::set_material(const Ref<Material> &p_material) {
|
||||
|
|
@ -956,7 +1263,8 @@ void CSGMesh3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_material", "material"), &CSGMesh3D::set_material);
|
||||
ClassDB::bind_method(D_METHOD("get_material"), &CSGMesh3D::get_material);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh");
|
||||
// Hide PrimitiveMeshes that are always non-manifold and therefore can't be used as CSG meshes.
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh,-PlaneMesh,-PointMesh,-QuadMesh,-RibbonTrailMesh"), "set_mesh", "get_mesh");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material");
|
||||
}
|
||||
|
||||
|
|
@ -1018,14 +1326,22 @@ CSGBrush *CSGSphere3D::_build_brush() {
|
|||
const double longitude_step = Math_TAU / radial_segments;
|
||||
int face = 0;
|
||||
for (int i = 0; i < rings; i++) {
|
||||
double latitude0 = latitude_step * i + Math_TAU / 4;
|
||||
double cos0 = Math::cos(latitude0);
|
||||
double sin0 = Math::sin(latitude0);
|
||||
double cos0 = 0;
|
||||
double sin0 = 1;
|
||||
if (i > 0) {
|
||||
double latitude0 = latitude_step * i + Math_TAU / 4;
|
||||
cos0 = Math::cos(latitude0);
|
||||
sin0 = Math::sin(latitude0);
|
||||
}
|
||||
double v0 = double(i) / rings;
|
||||
|
||||
double latitude1 = latitude_step * (i + 1) + Math_TAU / 4;
|
||||
double cos1 = Math::cos(latitude1);
|
||||
double sin1 = Math::sin(latitude1);
|
||||
double cos1 = 0;
|
||||
double sin1 = -1;
|
||||
if (i < rings - 1) {
|
||||
double latitude1 = latitude_step * (i + 1) + Math_TAU / 4;
|
||||
cos1 = Math::cos(latitude1);
|
||||
sin1 = Math::sin(latitude1);
|
||||
}
|
||||
double v1 = double(i + 1) / rings;
|
||||
|
||||
for (int j = 0; j < radial_segments; j++) {
|
||||
|
|
@ -1930,24 +2246,35 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
base_xform = path->get_global_transform();
|
||||
}
|
||||
|
||||
Vector3 current_point = curve->sample_baked(0);
|
||||
Vector3 next_point = curve->sample_baked(extrusion_step);
|
||||
Vector3 current_point;
|
||||
Vector3 current_up = Vector3(0, 1, 0);
|
||||
Vector3 direction = next_point - current_point;
|
||||
|
||||
if (path_joined) {
|
||||
Vector3 last_point = curve->sample_baked(curve->get_baked_length());
|
||||
direction = next_point - last_point;
|
||||
}
|
||||
Vector3 direction;
|
||||
|
||||
switch (path_rotation) {
|
||||
case PATH_ROTATION_POLYGON:
|
||||
current_point = curve->sample_baked(0);
|
||||
direction = Vector3(0, 0, -1);
|
||||
break;
|
||||
case PATH_ROTATION_PATH:
|
||||
break;
|
||||
case PATH_ROTATION_PATH_FOLLOW:
|
||||
current_up = curve->sample_baked_up_vector(0, true);
|
||||
if (!path_rotation_accurate) {
|
||||
current_point = curve->sample_baked(0);
|
||||
Vector3 next_point = curve->sample_baked(extrusion_step);
|
||||
direction = next_point - current_point;
|
||||
|
||||
if (path_joined) {
|
||||
Vector3 last_point = curve->sample_baked(curve->get_baked_length());
|
||||
direction = next_point - last_point;
|
||||
}
|
||||
} else {
|
||||
Transform3D current_sample_xform = curve->sample_baked_with_rotation(0);
|
||||
current_point = current_sample_xform.get_origin();
|
||||
direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1));
|
||||
}
|
||||
|
||||
if (path_rotation == PATH_ROTATION_PATH_FOLLOW) {
|
||||
current_up = curve->sample_baked_up_vector(0, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1994,37 +2321,35 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
current_xform.translate_local(Vector3(0, 0, -depth));
|
||||
} break;
|
||||
case MODE_SPIN: {
|
||||
current_xform.rotate(Vector3(0, 1, 0), spin_step);
|
||||
if (end_count == 0 && x0 == extrusions - 1) {
|
||||
current_xform = base_xform;
|
||||
} else {
|
||||
current_xform.rotate(Vector3(0, 1, 0), spin_step);
|
||||
}
|
||||
} break;
|
||||
case MODE_PATH: {
|
||||
double previous_offset = x0 * extrusion_step;
|
||||
double current_offset = (x0 + 1) * extrusion_step;
|
||||
double next_offset = (x0 + 2) * extrusion_step;
|
||||
if (x0 == extrusions - 1) {
|
||||
if (path_joined) {
|
||||
current_offset = 0;
|
||||
next_offset = extrusion_step;
|
||||
} else {
|
||||
next_offset = current_offset;
|
||||
}
|
||||
if (path_joined && x0 == extrusions - 1) {
|
||||
current_offset = 0;
|
||||
}
|
||||
|
||||
Vector3 previous_point = curve->sample_baked(previous_offset);
|
||||
Vector3 current_point = curve->sample_baked(current_offset);
|
||||
Vector3 next_point = curve->sample_baked(next_offset);
|
||||
Transform3D current_sample_xform = curve->sample_baked_with_rotation(current_offset);
|
||||
Vector3 current_point = current_sample_xform.get_origin();
|
||||
Vector3 current_up = Vector3(0, 1, 0);
|
||||
Vector3 direction = next_point - previous_point;
|
||||
Vector3 current_dir = (current_point - previous_point).normalized();
|
||||
Vector3 current_extrusion_dir = (current_point - previous_point).normalized();
|
||||
Vector3 direction;
|
||||
|
||||
// If the angles are similar, remove the previous face and replace it with this one.
|
||||
if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_dir) > angle_simplify_dot) {
|
||||
if (path_simplify_angle > 0.0 && x0 > 0 && previous_simplify_dir.dot(current_extrusion_dir) > angle_simplify_dot) {
|
||||
faces_combined += 1;
|
||||
previous_xform = previous_previous_xform;
|
||||
face -= extrusion_face_count;
|
||||
faces_removed += extrusion_face_count;
|
||||
} else {
|
||||
faces_combined = 0;
|
||||
previous_simplify_dir = current_dir;
|
||||
previous_simplify_dir = current_extrusion_dir;
|
||||
}
|
||||
|
||||
switch (path_rotation) {
|
||||
|
|
@ -2032,9 +2357,21 @@ CSGBrush *CSGPolygon3D::_build_brush() {
|
|||
direction = Vector3(0, 0, -1);
|
||||
break;
|
||||
case PATH_ROTATION_PATH:
|
||||
break;
|
||||
case PATH_ROTATION_PATH_FOLLOW:
|
||||
current_up = curve->sample_baked_up_vector(current_offset, true);
|
||||
if (!path_rotation_accurate) {
|
||||
double next_offset = (x0 + 2) * extrusion_step;
|
||||
if (x0 == extrusions - 1) {
|
||||
next_offset = path_joined ? extrusion_step : current_offset;
|
||||
}
|
||||
Vector3 next_point = curve->sample_baked(next_offset);
|
||||
direction = next_point - previous_point;
|
||||
} else {
|
||||
direction = current_sample_xform.get_basis().xform(Vector3(0, 0, -1));
|
||||
}
|
||||
|
||||
if (path_rotation == PATH_ROTATION_PATH_FOLLOW) {
|
||||
current_up = curve->sample_baked_up_vector(current_offset, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2204,6 +2541,9 @@ void CSGPolygon3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_path_rotation", "path_rotation"), &CSGPolygon3D::set_path_rotation);
|
||||
ClassDB::bind_method(D_METHOD("get_path_rotation"), &CSGPolygon3D::get_path_rotation);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_path_rotation_accurate", "enable"), &CSGPolygon3D::set_path_rotation_accurate);
|
||||
ClassDB::bind_method(D_METHOD("get_path_rotation_accurate"), &CSGPolygon3D::get_path_rotation_accurate);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_path_local", "enable"), &CSGPolygon3D::set_path_local);
|
||||
ClassDB::bind_method(D_METHOD("is_path_local"), &CSGPolygon3D::is_path_local);
|
||||
|
||||
|
|
@ -2235,6 +2575,7 @@ void CSGPolygon3D::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_interval", PROPERTY_HINT_RANGE, "0.01,1.0,0.01,exp,or_greater"), "set_path_interval", "get_path_interval");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_simplify_angle", PROPERTY_HINT_RANGE, "0.0,180.0,0.1"), "set_path_simplify_angle", "get_path_simplify_angle");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "path_rotation", PROPERTY_HINT_ENUM, "Polygon,Path,PathFollow"), "set_path_rotation", "get_path_rotation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_rotation_accurate"), "set_path_rotation_accurate", "get_path_rotation_accurate");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_local"), "set_path_local", "is_path_local");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "path_continuous_u"), "set_path_continuous_u", "is_path_continuous_u");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "path_u_distance", PROPERTY_HINT_RANGE, "0.0,10.0,0.01,or_greater,suffix:m"), "set_path_u_distance", "get_path_u_distance");
|
||||
|
|
@ -2377,6 +2718,16 @@ CSGPolygon3D::PathRotation CSGPolygon3D::get_path_rotation() const {
|
|||
return path_rotation;
|
||||
}
|
||||
|
||||
void CSGPolygon3D::set_path_rotation_accurate(bool p_enabled) {
|
||||
path_rotation_accurate = p_enabled;
|
||||
_make_dirty();
|
||||
update_gizmos();
|
||||
}
|
||||
|
||||
bool CSGPolygon3D::get_path_rotation_accurate() const {
|
||||
return path_rotation_accurate;
|
||||
}
|
||||
|
||||
void CSGPolygon3D::set_path_local(bool p_enable) {
|
||||
path_local = p_enable;
|
||||
_make_dirty();
|
||||
|
|
@ -2438,6 +2789,7 @@ CSGPolygon3D::CSGPolygon3D() {
|
|||
path_interval = 1.0;
|
||||
path_simplify_angle = 0.0;
|
||||
path_rotation = PATH_ROTATION_PATH_FOLLOW;
|
||||
path_rotation_accurate = false;
|
||||
path_local = false;
|
||||
path_continuous_u = true;
|
||||
path_u_distance = 1.0;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@
|
|||
|
||||
#include "thirdparty/misc/mikktspace.h"
|
||||
|
||||
class NavigationMesh;
|
||||
class NavigationMeshSourceGeometryData3D;
|
||||
|
||||
class CSGShape3D : public GeometryInstance3D {
|
||||
GDCLASS(CSGShape3D, GeometryInstance3D);
|
||||
|
||||
|
|
@ -113,11 +116,13 @@ private:
|
|||
void _update_debug_collision_shape();
|
||||
void _clear_debug_collision_shape();
|
||||
void _on_transform_changed();
|
||||
Vector<Vector3> _get_brush_collision_faces();
|
||||
|
||||
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();
|
||||
|
||||
|
|
@ -151,16 +156,34 @@ public:
|
|||
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();
|
||||
Ref<ConcavePolygonShape3D> bake_collision_shape();
|
||||
|
||||
virtual Ref<TriangleMesh> generate_triangle_mesh() const override;
|
||||
|
||||
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);
|
||||
|
||||
CSGShape3D();
|
||||
~CSGShape3D();
|
||||
};
|
||||
|
|
@ -380,6 +403,7 @@ private:
|
|||
float path_interval;
|
||||
float path_simplify_angle;
|
||||
PathRotation path_rotation;
|
||||
bool path_rotation_accurate;
|
||||
bool path_local;
|
||||
|
||||
Path3D *path = nullptr;
|
||||
|
|
@ -431,6 +455,9 @@ public:
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
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 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.
|
||||
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>
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
</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>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,29 @@
|
|||
</brief_description>
|
||||
<description>
|
||||
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
|
||||
[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.
|
||||
[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.
|
||||
</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.
|
||||
</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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_collision_layer_value" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="layer_number" type="int" />
|
||||
|
|
@ -72,8 +89,8 @@
|
|||
<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" default="0.001">
|
||||
Snap makes the mesh vertices snap to a given distance so that the faces of two meshes can be perfectly aligned. A lower value results in greater precision but may be harder to adjust. The top-level CSG shape's snap value is used for the entire CSG tree.
|
||||
<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].
|
||||
|
|
|
|||
|
|
@ -38,13 +38,141 @@
|
|||
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
|
||||
#include "editor/plugins/node_3d_editor_plugin.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);
|
||||
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_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
|
||||
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();
|
||||
|
|
@ -149,24 +277,13 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
|
|||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector3 axis;
|
||||
axis[p_id == 0 ? 0 : 1] = 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_radius(d);
|
||||
} else if (p_id == 1) {
|
||||
s->set_height(d * 2.0);
|
||||
}
|
||||
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)) {
|
||||
|
|
@ -211,32 +328,11 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
|
|||
}
|
||||
|
||||
if (Object::cast_to<CSGBox3D>(cs)) {
|
||||
helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
|
||||
helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
|
||||
}
|
||||
|
||||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
if (p_cancel) {
|
||||
if (p_id == 0) {
|
||||
s->set_radius(p_restore);
|
||||
} else {
|
||||
s->set_height(p_restore);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
|
||||
if (p_id == 0) {
|
||||
ur->create_action(TTR("Change Cylinder Radius"));
|
||||
ur->add_do_method(s, "set_radius", s->get_radius());
|
||||
ur->add_undo_method(s, "set_radius", p_restore);
|
||||
} else {
|
||||
ur->create_action(TTR("Change Cylinder Height"));
|
||||
ur->add_do_method(s, "set_height", s->get_height());
|
||||
ur->add_undo_method(s, "set_height", p_restore);
|
||||
}
|
||||
|
||||
ur->commit_action();
|
||||
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)) {
|
||||
|
|
@ -377,9 +473,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|||
if (Object::cast_to<CSGCylinder3D>(cs)) {
|
||||
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
|
||||
|
||||
Vector<Vector3> handles;
|
||||
handles.push_back(Vector3(s->get_radius(), 0, 0));
|
||||
handles.push_back(Vector3(0, s->get_height() * 0.5, 0));
|
||||
Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
|
||||
p_gizmo->add_handles(handles, handles_material);
|
||||
}
|
||||
|
||||
|
|
@ -393,9 +487,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
|
|
|||
|
|
@ -37,8 +37,11 @@
|
|||
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
#include "editor/plugins/node_3d_editor_gizmos.h"
|
||||
#include "scene/gui/control.h"
|
||||
|
||||
class AcceptDialog;
|
||||
class Gizmo3DHelper;
|
||||
class MenuButton;
|
||||
|
||||
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
|
||||
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
|
||||
|
|
@ -62,10 +65,43 @@ public:
|
|||
~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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@
|
|||
|
||||
#include "register_types.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "csg_shape.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
|
@ -49,6 +47,7 @@ void initialize_csg_module(ModuleInitializationLevel p_level) {
|
|||
GDREGISTER_CLASS(CSGTorus3D);
|
||||
GDREGISTER_CLASS(CSGPolygon3D);
|
||||
GDREGISTER_CLASS(CSGCombiner3D);
|
||||
CSGShape3D::navmesh_parse_init();
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
|
||||
|
|
@ -62,5 +61,3 @@ void uninitialize_csg_module(ModuleInitializationLevel p_level) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
|
|
|
|||
114
engine/modules/csg/tests/test_csg.h
Normal file
114
engine/modules/csg/tests/test_csg.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_CSG_H
|
||||
#define TEST_CSG_H
|
||||
|
||||
#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
|
||||
|
||||
#endif // TEST_CSG_H
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
|
|||
|
|
@ -142,14 +142,17 @@ static void _digest_job_queue(void *p_job_queue, uint32_t p_index) {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8);
|
||||
bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
|
||||
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
|
||||
|
|
@ -171,17 +174,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
|
|||
p_image->convert(Image::FORMAT_RGBH);
|
||||
}
|
||||
|
||||
const uint8_t *rb = p_image->get_data().ptr();
|
||||
|
||||
const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]);
|
||||
int pixel_element_count = w * h * 3;
|
||||
for (int i = 0; i < pixel_element_count; i++) {
|
||||
if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) {
|
||||
is_signed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -250,6 +243,8 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
|
|||
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
|
||||
|
||||
p_image->set_data(p_image->get_width(), p_image->get_height(), 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) {
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ void initialize_cvtt_module(ModuleInitializationLevel p_level) {
|
|||
return;
|
||||
}
|
||||
|
||||
Image::set_compress_bptc_func(image_compress_cvtt);
|
||||
Image::_image_decompress_bptc = image_decompress_cvtt;
|
||||
Image::_image_compress_bptc_func = image_compress_cvtt;
|
||||
}
|
||||
|
||||
void uninitialize_cvtt_module(ModuleInitializationLevel p_level) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
|
|||
|
|
@ -42,14 +42,23 @@ enum {
|
|||
DDSD_PITCH = 0x00000008,
|
||||
DDSD_LINEARSIZE = 0x00080000,
|
||||
DDSD_MIPMAPCOUNT = 0x00020000,
|
||||
DDPF_FOURCC = 0x00000004,
|
||||
DDPF_ALPHAPIXELS = 0x00000001,
|
||||
DDPF_RGB = 0x00000040
|
||||
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"),
|
||||
|
|
@ -68,17 +77,25 @@ enum DDSFourCC {
|
|||
// 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,
|
||||
|
|
@ -87,6 +104,7 @@ enum DXGIFormat {
|
|||
DXGI_BC6H_UF16 = 95,
|
||||
DXGI_BC6H_SF16 = 96,
|
||||
DXGI_BC7_UNORM = 98,
|
||||
DXGI_BC7_UNORM_SRGB = 99,
|
||||
DXGI_B4G4R4A4_UNORM = 115
|
||||
};
|
||||
|
||||
|
|
@ -100,28 +118,41 @@ enum DDSFormat {
|
|||
DDS_ATI2,
|
||||
DDS_BC6U,
|
||||
DDS_BC6S,
|
||||
DDS_BC7U,
|
||||
DDS_BC7,
|
||||
DDS_R16F,
|
||||
DDS_RG16F,
|
||||
DDS_RGBA16F,
|
||||
DDS_R32F,
|
||||
DDS_RG32F,
|
||||
DDS_RGB32F,
|
||||
DDS_RGBA32F,
|
||||
DDS_RGB9E5,
|
||||
DDS_BGRA8,
|
||||
DDS_BGR8,
|
||||
DDS_RGBA8,
|
||||
DDS_RGB8,
|
||||
DDS_RGBA8,
|
||||
DDS_BGR8,
|
||||
DDS_BGRA8,
|
||||
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;
|
||||
|
|
@ -132,38 +163,45 @@ struct DDSFormatInfo {
|
|||
|
||||
static const DDSFormatInfo dds_format_info[DDS_MAX] = {
|
||||
{ "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 },
|
||||
{ "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 },
|
||||
{ "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 },
|
||||
{ "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 },
|
||||
{ "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU },
|
||||
{ "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF },
|
||||
{ "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA },
|
||||
{ "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 },
|
||||
{ "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
|
||||
{ "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "RGB8", false, 1, 3, Image::FORMAT_RGB8 },
|
||||
{ "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "BGR8", false, 1, 3, Image::FORMAT_RGB8 },
|
||||
{ "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 },
|
||||
{ "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 },
|
||||
{ "BGR565", false, 1, 2, Image::FORMAT_RGB8 },
|
||||
{ "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_RGBA8 },
|
||||
{ "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 },
|
||||
{ "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }
|
||||
{ "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 },
|
||||
{ "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 }
|
||||
};
|
||||
|
||||
static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
||||
inline 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;
|
||||
}
|
||||
|
|
@ -173,7 +211,8 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
|||
case DXGI_R10G10B10A2_UNORM: {
|
||||
return DDS_RGB10A2;
|
||||
}
|
||||
case DXGI_R8G8B8A8_UNORM: {
|
||||
case DXGI_R8G8B8A8_UNORM:
|
||||
case DXGI_R8G8B8A8_UNORM_SRGB: {
|
||||
return DDS_RGBA8;
|
||||
}
|
||||
case DXGI_R16G16_FLOAT: {
|
||||
|
|
@ -182,19 +221,29 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
|||
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:
|
||||
case DXGI_BC1_UNORM_SRGB: {
|
||||
return DDS_DXT1;
|
||||
}
|
||||
case DXGI_BC2_UNORM: {
|
||||
case DXGI_BC2_UNORM:
|
||||
case DXGI_BC2_UNORM_SRGB: {
|
||||
return DDS_DXT3;
|
||||
}
|
||||
case DXGI_BC3_UNORM: {
|
||||
case DXGI_BC3_UNORM:
|
||||
case DXGI_BC3_UNORM_SRGB: {
|
||||
return DDS_DXT5;
|
||||
}
|
||||
case DXGI_BC4_UNORM: {
|
||||
|
|
@ -218,8 +267,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
|||
case DXGI_BC6H_SF16: {
|
||||
return DDS_BC6S;
|
||||
}
|
||||
case DXGI_BC7_UNORM: {
|
||||
return DDS_BC7U;
|
||||
case DXGI_BC7_UNORM:
|
||||
case DXGI_BC7_UNORM_SRGB: {
|
||||
return DDS_BC7;
|
||||
}
|
||||
case DXGI_B4G4R4A4_UNORM: {
|
||||
return DDS_BGRA4;
|
||||
|
|
@ -231,186 +281,32 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) {
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (r_error) {
|
||||
*r_error = ERR_CANT_OPEN;
|
||||
}
|
||||
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];
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (f.is_null()) {
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
Ref<FileAccess> fref(f);
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Unable to open DDS texture file '" + p_path + "'.");
|
||||
|
||||
uint32_t magic = f->get_32();
|
||||
uint32_t hsize = f->get_32();
|
||||
uint32_t flags = f->get_32();
|
||||
uint32_t height = f->get_32();
|
||||
uint32_t width = f->get_32();
|
||||
uint32_t pitch = f->get_32();
|
||||
/* uint32_t depth = */ f->get_32();
|
||||
uint32_t mipmaps = f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
for (int i = 0; i < 11; i++) {
|
||||
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(Ref<Resource>(), "Invalid or unsupported DDS texture file '" + p_path + "'.");
|
||||
}
|
||||
|
||||
/* uint32_t format_size = */ f->get_32();
|
||||
uint32_t format_flags = f->get_32();
|
||||
uint32_t format_fourcc = f->get_32();
|
||||
uint32_t format_rgb_bits = f->get_32();
|
||||
uint32_t format_red_mask = f->get_32();
|
||||
uint32_t format_green_mask = f->get_32();
|
||||
uint32_t format_blue_mask = f->get_32();
|
||||
uint32_t format_alpha_mask = f->get_32();
|
||||
|
||||
/* uint32_t caps_1 = */ f->get_32();
|
||||
/* uint32_t caps_2 = */ f->get_32();
|
||||
/* uint32_t caps_3 = */ f->get_32();
|
||||
/* uint32_t caps_4 = */ f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
f->get_32();
|
||||
|
||||
if (f->get_position() < 128) {
|
||||
f->seek(128);
|
||||
}
|
||||
|
||||
DDSFormat dds_format = DDS_MAX;
|
||||
|
||||
if (format_flags & DDPF_FOURCC) {
|
||||
// FourCC formats.
|
||||
switch (format_fourcc) {
|
||||
case DDFCC_DXT1: {
|
||||
dds_format = DDS_DXT1;
|
||||
} break;
|
||||
case DDFCC_DXT3: {
|
||||
dds_format = DDS_DXT3;
|
||||
} break;
|
||||
case DDFCC_DXT5: {
|
||||
dds_format = DDS_DXT5;
|
||||
} break;
|
||||
case DDFCC_ATI1:
|
||||
case DDFCC_BC4U: {
|
||||
dds_format = DDS_ATI1;
|
||||
} break;
|
||||
case DDFCC_ATI2:
|
||||
case DDFCC_BC5U:
|
||||
case DDFCC_A2XY: {
|
||||
dds_format = DDS_ATI2;
|
||||
} break;
|
||||
case DDFCC_R16F: {
|
||||
dds_format = DDS_R16F;
|
||||
} break;
|
||||
case DDFCC_RG16F: {
|
||||
dds_format = DDS_RG16F;
|
||||
} break;
|
||||
case DDFCC_RGBA16F: {
|
||||
dds_format = DDS_RGBA16F;
|
||||
} break;
|
||||
case DDFCC_R32F: {
|
||||
dds_format = DDS_R32F;
|
||||
} break;
|
||||
case DDFCC_RG32F: {
|
||||
dds_format = DDS_RG32F;
|
||||
} break;
|
||||
case DDFCC_RGBA32F: {
|
||||
dds_format = DDS_RGBA32F;
|
||||
} break;
|
||||
case DDFCC_DX10: {
|
||||
uint32_t dxgi_format = f->get_32();
|
||||
/* uint32_t dimension = */ f->get_32();
|
||||
/* uint32_t misc_flags_1 = */ f->get_32();
|
||||
/* uint32_t array_size = */ f->get_32();
|
||||
/* uint32_t misc_flags_2 = */ f->get_32();
|
||||
|
||||
dds_format = dxgi_to_dds_format(dxgi_format);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported FourCC in DDS '" + 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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
dds_format = DDS_BGRA4;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Without alpha.
|
||||
if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
|
||||
dds_format = DDS_BGR8;
|
||||
} else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
|
||||
dds_format = DDS_RGB8;
|
||||
} else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
|
||||
dds_format = DDS_BGR565;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Other formats.
|
||||
if (format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
|
||||
dds_format = DDS_LUMINANCE_ALPHA;
|
||||
} else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) {
|
||||
dds_format = DDS_LUMINANCE;
|
||||
}
|
||||
}
|
||||
|
||||
// No format detected, error.
|
||||
if (dds_format == DDS_MAX) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'.");
|
||||
}
|
||||
|
||||
if (!(flags & DDSD_MIPMAPCOUNT)) {
|
||||
mipmaps = 1;
|
||||
}
|
||||
|
||||
Vector<uint8_t> src_data;
|
||||
|
||||
const DDSFormatInfo &info = dds_format_info[dds_format];
|
||||
uint32_t w = width;
|
||||
uint32_t h = height;
|
||||
uint32_t w = p_width;
|
||||
uint32_t h = p_height;
|
||||
|
||||
if (info.compressed) {
|
||||
// BC compressed.
|
||||
uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * info.block_size;
|
||||
|
||||
if (flags & DDSD_LINEARSIZE) {
|
||||
ERR_FAIL_COND_V_MSG(size != 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(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.");
|
||||
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));
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < mipmaps; i++) {
|
||||
uint32_t size = MAX(info.divisor, w) / info.divisor * MAX(info.divisor, h) / info.divisor * 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);
|
||||
|
||||
|
|
@ -418,33 +314,46 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
|
|||
size += bsize;
|
||||
}
|
||||
|
||||
src_data.resize(size);
|
||||
uint8_t *wb = src_data.ptrw();
|
||||
f->get_buffer(wb, size);
|
||||
r_src_data.resize(size);
|
||||
uint8_t *wb = r_src_data.ptrw();
|
||||
p_file->get_buffer(wb, size);
|
||||
|
||||
} else {
|
||||
// Generic uncompressed.
|
||||
uint32_t size = width * height * info.block_size;
|
||||
uint32_t size = p_width * p_height * info.block_size;
|
||||
|
||||
for (uint32_t i = 1; i < mipmaps; i++) {
|
||||
for (uint32_t i = 1; i < p_mipmaps; i++) {
|
||||
w = (w + 1) >> 1;
|
||||
h = (h + 1) >> 1;
|
||||
size += w * h * info.block_size;
|
||||
}
|
||||
|
||||
// Calculate the space these formats will take up after decoding.
|
||||
if (dds_format == DDS_BGR565) {
|
||||
size = size * 3 / 2;
|
||||
} else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) {
|
||||
size = size * 2;
|
||||
switch (p_dds_format) {
|
||||
case DDS_BGR565:
|
||||
size = size * 3 / 2;
|
||||
break;
|
||||
|
||||
case DDS_BGR5A1:
|
||||
case DDS_BGRA4:
|
||||
case DDS_B2GR3A8:
|
||||
case DDS_LUMINANCE_ALPHA_4:
|
||||
size = size * 2;
|
||||
break;
|
||||
|
||||
case DDS_B2GR3:
|
||||
size = size * 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
src_data.resize(size);
|
||||
uint8_t *wb = src_data.ptrw();
|
||||
f->get_buffer(wb, size);
|
||||
r_src_data.resize(size);
|
||||
uint8_t *wb = r_src_data.ptrw();
|
||||
p_file->get_buffer(wb, size);
|
||||
|
||||
// Decode nonstandard formats.
|
||||
switch (dds_format) {
|
||||
switch (p_dds_format) {
|
||||
case DDS_BGR5A1: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
|
@ -502,6 +411,44 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
|
|||
wb[dst_ofs + 3] = a | (a >> 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.
|
||||
|
|
@ -549,6 +496,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
|
|||
}
|
||||
|
||||
} break;
|
||||
|
||||
// Channel-swapped.
|
||||
case DDS_BGRA8: {
|
||||
// To RGBA8.
|
||||
int colcount = size / 4;
|
||||
|
|
@ -568,19 +517,305 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig
|
|||
|
||||
} 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: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Image> img = memnew(Image(width, height, mipmaps - 1, info.format, src_data));
|
||||
Ref<ImageTexture> texture = ImageTexture::create_from_image(img);
|
||||
return memnew(Image(p_width, p_height, p_mipmaps > 1, info.format, r_src_data));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
*r_error = ERR_CANT_OPEN;
|
||||
}
|
||||
|
||||
return texture;
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (f.is_null()) {
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
Ref<FileAccess> fref(f);
|
||||
if (r_error) {
|
||||
*r_error = ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), vformat("Unable to open DDS texture file '%s'.", p_path));
|
||||
|
||||
uint32_t magic = f->get_32();
|
||||
uint32_t hsize = f->get_32();
|
||||
uint32_t flags = f->get_32();
|
||||
uint32_t height = f->get_32();
|
||||
uint32_t width = f->get_32();
|
||||
uint32_t pitch = f->get_32();
|
||||
uint32_t depth = f->get_32();
|
||||
uint32_t mipmaps = f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
for (int i = 0; i < 11; i++) {
|
||||
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(Ref<Resource>(), vformat("Invalid or unsupported DDS texture file '%s'.", p_path));
|
||||
}
|
||||
|
||||
/* uint32_t format_size = */ f->get_32();
|
||||
uint32_t format_flags = f->get_32();
|
||||
uint32_t format_fourcc = f->get_32();
|
||||
uint32_t format_rgb_bits = f->get_32();
|
||||
uint32_t format_red_mask = f->get_32();
|
||||
uint32_t format_green_mask = f->get_32();
|
||||
uint32_t format_blue_mask = f->get_32();
|
||||
uint32_t format_alpha_mask = f->get_32();
|
||||
|
||||
/* uint32_t caps_1 = */ f->get_32();
|
||||
uint32_t caps_2 = f->get_32();
|
||||
/* uint32_t caps_3 = */ f->get_32();
|
||||
/* uint32_t caps_4 = */ f->get_32();
|
||||
|
||||
// Skip reserved.
|
||||
f->get_32();
|
||||
|
||||
if (f->get_position() < 128) {
|
||||
f->seek(128);
|
||||
}
|
||||
|
||||
uint32_t layer_count = 1;
|
||||
uint32_t dds_type = DDST_2D;
|
||||
|
||||
if (caps_2 & DDSC2_CUBEMAP) {
|
||||
dds_type = DDST_CUBEMAP;
|
||||
layer_count *= 6;
|
||||
|
||||
} else if (caps_2 & DDSC2_VOLUME) {
|
||||
dds_type = DDST_3D;
|
||||
layer_count = depth;
|
||||
}
|
||||
|
||||
DDSFormat dds_format = DDS_MAX;
|
||||
|
||||
if (format_flags & DDPF_FOURCC) {
|
||||
// FourCC formats.
|
||||
switch (format_fourcc) {
|
||||
case DDFCC_DXT1: {
|
||||
dds_format = DDS_DXT1;
|
||||
} break;
|
||||
case DDFCC_DXT2:
|
||||
case DDFCC_DXT3: {
|
||||
dds_format = DDS_DXT3;
|
||||
} break;
|
||||
case DDFCC_DXT4:
|
||||
case DDFCC_DXT5: {
|
||||
dds_format = DDS_DXT5;
|
||||
} break;
|
||||
case DDFCC_ATI1:
|
||||
case DDFCC_BC4U: {
|
||||
dds_format = DDS_ATI1;
|
||||
} break;
|
||||
case DDFCC_ATI2:
|
||||
case DDFCC_BC5U:
|
||||
case DDFCC_A2XY: {
|
||||
dds_format = DDS_ATI2;
|
||||
} break;
|
||||
case DDFCC_R16F: {
|
||||
dds_format = DDS_R16F;
|
||||
} break;
|
||||
case DDFCC_RG16F: {
|
||||
dds_format = DDS_RG16F;
|
||||
} break;
|
||||
case DDFCC_RGBA16F: {
|
||||
dds_format = DDS_RGBA16F;
|
||||
} break;
|
||||
case DDFCC_R32F: {
|
||||
dds_format = DDS_R32F;
|
||||
} break;
|
||||
case DDFCC_RG32F: {
|
||||
dds_format = DDS_RG32F;
|
||||
} break;
|
||||
case DDFCC_RGBA32F: {
|
||||
dds_format = DDS_RGBA32F;
|
||||
} break;
|
||||
case DDFCC_DX10: {
|
||||
uint32_t dxgi_format = f->get_32();
|
||||
uint32_t dimension = f->get_32();
|
||||
/* uint32_t misc_flags_1 = */ f->get_32();
|
||||
uint32_t array_size = f->get_32();
|
||||
/* uint32_t misc_flags_2 = */ f->get_32();
|
||||
|
||||
if (dimension == DX10D_3D) {
|
||||
dds_type = DDST_3D;
|
||||
layer_count = depth;
|
||||
}
|
||||
|
||||
if (array_size > 1) {
|
||||
layer_count *= array_size;
|
||||
dds_type |= DDST_ARRAY;
|
||||
}
|
||||
|
||||
dds_format = _dxgi_to_dds_format(dxgi_format);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), 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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
dds_format = DDS_B2GR3A8;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Without alpha.
|
||||
if (format_rgb_bits == 24 && format_red_mask == 0xff0000 && format_green_mask == 0xff00 && format_blue_mask == 0xff) {
|
||||
dds_format = DDS_BGR8;
|
||||
} else if (format_rgb_bits == 24 && format_red_mask == 0xff && format_green_mask == 0xff00 && format_blue_mask == 0xff0000) {
|
||||
dds_format = DDS_RGB8;
|
||||
} else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) {
|
||||
dds_format = DDS_BGR565;
|
||||
} else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) {
|
||||
dds_format = DDS_B2GR3;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Other formats.
|
||||
if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) {
|
||||
// Alpha only.
|
||||
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 (dds_format == DDS_MAX) {
|
||||
if (format_flags & DDPF_ALPHAPIXELS) {
|
||||
// With alpha.
|
||||
if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) {
|
||||
dds_format = DDS_LUMINANCE_ALPHA;
|
||||
} else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) {
|
||||
dds_format = DDS_LUMINANCE_ALPHA_4;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Without alpha.
|
||||
if (format_rgb_bits == 8 && format_red_mask == 0xff) {
|
||||
dds_format = DDS_LUMINANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No format detected, error.
|
||||
if (dds_format == DDS_MAX) {
|
||||
ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unrecognized or unsupported color layout in DDS '%s'.", p_path));
|
||||
}
|
||||
|
||||
if (!(flags & DDSD_MIPMAPCOUNT)) {
|
||||
mipmaps = 1;
|
||||
}
|
||||
|
||||
Vector<uint8_t> src_data;
|
||||
|
||||
Vector<Ref<Image>> images;
|
||||
images.resize(layer_count);
|
||||
|
||||
for (uint32_t i = 0; i < layer_count; i++) {
|
||||
images.write[i] = _dds_load_layer(f, dds_format, width, height, mipmaps, pitch, flags, src_data);
|
||||
}
|
||||
|
||||
if ((dds_type & DDST_TYPE_MASK) == DDST_2D) {
|
||||
if (dds_type & DDST_ARRAY) {
|
||||
Ref<Texture2DArray> texture = memnew(Texture2DArray());
|
||||
texture->create_from_images(images);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return texture;
|
||||
|
||||
} else {
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return ImageTexture::create_from_image(images[0]);
|
||||
}
|
||||
|
||||
} else if ((dds_type & DDST_TYPE_MASK) == DDST_CUBEMAP) {
|
||||
ERR_FAIL_COND_V(layer_count % 6 != 0, Ref<Resource>());
|
||||
|
||||
if (dds_type & DDST_ARRAY) {
|
||||
Ref<CubemapArray> texture = memnew(CubemapArray());
|
||||
texture->create_from_images(images);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return texture;
|
||||
|
||||
} else {
|
||||
Ref<Cubemap> texture = memnew(Cubemap());
|
||||
texture->create_from_images(images);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
} else if ((dds_type & DDST_TYPE_MASK) == DDST_3D) {
|
||||
Ref<ImageTexture3D> texture = memnew(ImageTexture3D());
|
||||
texture->create(images[0]->get_format(), width, height, layer_count, mipmaps > 1, images);
|
||||
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
return Ref<Resource>();
|
||||
}
|
||||
|
||||
void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) const {
|
||||
|
|
@ -588,12 +823,12 @@ void ResourceFormatDDS::get_recognized_extensions(List<String> *p_extensions) co
|
|||
}
|
||||
|
||||
bool ResourceFormatDDS::handles_type(const String &p_type) const {
|
||||
return ClassDB::is_parent_class(p_type, "Texture2D");
|
||||
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 "ImageTexture";
|
||||
return "Texture";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
@ -12,7 +13,7 @@ thirdparty_obj = []
|
|||
if env["builtin_enet"]:
|
||||
thirdparty_dir = "#thirdparty/enet/"
|
||||
thirdparty_sources = [
|
||||
"godot.cpp",
|
||||
"enet_godot.cpp",
|
||||
"callbacks.c",
|
||||
"compress.c",
|
||||
"host.c",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@
|
|||
Returns the number of channels allocated for communication with peer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_packet_flags" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the ENet flags of the next packet in the received queue. See [code]FLAG_*[/code] constants for available packet flags. Note that not all flags are replicated from the sending peer to the receiving peer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_remote_address" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ Ref<ENetPacketPeer> ENetConnection::connect_to_host(const String &p_address, int
|
|||
if (peer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
out = Ref<ENetPacketPeer>(memnew(ENetPacketPeer(peer)));
|
||||
out.instantiate(peer);
|
||||
peers.push_back(out);
|
||||
return out;
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) {
|
|||
#ifdef GODOT_ENET
|
||||
ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
|
||||
return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
|
||||
return enet_host_dtls_server_setup(host, p_options.ptr()) ? FAILED : OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
|
||||
#endif
|
||||
|
|
@ -296,7 +296,7 @@ Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSO
|
|||
#ifdef GODOT_ENET
|
||||
ERR_FAIL_NULL_V_MSG(host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
|
||||
ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER);
|
||||
return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
|
||||
return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), p_options.ptr()) ? FAILED : OK;
|
||||
#else
|
||||
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,10 +30,6 @@
|
|||
|
||||
#include "enet_multiplayer_peer.h"
|
||||
|
||||
#include "core/io/ip.h"
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
void ENetMultiplayerPeer::set_target_peer(int p_peer) {
|
||||
target_peer = p_peer;
|
||||
}
|
||||
|
|
@ -305,6 +301,7 @@ void ENetMultiplayerPeer::close() {
|
|||
}
|
||||
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
|
||||
E.value->flush();
|
||||
E.value->destroy();
|
||||
}
|
||||
|
||||
active_mode = MODE_NONE;
|
||||
|
|
|
|||
|
|
@ -175,6 +175,11 @@ int ENetPacketPeer::get_channels() const {
|
|||
return peer->channelCount;
|
||||
}
|
||||
|
||||
int ENetPacketPeer::get_packet_flags() const {
|
||||
ERR_FAIL_COND_V(packet_queue.is_empty(), 0);
|
||||
return packet_queue.front()->get()->flags;
|
||||
}
|
||||
|
||||
void ENetPacketPeer::_on_disconnect() {
|
||||
if (peer) {
|
||||
peer->data = nullptr;
|
||||
|
|
@ -206,6 +211,7 @@ void ENetPacketPeer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("send", "channel", "packet", "flags"), &ENetPacketPeer::_send);
|
||||
ClassDB::bind_method(D_METHOD("throttle_configure", "interval", "acceleration", "deceleration"), &ENetPacketPeer::throttle_configure);
|
||||
ClassDB::bind_method(D_METHOD("set_timeout", "timeout", "timeout_min", "timeout_max"), &ENetPacketPeer::set_timeout);
|
||||
ClassDB::bind_method(D_METHOD("get_packet_flags"), &ENetPacketPeer::get_packet_flags);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_address"), &ENetPacketPeer::get_remote_address);
|
||||
ClassDB::bind_method(D_METHOD("get_remote_port"), &ENetPacketPeer::get_remote_port);
|
||||
ClassDB::bind_method(D_METHOD("get_statistic", "statistic"), &ENetPacketPeer::get_statistic);
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ public:
|
|||
double get_statistic(PeerStatistic p_stat);
|
||||
PeerState get_state() const;
|
||||
int get_channels() const;
|
||||
int get_packet_flags() const;
|
||||
|
||||
// Extras
|
||||
IPAddress get_remote_address() const;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
@ -11,6 +12,7 @@ thirdparty_obj = []
|
|||
|
||||
thirdparty_dir = "#thirdparty/etcpak/"
|
||||
thirdparty_sources = [
|
||||
"DecodeRGB.cpp",
|
||||
"Dither.cpp",
|
||||
"ProcessDxtc.cpp",
|
||||
"ProcessRGB.cpp",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
def can_build(env, platform):
|
||||
return env.editor_build
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "image_compress_etcpak.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
|
|
@ -50,6 +52,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
|
|||
return EtcpakType::ETCPAK_TYPE_ETC2;
|
||||
case Image::USED_CHANNELS_RGBA:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
|
||||
|
||||
default:
|
||||
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
|
||||
}
|
||||
|
|
@ -69,6 +72,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
|
|||
return EtcpakType::ETCPAK_TYPE_DXT1;
|
||||
case Image::USED_CHANNELS_RGBA:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT5;
|
||||
|
||||
default:
|
||||
return EtcpakType::ETCPAK_TYPE_DXT5;
|
||||
}
|
||||
|
|
@ -79,71 +83,86 @@ void _compress_etc1(Image *r_img) {
|
|||
}
|
||||
|
||||
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
|
||||
EtcpakType type = _determine_etc_type(p_channels);
|
||||
_compress_etcpak(type, r_img);
|
||||
_compress_etcpak(_determine_etc_type(p_channels), r_img);
|
||||
}
|
||||
|
||||
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
|
||||
EtcpakType type = _determine_dxt_type(p_channels);
|
||||
_compress_etcpak(type, r_img);
|
||||
_compress_etcpak(_determine_dxt_type(p_channels), r_img);
|
||||
}
|
||||
|
||||
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
|
||||
void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) {
|
||||
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
|
||||
|
||||
Image::Format img_format = r_img->get_format();
|
||||
if (Image::is_format_compressed(img_format)) {
|
||||
return; // Do not compress, already compressed.
|
||||
}
|
||||
if (img_format > Image::FORMAT_RGBA8) {
|
||||
// TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually
|
||||
// The image is already compressed, return.
|
||||
if (r_img->is_compressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use RGBA8 to convert.
|
||||
if (img_format != Image::FORMAT_RGBA8) {
|
||||
r_img->convert(Image::FORMAT_RGBA8);
|
||||
}
|
||||
// Convert to RGBA8 for compression.
|
||||
r_img->convert(Image::FORMAT_RGBA8);
|
||||
|
||||
// Determine output format based on Etcpak type.
|
||||
Image::Format target_format = Image::FORMAT_RGBA8;
|
||||
if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) {
|
||||
target_format = Image::FORMAT_ETC;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) {
|
||||
target_format = Image::FORMAT_ETC2_RGB8;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) {
|
||||
target_format = Image::FORMAT_ETC2_R11;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) {
|
||||
target_format = Image::FORMAT_ETC2_RG11;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) {
|
||||
target_format = Image::FORMAT_ETC2_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) {
|
||||
target_format = Image::FORMAT_ETC2_RGBA8;
|
||||
r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC.
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) {
|
||||
target_format = Image::FORMAT_DXT1;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
|
||||
target_format = Image::FORMAT_DXT5_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
|
||||
target_format = Image::FORMAT_DXT5;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) {
|
||||
target_format = Image::FORMAT_RGTC_R;
|
||||
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) {
|
||||
target_format = Image::FORMAT_RGTC_RG;
|
||||
} else {
|
||||
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
|
||||
|
||||
switch (p_compress_type) {
|
||||
case EtcpakType::ETCPAK_TYPE_ETC1:
|
||||
target_format = Image::FORMAT_ETC;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2:
|
||||
target_format = Image::FORMAT_ETC2_RGB8;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
|
||||
target_format = Image::FORMAT_ETC2_RGBA8;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_R:
|
||||
target_format = Image::FORMAT_ETC2_R11;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
|
||||
target_format = Image::FORMAT_ETC2_RG11;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
|
||||
target_format = Image::FORMAT_ETC2_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT1:
|
||||
target_format = Image::FORMAT_DXT1;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5:
|
||||
target_format = Image::FORMAT_DXT5;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_R:
|
||||
target_format = Image::FORMAT_RGTC_R;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
|
||||
target_format = Image::FORMAT_RGTC_RG;
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
|
||||
target_format = Image::FORMAT_DXT5_RA_AS_RG;
|
||||
r_img->convert_rg_to_ra_rgba8();
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
|
||||
break;
|
||||
}
|
||||
|
||||
// It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats.
|
||||
if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) {
|
||||
r_img->convert_rgba8_to_bgra8();
|
||||
}
|
||||
|
||||
// Compress image data and (if required) mipmaps.
|
||||
|
||||
const bool mipmaps = r_img->has_mipmaps();
|
||||
const bool has_mipmaps = r_img->has_mipmaps();
|
||||
int width = r_img->get_width();
|
||||
int height = r_img->get_height();
|
||||
|
||||
|
|
@ -164,109 +183,115 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
|
|||
are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from,
|
||||
the surface pitch, which can encompass additional padding beyond the physical surface size.
|
||||
*/
|
||||
int next_width = width <= 2 ? width : (width + 3) & ~3;
|
||||
int next_height = height <= 2 ? height : (height + 3) & ~3;
|
||||
if (next_width != width || next_height != height) {
|
||||
r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS);
|
||||
width = r_img->get_width();
|
||||
height = r_img->get_height();
|
||||
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
width = width <= 2 ? width : (width + 3) & ~3;
|
||||
height = height <= 2 ? height : (height + 3) & ~3;
|
||||
}
|
||||
// ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed.
|
||||
|
||||
// Multiple-of-4 should be guaranteed by above.
|
||||
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
|
||||
// which are individually compressed Image objects that violate the above rule.
|
||||
// Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
|
||||
|
||||
const uint8_t *src_read = r_img->get_data().ptr();
|
||||
|
||||
print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
|
||||
|
||||
int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
|
||||
// Create the buffer for compressed image data.
|
||||
Vector<uint8_t> dest_data;
|
||||
dest_data.resize(dest_size);
|
||||
dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps));
|
||||
uint8_t *dest_write = dest_data.ptrw();
|
||||
|
||||
int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
const uint8_t *src_read = r_img->get_data().ptr();
|
||||
|
||||
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
|
||||
Vector<uint32_t> padded_src;
|
||||
|
||||
for (int i = 0; i < mip_count + 1; i++) {
|
||||
// Get write mip metrics for target image.
|
||||
int orig_mip_w, orig_mip_h;
|
||||
int64_t mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h);
|
||||
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
|
||||
ERR_FAIL_COND(mip_ofs % 8 != 0);
|
||||
uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs];
|
||||
int dest_mip_w, dest_mip_h;
|
||||
int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h);
|
||||
|
||||
// Block size. Align stride to multiple of 4 (RGBA8).
|
||||
int mip_w = (orig_mip_w + 3) & ~3;
|
||||
int mip_h = (orig_mip_h + 3) & ~3;
|
||||
const uint32_t blocks = mip_w * mip_h / 16;
|
||||
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
|
||||
ERR_FAIL_COND(dest_mip_ofs % 8 != 0);
|
||||
uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs);
|
||||
|
||||
// Block size.
|
||||
dest_mip_w = (dest_mip_w + 3) & ~3;
|
||||
dest_mip_h = (dest_mip_h + 3) & ~3;
|
||||
const uint32_t blocks = dest_mip_w * dest_mip_h / 16;
|
||||
|
||||
// Get mip data from source image for reading.
|
||||
int64_t src_mip_ofs = r_img->get_mipmap_offset(i);
|
||||
const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs];
|
||||
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);
|
||||
|
||||
const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs);
|
||||
|
||||
// Pad textures to nearest block by smearing.
|
||||
if (mip_w != orig_mip_w || mip_h != orig_mip_h) {
|
||||
padded_src.resize(mip_w * mip_h);
|
||||
if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) {
|
||||
// Reserve the buffer for padded image data.
|
||||
padded_src.resize(dest_mip_w * dest_mip_h);
|
||||
uint32_t *ptrw = padded_src.ptrw();
|
||||
|
||||
int x = 0, y = 0;
|
||||
for (y = 0; y < orig_mip_h; y++) {
|
||||
for (x = 0; x < orig_mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x];
|
||||
for (y = 0; y < src_mip_h; y++) {
|
||||
for (x = 0; x < src_mip_w; x++) {
|
||||
ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x];
|
||||
}
|
||||
|
||||
// First, smear in x.
|
||||
for (; x < mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1];
|
||||
for (; x < dest_mip_w; x++) {
|
||||
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Then, smear in y.
|
||||
for (; y < mip_h; y++) {
|
||||
for (x = 0; x < mip_w; x++) {
|
||||
ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w];
|
||||
for (; y < dest_mip_h; y++) {
|
||||
for (x = 0; x < dest_mip_w; x++) {
|
||||
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w];
|
||||
}
|
||||
}
|
||||
|
||||
// Override the src_mip_read pointer to our temporary Vector.
|
||||
src_mip_read = padded_src.ptr();
|
||||
}
|
||||
|
||||
switch (p_compresstype) {
|
||||
switch (p_compress_type) {
|
||||
case EtcpakType::ETCPAK_TYPE_ETC1:
|
||||
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2:
|
||||
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true);
|
||||
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
|
||||
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true);
|
||||
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_R:
|
||||
CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
|
||||
CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT1:
|
||||
CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressBc1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5:
|
||||
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
|
||||
CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressBc3(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_R:
|
||||
CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
|
||||
CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w);
|
||||
CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -276,7 +301,8 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) {
|
|||
}
|
||||
|
||||
// Replace original image with compressed one.
|
||||
r_img->set_data(width, height, mipmaps, target_format, dest_data);
|
||||
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);
|
||||
|
||||
print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
#ifndef IMAGE_COMPRESS_ETCPAK_H
|
||||
#define IMAGE_COMPRESS_ETCPAK_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
enum class EtcpakType {
|
||||
|
|
@ -51,6 +53,8 @@ void _compress_etc1(Image *r_img);
|
|||
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels);
|
||||
void _compress_bc(Image *r_img, Image::UsedChannels p_channels);
|
||||
|
||||
void _compress_etcpak(EtcpakType p_compresstype, Image *r_img);
|
||||
void _compress_etcpak(EtcpakType p_compress_type, Image *r_img);
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
#endif // IMAGE_COMPRESS_ETCPAK_H
|
||||
|
|
|
|||
197
engine/modules/etcpak/image_decompress_etcpak.cpp
Normal file
197
engine/modules/etcpak/image_decompress_etcpak.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
/**************************************************************************/
|
||||
/* image_decompress_etcpak.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_etcpak.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
#include <DecodeRGB.hpp>
|
||||
|
||||
#define ETCPAK_R_BLOCK_SIZE 8
|
||||
#define ETCPAK_RG_BLOCK_SIZE 16
|
||||
#define ETCPAK_RGB_BLOCK_SIZE 8
|
||||
#define ETCPAK_RGBA_BLOCK_SIZE 16
|
||||
|
||||
static void decompress_image(EtcpakFormat 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);
|
||||
|
||||
#define DECOMPRESS_LOOP(m_func, m_block_size, m_color_bytesize) \
|
||||
for (uint64_t y = 0; y < height; y += 4) { \
|
||||
for (uint64_t x = 0; x < width; x += 4) { \
|
||||
m_func(&src_blocks[src_pos], &dec_blocks[dst_pos], width); \
|
||||
src_pos += m_block_size; \
|
||||
dst_pos += 4 * m_color_bytesize; \
|
||||
} \
|
||||
dst_pos += 3 * width * m_color_bytesize; \
|
||||
}
|
||||
|
||||
#define DECOMPRESS_LOOP_SAFE(m_func, m_block_size, m_color_bytesize, m_output) \
|
||||
for (uint64_t y = 0; y < height; y += 4) { \
|
||||
for (uint64_t x = 0; x < width; x += 4) { \
|
||||
const uint32_t yblock = MIN(height - y, 4ul); \
|
||||
const uint32_t xblock = MIN(width - x, 4ul); \
|
||||
\
|
||||
const bool incomplete = yblock < 4 && xblock < 4; \
|
||||
uint8_t *dec_out = incomplete ? m_output : &dec_blocks[y * 4 * width + x * m_color_bytesize]; \
|
||||
\
|
||||
m_func(&src_blocks[src_pos], dec_out, incomplete ? 4 : width); \
|
||||
src_pos += m_block_size; \
|
||||
\
|
||||
if (incomplete) { \
|
||||
for (uint32_t cy = 0; cy < yblock; cy++) { \
|
||||
for (uint32_t cx = 0; cx < xblock; cx++) { \
|
||||
memcpy(&dec_blocks[(y + cy) * 4 * width + (x + cx) * m_color_bytesize], &m_output[cy * 4 + cx * m_color_bytesize], m_color_bytesize); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
uint64_t src_pos = 0;
|
||||
|
||||
uint8_t rgba8_output[4 * 4 * 4];
|
||||
|
||||
switch (format) {
|
||||
case Etcpak_R: {
|
||||
DECOMPRESS_LOOP_SAFE(DecodeRBlock, ETCPAK_R_BLOCK_SIZE, 4, rgba8_output)
|
||||
} break;
|
||||
case Etcpak_RG: {
|
||||
DECOMPRESS_LOOP_SAFE(DecodeRGBlock, ETCPAK_RG_BLOCK_SIZE, 4, rgba8_output)
|
||||
} break;
|
||||
case Etcpak_RGB: {
|
||||
DECOMPRESS_LOOP_SAFE(DecodeRGBBlock, ETCPAK_RGB_BLOCK_SIZE, 4, rgba8_output)
|
||||
} break;
|
||||
case Etcpak_RGBA: {
|
||||
DECOMPRESS_LOOP_SAFE(DecodeRGBABlock, ETCPAK_RGBA_BLOCK_SIZE, 4, rgba8_output)
|
||||
} break;
|
||||
}
|
||||
|
||||
} else {
|
||||
uint64_t src_pos = 0, dst_pos = 0;
|
||||
|
||||
switch (format) {
|
||||
case Etcpak_R: {
|
||||
DECOMPRESS_LOOP(DecodeRBlock, ETCPAK_R_BLOCK_SIZE, 4)
|
||||
} break;
|
||||
case Etcpak_RG: {
|
||||
DECOMPRESS_LOOP(DecodeRGBlock, ETCPAK_RG_BLOCK_SIZE, 4)
|
||||
} break;
|
||||
case Etcpak_RGB: {
|
||||
DECOMPRESS_LOOP(DecodeRGBBlock, ETCPAK_RGB_BLOCK_SIZE, 4)
|
||||
} break;
|
||||
case Etcpak_RGBA: {
|
||||
DECOMPRESS_LOOP(DecodeRGBABlock, ETCPAK_RGBA_BLOCK_SIZE, 4)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef DECOMPRESS_LOOP
|
||||
#undef DECOMPRESS_LOOP_SAFE
|
||||
}
|
||||
|
||||
void _decompress_etc(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();
|
||||
|
||||
// Compressed images' dimensions should be padded to the upper multiple of 4.
|
||||
// If they aren't, they need to be realigned (the actual data is correctly padded though).
|
||||
if (width % 4 != 0 || height % 4 != 0) {
|
||||
int new_width = width + (4 - (width % 4));
|
||||
int new_height = height + (4 - (height % 4));
|
||||
|
||||
print_verbose(vformat("Compressed image (%s) has dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", p_image->get_path(), width, height, new_width, new_height));
|
||||
|
||||
width = new_width;
|
||||
height = new_height;
|
||||
}
|
||||
|
||||
Image::Format source_format = p_image->get_format();
|
||||
Image::Format target_format = Image::FORMAT_RGBA8;
|
||||
|
||||
EtcpakFormat etcpak_format = Etcpak_R;
|
||||
|
||||
switch (source_format) {
|
||||
case Image::FORMAT_ETC:
|
||||
case Image::FORMAT_ETC2_RGB8:
|
||||
etcpak_format = Etcpak_RGB;
|
||||
break;
|
||||
|
||||
case Image::FORMAT_ETC2_RGBA8:
|
||||
case Image::FORMAT_ETC2_RA_AS_RG:
|
||||
etcpak_format = Etcpak_RGBA;
|
||||
break;
|
||||
|
||||
case Image::FORMAT_ETC2_R11:
|
||||
etcpak_format = Etcpak_R;
|
||||
break;
|
||||
|
||||
case Image::FORMAT_ETC2_RG11:
|
||||
etcpak_format = Etcpak_RG;
|
||||
break;
|
||||
|
||||
default:
|
||||
ERR_FAIL_MSG(vformat("etcpak: Can't decompress image %s with an unknown format: %s.", p_image->get_path(), 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->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_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h);
|
||||
int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i);
|
||||
decompress_image(etcpak_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h);
|
||||
}
|
||||
|
||||
p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
|
||||
|
||||
// Swap channels if the format is using a channel swizzle.
|
||||
if (source_format == Image::FORMAT_ETC2_RA_AS_RG) {
|
||||
p_image->convert_ra_rgba8_to_rg();
|
||||
}
|
||||
|
||||
print_verbose(vformat("etcpak: Decompression of %dx%d %s image %s with %d mipmaps took %d ms.",
|
||||
p_image->get_width(), p_image->get_height(), Image::get_format_name(source_format), p_image->get_path(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time));
|
||||
}
|
||||
45
engine/modules/etcpak/image_decompress_etcpak.h
Normal file
45
engine/modules/etcpak/image_decompress_etcpak.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**************************************************************************/
|
||||
/* image_decompress_etcpak.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef IMAGE_DECOMPRESS_ETCPAK_H
|
||||
#define IMAGE_DECOMPRESS_ETCPAK_H
|
||||
|
||||
#include "core/io/image.h"
|
||||
|
||||
enum EtcpakFormat {
|
||||
Etcpak_R,
|
||||
Etcpak_RG,
|
||||
Etcpak_RGB,
|
||||
Etcpak_RGBA,
|
||||
};
|
||||
|
||||
void _decompress_etc(Image *p_image);
|
||||
|
||||
#endif // IMAGE_DECOMPRESS_ETCPAK_H
|
||||
|
|
@ -31,15 +31,21 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "image_compress_etcpak.h"
|
||||
#include "image_decompress_etcpak.h"
|
||||
|
||||
void initialize_etcpak_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Image::_image_compress_etc1_func = _compress_etc1;
|
||||
Image::_image_compress_etc2_func = _compress_etc2;
|
||||
Image::_image_compress_bc_func = _compress_bc;
|
||||
#endif
|
||||
|
||||
Image::_image_decompress_etc1 = _decompress_etc;
|
||||
Image::_image_decompress_etc2 = _decompress_etc;
|
||||
}
|
||||
|
||||
void uninitialize_etcpak_module(ModuleInitializationLevel p_level) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
|
|||
|
|
@ -32,15 +32,11 @@
|
|||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "editor_scene_importer_ufbx.h"
|
||||
#include "modules/gltf/gltf_document.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor_scene_importer_ufbx.h"
|
||||
|
||||
uint32_t EditorSceneFormatImporterFBX2GLTF::get_import_flags() const {
|
||||
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
|
||||
}
|
||||
#include "modules/gltf/gltf_document.h"
|
||||
|
||||
void EditorSceneFormatImporterFBX2GLTF::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("fbx");
|
||||
|
|
@ -124,7 +120,7 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint
|
|||
#endif
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation,
|
||||
Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) {
|
||||
// Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf.
|
||||
// These options are available only for ufbx.
|
||||
|
|
|
|||
|
|
@ -42,14 +42,13 @@ class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter {
|
|||
GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter);
|
||||
|
||||
public:
|
||||
virtual uint32_t get_import_flags() const override;
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
||||
virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,10 +37,6 @@
|
|||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
uint32_t EditorSceneFormatImporterUFBX::get_import_flags() const {
|
||||
return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION;
|
||||
}
|
||||
|
||||
void EditorSceneFormatImporterUFBX::get_extensions(List<String> *r_extensions) const {
|
||||
r_extensions->push_back("fbx");
|
||||
}
|
||||
|
|
@ -88,7 +84,7 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t
|
|||
return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
|
||||
}
|
||||
|
||||
Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation,
|
||||
Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, const String &p_scene_import_type,
|
||||
const String &p_option, const HashMap<StringName, Variant> &p_options) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,14 +46,13 @@ public:
|
|||
FBX_IMPORTER_UFBX,
|
||||
FBX_IMPORTER_FBX2GLTF,
|
||||
};
|
||||
virtual uint32_t get_import_flags() const override;
|
||||
virtual void get_extensions(List<String> *r_extensions) const override;
|
||||
virtual Node *import_scene(const String &p_path, uint32_t p_flags,
|
||||
const HashMap<StringName, Variant> &p_options,
|
||||
List<String> *r_missing_deps, Error *r_err = nullptr) override;
|
||||
virtual void get_import_options(const String &p_path,
|
||||
List<ResourceImporter::ImportOption> *r_options) override;
|
||||
virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
|
||||
virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
|
||||
const HashMap<StringName, Variant> &p_options) override;
|
||||
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -288,14 +288,8 @@ String FBXDocument::_gen_unique_name(HashSet<String> &unique_names, const String
|
|||
}
|
||||
|
||||
String FBXDocument::_sanitize_animation_name(const String &p_name) {
|
||||
// Animations disallow the normal node invalid characters as well as "," and "["
|
||||
// (See animation/animation_player.cpp::add_animation)
|
||||
|
||||
// TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
|
||||
String anim_name = p_name.validate_node_name();
|
||||
anim_name = anim_name.replace(",", "");
|
||||
anim_name = anim_name.replace("[", "");
|
||||
return anim_name;
|
||||
return AnimationLibrary::validate_library_name(anim_name);
|
||||
}
|
||||
|
||||
String FBXDocument::_gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name) {
|
||||
|
|
@ -375,21 +369,25 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
|
|||
// all skin clusters connected to the bone.
|
||||
for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
|
||||
ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
|
||||
if (!child_cluster)
|
||||
if (!child_cluster) {
|
||||
continue;
|
||||
}
|
||||
ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
|
||||
if (!child_deformer)
|
||||
if (!child_deformer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found a skin cluster: Now iterate through all the skin clusters of the parent and
|
||||
// try to find one that used by the same deformer.
|
||||
for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) {
|
||||
ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst);
|
||||
if (!parent_cluster)
|
||||
if (!parent_cluster) {
|
||||
continue;
|
||||
}
|
||||
ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster);
|
||||
if (parent_deformer != child_deformer)
|
||||
if (parent_deformer != child_deformer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Success: Found two skin clusters from the same deformer, now we can resolve the
|
||||
// local bind pose from the difference between the two world-space bind poses.
|
||||
|
|
@ -875,7 +873,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
|
|||
const int material = int(fbx_material->typed_id);
|
||||
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
|
||||
Ref<Material> mat3d = p_state->materials[material];
|
||||
ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
|
||||
ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
|
||||
|
||||
Ref<BaseMaterial3D> base_material = mat3d;
|
||||
if (has_vertex_color && base_material.is_valid()) {
|
||||
|
|
@ -891,7 +889,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
|
|||
}
|
||||
mat = mat3d;
|
||||
}
|
||||
ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT);
|
||||
ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
|
||||
mat_name = mat->get_name();
|
||||
}
|
||||
import_mesh->add_surface(primitive, array, morphs,
|
||||
|
|
@ -1056,7 +1054,7 @@ GLTFImageIndex FBXDocument::_parse_image_save_image(Ref<FBXState> p_state, const
|
|||
}
|
||||
|
||||
Error FBXDocument::_parse_images(Ref<FBXState> p_state, const String &p_base_path) {
|
||||
ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
|
||||
|
||||
const ufbx_scene *fbx_scene = p_state->scene.get();
|
||||
for (int texture_i = 0; texture_i < static_cast<int>(fbx_scene->texture_files.count); texture_i++) {
|
||||
|
|
@ -1395,7 +1393,7 @@ Error FBXDocument::_parse_animations(Ref<FBXState> p_state) {
|
|||
|
||||
for (const ufbx_baked_node &fbx_baked_node : fbx_baked_anim->nodes) {
|
||||
const GLTFNodeIndex node = fbx_baked_node.typed_id;
|
||||
GLTFAnimation::Track &track = animation->get_tracks()[node];
|
||||
GLTFAnimation::NodeTrack &track = animation->get_node_tracks()[node];
|
||||
|
||||
for (const ufbx_baked_vec3 &key : fbx_baked_node.translation_keys) {
|
||||
track.position_track.times.push_back(float(key.time));
|
||||
|
|
@ -1785,8 +1783,8 @@ void FBXDocument::_import_animation(Ref<FBXState> p_state, AnimationPlayer *p_an
|
|||
|
||||
double anim_start_offset = p_trimming ? double(additional_animation_data["time_begin"]) : 0.0;
|
||||
|
||||
for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) {
|
||||
const GLTFAnimation::Track &track = track_i.value;
|
||||
for (const KeyValue<int, GLTFAnimation::NodeTrack> &track_i : anim->get_node_tracks()) {
|
||||
const GLTFAnimation::NodeTrack &track = track_i.value;
|
||||
//need to find the path: for skeletons, weight tracks will affect the mesh
|
||||
NodePath node_path;
|
||||
//for skeletons, transform tracks always affect bones
|
||||
|
|
@ -2017,6 +2015,7 @@ void FBXDocument::_process_mesh_instances(Ref<FBXState> p_state, Node *p_scene_r
|
|||
ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i));
|
||||
|
||||
mi->get_parent()->remove_child(mi);
|
||||
mi->set_owner(nullptr);
|
||||
skeleton->add_child(mi, true);
|
||||
mi->set_owner(skeleton->get_owner());
|
||||
|
||||
|
|
@ -2114,13 +2113,9 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess>
|
|||
return OK;
|
||||
}
|
||||
|
||||
void FBXDocument::_bind_methods() {
|
||||
}
|
||||
|
||||
Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
|
||||
Ref<FBXState> state = p_state;
|
||||
ERR_FAIL_COND_V(state.is_null(), nullptr);
|
||||
ERR_FAIL_NULL_V(state, nullptr);
|
||||
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
|
||||
p_state->set_bake_fps(p_bake_fps);
|
||||
GLTFNodeIndex fbx_root = state->root_nodes.write[0];
|
||||
|
|
@ -2248,7 +2243,7 @@ Error FBXDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint3
|
|||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
|
||||
ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN);
|
||||
ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
|
||||
String base_path = p_base_path;
|
||||
if (base_path.is_empty()) {
|
||||
base_path = p_path.get_base_dir();
|
||||
|
|
|
|||
|
|
@ -61,9 +61,6 @@ public:
|
|||
PackedByteArray generate_buffer(Ref<GLTFState> p_state) override;
|
||||
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) override;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const;
|
||||
void _process_uv_set(PackedVector2Array &uv_array);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
static void _editor_init() {
|
||||
Ref<EditorSceneFormatImporterUFBX> import_fbx;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
@ -66,8 +67,6 @@ if env["builtin_freetype"]:
|
|||
env.Prepend(CPPPATH=[thirdparty_dir + "/include"])
|
||||
|
||||
env_freetype.Append(CPPDEFINES=["FT2_BUILD_LIBRARY", "FT_CONFIG_OPTION_USE_PNG", "FT_CONFIG_OPTION_SYSTEM_ZLIB"])
|
||||
if env.dev_build:
|
||||
env_freetype.Append(CPPDEFINES=["ZLIB_DEBUG"])
|
||||
|
||||
# Also requires libpng headers
|
||||
if env["builtin_libpng"]:
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
[*.gd]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.out]
|
||||
insert_final_newline = true
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ def get_doc_classes():
|
|||
return [
|
||||
"@GDScript",
|
||||
"GDScript",
|
||||
"GDScriptSyntaxHighlighter",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
Built-in GDScript constants, functions, and annotations.
|
||||
</brief_description>
|
||||
<description>
|
||||
A list of GDScript-specific utility functions and annotations accessible from any script.
|
||||
For the list of the global functions and constants see [@GlobalScope].
|
||||
A list of utility functions and annotations accessible from any script written in GDScript.
|
||||
For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="Color8">
|
||||
<method name="Color8" deprecated="Use [method Color.from_rgba8] instead.">
|
||||
<return type="Color" />
|
||||
<param index="0" name="r8" type="int" />
|
||||
<param index="1" name="g8" type="int" />
|
||||
|
|
@ -52,16 +52,16 @@
|
|||
<description>
|
||||
Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code).
|
||||
[codeblock]
|
||||
a = char(65) # a is "A"
|
||||
a = char(65 + 32) # a is "a"
|
||||
a = char(8364) # a is "€"
|
||||
var upper = char(65) # upper is "A"
|
||||
var lower = char(65 + 32) # lower is "a"
|
||||
var euro = char(8364) # euro is "€"
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="what" type="Variant" />
|
||||
<param index="1" name="type" type="int" />
|
||||
<param index="1" name="type" type="int" enum="Variant.Type" />
|
||||
<description>
|
||||
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
|
||||
[codeblock]
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="dict_to_inst">
|
||||
<method name="dict_to_inst" deprecated="Consider using [method JSON.to_native] or [method Object.get_property_list] instead.">
|
||||
<return type="Object" />
|
||||
<param index="0" name="dictionary" type="Dictionary" />
|
||||
<description>
|
||||
|
|
@ -103,12 +103,11 @@
|
|||
[b]Note:[/b] Calling this function from a [Thread] is not supported. Doing so will return an empty array.
|
||||
</description>
|
||||
</method>
|
||||
<method name="inst_to_dict">
|
||||
<method name="inst_to_dict" deprecated="Consider using [method JSON.from_native] or [method Object.get_property_list] instead.">
|
||||
<return type="Dictionary" />
|
||||
<param index="0" name="instance" type="Object" />
|
||||
<description>
|
||||
Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing.
|
||||
[b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts.
|
||||
[codeblock]
|
||||
var foo = "bar"
|
||||
func _ready():
|
||||
|
|
@ -121,6 +120,8 @@
|
|||
[@subpath, @path, foo]
|
||||
[, res://test.gd, bar]
|
||||
[/codeblock]
|
||||
[b]Note:[/b] This function can only be used to serialize objects with an attached [GDScript] stored in a separate file. Objects without an attached script, with a script written in another language, or with a built-in script are not supported.
|
||||
[b]Note:[/b] This function is not recursive, which means that nested objects will not be represented as dictionaries. Also, properties passed by reference ([Object], [Dictionary], [Array], and packed arrays) are copied by reference, not duplicated.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_instance_of">
|
||||
|
|
@ -133,7 +134,7 @@
|
|||
- An [Object]-derived class which exists in [ClassDB], for example [Node].
|
||||
- A [Script] (you can use any class, including inner one).
|
||||
Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking.
|
||||
Examples:
|
||||
[b]Examples:[/b]
|
||||
[codeblock]
|
||||
print(is_instance_of(a, TYPE_INT))
|
||||
print(is_instance_of(a, Node))
|
||||
|
|
@ -150,10 +151,10 @@
|
|||
<description>
|
||||
Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
|
||||
[codeblock]
|
||||
a = [1, 2, 3, 4]
|
||||
var a = [1, 2, 3, 4]
|
||||
len(a) # Returns 4
|
||||
|
||||
b = "Hello!"
|
||||
var b = "Hello!"
|
||||
len(b) # Returns 6
|
||||
[/codeblock]
|
||||
</description>
|
||||
|
|
@ -220,7 +221,7 @@
|
|||
[code]range(b: int, n: int, s: int)[/code]: Starts from [code]b[/code], increases/decreases by steps of [code]s[/code], and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively. The argument [code]s[/code] [b]can[/b] be negative, but not [code]0[/code]. If [code]s[/code] is [code]0[/code], an error message is printed.
|
||||
[method range] converts all arguments to [int] before processing.
|
||||
[b]Note:[/b] Returns an empty array if no value meets the value constraint (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]).
|
||||
Examples:
|
||||
[b]Examples:[/b]
|
||||
[codeblock]
|
||||
print(range(4)) # Prints [0, 1, 2, 3]
|
||||
print(range(2, 5)) # Prints [2, 3, 4]
|
||||
|
|
@ -666,7 +667,54 @@
|
|||
@export var car_label = "Speedy"
|
||||
@export var car_number = 3
|
||||
[/codeblock]
|
||||
[b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups.
|
||||
[b]Note:[/b] Subgroups cannot be nested, but you can use the slash separator ([code]/[/code]) to achieve the desired effect:
|
||||
[codeblock]
|
||||
@export_group("Car Properties")
|
||||
@export_subgroup("Wheels", "wheel_")
|
||||
@export_subgroup("Wheels/Front", "front_wheel_")
|
||||
@export var front_wheel_strength = 10
|
||||
@export var front_wheel_mobility = 5
|
||||
@export_subgroup("Wheels/Rear", "rear_wheel_")
|
||||
@export var rear_wheel_strength = 8
|
||||
@export var rear_wheel_mobility = 3
|
||||
@export_subgroup("Wheels", "wheel_")
|
||||
@export var wheel_material: PhysicsMaterial
|
||||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@export_tool_button">
|
||||
<return type="void" />
|
||||
<param index="0" name="text" type="String" />
|
||||
<param index="1" name="icon" type="String" default="""" />
|
||||
<description>
|
||||
Export a [Callable] property as a clickable button with the label [param text]. When the button is pressed, the callable is called.
|
||||
If [param icon] is specified, it is used to fetch an icon for the button via [method Control.get_theme_icon], from the [code]"EditorIcons"[/code] theme type. If [param icon] is omitted, the default [code]"Callable"[/code] icon is used instead.
|
||||
Consider using the [EditorUndoRedoManager] to allow the action to be reverted safely.
|
||||
See also [constant PROPERTY_HINT_TOOL_BUTTON].
|
||||
[codeblock]
|
||||
@tool
|
||||
extends Sprite2D
|
||||
|
||||
@export_tool_button("Hello") var hello_action = hello
|
||||
@export_tool_button("Randomize the color!", "ColorRect")
|
||||
var randomize_color_action = randomize_color
|
||||
|
||||
func hello():
|
||||
print("Hello world!")
|
||||
|
||||
func randomize_color():
|
||||
var undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
undo_redo.create_action("Randomized Sprite2D Color")
|
||||
undo_redo.add_do_property(self, &"self_modulate", Color(randf(), randf(), randf()))
|
||||
undo_redo.add_undo_property(self, &"self_modulate", self_modulate)
|
||||
undo_redo.commit_action()
|
||||
[/codeblock]
|
||||
[b]Note:[/b] The property is exported without the [constant PROPERTY_USAGE_STORAGE] flag because a [Callable] cannot be properly serialized and stored in a file.
|
||||
[b]Note:[/b] In an exported project neither [EditorInterface] nor [EditorUndoRedoManager] exist, which may cause some scripts to break. To prevent this, you can use [method Engine.get_singleton] and omit the static type from the variable declaration:
|
||||
[codeblock]
|
||||
var undo_redo = Engine.get_singleton(&"EditorInterface").get_editor_undo_redo()
|
||||
[/codeblock]
|
||||
[b]Note:[/b] Avoid storing lambda callables in member variables of [RefCounted]-based classes (e.g. resources), as this can lead to memory leaks. Use only method callables and optionally [method Callable.bind] or [method Callable.unbind].
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@icon">
|
||||
|
|
@ -679,7 +727,7 @@
|
|||
[/codeblock]
|
||||
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
|
||||
[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
|
||||
[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
|
||||
[b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@onready">
|
||||
|
|
@ -687,7 +735,7 @@
|
|||
<description>
|
||||
Mark the following property as assigned when the [Node] is ready. Values for these properties are not assigned immediately when the node is initialized ([method Object._init]), and instead are computed and stored right before [method Node._ready].
|
||||
[codeblock]
|
||||
@onready var character_name: Label = $Label
|
||||
@onready var character_name = $Label
|
||||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
|
|
@ -747,6 +795,33 @@
|
|||
@warning_ignore("unreachable_code")
|
||||
print("unreachable")
|
||||
[/codeblock]
|
||||
See also [annotation @warning_ignore_start] and [annotation @warning_ignore_restore].
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@warning_ignore_restore" qualifiers="vararg">
|
||||
<return type="void" />
|
||||
<param index="0" name="warning" type="String" />
|
||||
<description>
|
||||
Stops ignoring the listed warning types after [annotation @warning_ignore_start]. Ignoring the specified warning types will be reset to Project Settings. This annotation can be omitted to ignore the warning types until the end of the file.
|
||||
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_restore] annotation must be string literals (constant expressions are not supported).
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@warning_ignore_start" qualifiers="vararg">
|
||||
<return type="void" />
|
||||
<param index="0" name="warning" type="String" />
|
||||
<description>
|
||||
Starts ignoring the listed warning types until the end of the file or the [annotation @warning_ignore_restore] annotation with the given warning type.
|
||||
[codeblock]
|
||||
func test():
|
||||
var a = 1 # Warning (if enabled in the Project Settings).
|
||||
@warning_ignore_start("unused_variable")
|
||||
var b = 2 # No warning.
|
||||
var c = 3 # No warning.
|
||||
@warning_ignore_restore("unused_variable")
|
||||
var d = 4 # Warning (if enabled in the Project Settings).
|
||||
[/codeblock]
|
||||
[b]Note:[/b] To suppress a single warning, use [annotation @warning_ignore] instead.
|
||||
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_start] annotation must be string literals (constant expressions are not supported).
|
||||
</description>
|
||||
</annotation>
|
||||
</annotations>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,10 @@
|
|||
<return type="Variant" />
|
||||
<description>
|
||||
Returns a new instance of the script.
|
||||
For example:
|
||||
[codeblock]
|
||||
var MyClass = load("myclass.gd")
|
||||
var instance = MyClass.new()
|
||||
assert(instance.get_script() == MyClass)
|
||||
print(instance.get_script() == MyClass) # Prints true
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes.
|
||||
</brief_description>
|
||||
<description>
|
||||
[b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings.
|
||||
[codeblocks]
|
||||
[gdscript]
|
||||
var code_preview = TextEdit.new()
|
||||
var highlighter = GDScriptSyntaxHighlighter.new()
|
||||
code_preview.syntax_highlighter = highlighter
|
||||
[/gdscript]
|
||||
[csharp]
|
||||
var codePreview = new TextEdit();
|
||||
var highlighter = new GDScriptSyntaxHighlighter();
|
||||
codePreview.SyntaxHighlighter = highlighter;
|
||||
[/csharp]
|
||||
[/codeblocks]
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
</class>
|
||||
|
|
@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
|
||||
String key, value;
|
||||
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
|
||||
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
|
||||
if (key != "Variant" || value != "Variant") {
|
||||
r_type = "Dictionary[" + key + ", " + value + "]";
|
||||
return;
|
||||
}
|
||||
}
|
||||
r_type = Variant::get_type_name(p_gdtype.builtin_type);
|
||||
return;
|
||||
case GDType::NATIVE:
|
||||
|
|
@ -130,10 +139,11 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
|
|||
r_type = "int";
|
||||
r_enum = String(p_gdtype.native_type).replace("::", ".");
|
||||
if (r_enum.begins_with("res://")) {
|
||||
r_enum = r_enum.trim_prefix("res://");
|
||||
int dot_pos = r_enum.rfind(".");
|
||||
int dot_pos = r_enum.rfind_char('.');
|
||||
if (dot_pos >= 0) {
|
||||
r_enum = r_enum.left(dot_pos).quote() + r_enum.substr(dot_pos);
|
||||
r_enum = _get_script_name(r_enum.left(dot_pos)) + r_enum.substr(dot_pos);
|
||||
} else {
|
||||
r_enum = _get_script_name(r_enum);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
|
@ -155,34 +165,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
|
|||
return "<Object>";
|
||||
case Variant::DICTIONARY: {
|
||||
const Dictionary dict = p_variant;
|
||||
String result;
|
||||
|
||||
if (dict.is_typed()) {
|
||||
result += "Dictionary[";
|
||||
|
||||
Ref<Script> key_script = dict.get_typed_key_script();
|
||||
if (key_script.is_valid()) {
|
||||
if (key_script->get_global_name() != StringName()) {
|
||||
result += key_script->get_global_name();
|
||||
} else if (!key_script->get_path().get_file().is_empty()) {
|
||||
result += key_script->get_path().get_file();
|
||||
} else {
|
||||
result += dict.get_typed_key_class_name();
|
||||
}
|
||||
} else if (dict.get_typed_key_class_name() != StringName()) {
|
||||
result += dict.get_typed_key_class_name();
|
||||
} else if (dict.is_typed_key()) {
|
||||
result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
|
||||
} else {
|
||||
result += "Variant";
|
||||
}
|
||||
|
||||
result += ", ";
|
||||
|
||||
Ref<Script> value_script = dict.get_typed_value_script();
|
||||
if (value_script.is_valid()) {
|
||||
if (value_script->get_global_name() != StringName()) {
|
||||
result += value_script->get_global_name();
|
||||
} else if (!value_script->get_path().get_file().is_empty()) {
|
||||
result += value_script->get_path().get_file();
|
||||
} else {
|
||||
result += dict.get_typed_value_class_name();
|
||||
}
|
||||
} else if (dict.get_typed_value_class_name() != StringName()) {
|
||||
result += dict.get_typed_value_class_name();
|
||||
} else if (dict.is_typed_value()) {
|
||||
result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
|
||||
} else {
|
||||
result += "Variant";
|
||||
}
|
||||
|
||||
result += "](";
|
||||
}
|
||||
|
||||
if (dict.is_empty()) {
|
||||
return "{}";
|
||||
}
|
||||
result += "{}";
|
||||
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
|
||||
result += "{...}";
|
||||
} else {
|
||||
result += "{";
|
||||
|
||||
if (p_recursion_level > MAX_RECURSION_LEVEL) {
|
||||
return "{...}";
|
||||
}
|
||||
List<Variant> keys;
|
||||
dict.get_key_list(&keys);
|
||||
keys.sort_custom<StringLikeVariantOrder>();
|
||||
|
||||
List<Variant> keys;
|
||||
dict.get_key_list(&keys);
|
||||
keys.sort();
|
||||
|
||||
String data;
|
||||
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
|
||||
if (E->prev()) {
|
||||
data += ", ";
|
||||
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
|
||||
if (E->prev()) {
|
||||
result += ", ";
|
||||
}
|
||||
result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
|
||||
}
|
||||
data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
|
||||
|
||||
result += "}";
|
||||
}
|
||||
|
||||
return "{" + data + "}";
|
||||
if (dict.is_typed()) {
|
||||
result += ")";
|
||||
}
|
||||
|
||||
return result;
|
||||
} break;
|
||||
case Variant::ARRAY: {
|
||||
const Array array = p_variant;
|
||||
String result;
|
||||
|
||||
if (array.get_typed_builtin() != Variant::NIL) {
|
||||
if (array.is_typed()) {
|
||||
result += "Array[";
|
||||
|
||||
Ref<Script> script = array.get_typed_script();
|
||||
|
|
@ -209,16 +267,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
|
|||
result += "[...]";
|
||||
} else {
|
||||
result += "[";
|
||||
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
if (i > 0) {
|
||||
result += ", ";
|
||||
}
|
||||
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
|
||||
}
|
||||
|
||||
result += "]";
|
||||
}
|
||||
|
||||
if (array.get_typed_builtin() != Variant::NIL) {
|
||||
if (array.is_typed()) {
|
||||
result += ")";
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +289,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
|
|||
}
|
||||
}
|
||||
|
||||
String GDScriptDocGen::_docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
|
||||
String GDScriptDocGen::docvalue_from_expression(const GDP::ExpressionNode *p_expression) {
|
||||
ERR_FAIL_NULL_V(p_expression, String());
|
||||
|
||||
if (p_expression->is_constant) {
|
||||
|
|
@ -325,6 +385,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
const_doc.name = const_name;
|
||||
const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value);
|
||||
const_doc.is_value_valid = true;
|
||||
_doctype_from_gdtype(m_const->get_datatype(), const_doc.type, const_doc.enumeration);
|
||||
const_doc.description = m_const->doc_data.description;
|
||||
const_doc.is_deprecated = m_const->doc_data.is_deprecated;
|
||||
const_doc.deprecated_message = m_const->doc_data.deprecated_message;
|
||||
|
|
@ -348,7 +409,9 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
method_doc.experimental_message = m_func->doc_data.experimental_message;
|
||||
method_doc.qualifiers = m_func->is_static ? "static" : "";
|
||||
|
||||
if (m_func->return_type) {
|
||||
if (func_name == "_init") {
|
||||
method_doc.return_type = "void";
|
||||
} else if (m_func->return_type) {
|
||||
// `m_func->return_type->get_datatype()` is a metatype.
|
||||
_doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
|
||||
} else if (!m_func->body->has_return) {
|
||||
|
|
@ -363,7 +426,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
arg_doc.name = p->identifier->name;
|
||||
_doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration);
|
||||
if (p->initializer != nullptr) {
|
||||
arg_doc.default_value = _docvalue_from_expression(p->initializer);
|
||||
arg_doc.default_value = docvalue_from_expression(p->initializer);
|
||||
}
|
||||
method_doc.arguments.push_back(arg_doc);
|
||||
}
|
||||
|
|
@ -432,7 +495,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
}
|
||||
|
||||
if (m_var->initializer != nullptr) {
|
||||
prop_doc.default_value = _docvalue_from_expression(m_var->initializer);
|
||||
prop_doc.default_value = docvalue_from_expression(m_var->initializer);
|
||||
}
|
||||
|
||||
prop_doc.overridden = false;
|
||||
|
|
@ -459,6 +522,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
const_doc.name = val.identifier->name;
|
||||
const_doc.value = _docvalue_from_variant(val.value);
|
||||
const_doc.is_value_valid = true;
|
||||
const_doc.type = "int";
|
||||
const_doc.enumeration = name;
|
||||
const_doc.description = val.doc_data.description;
|
||||
const_doc.is_deprecated = val.doc_data.is_deprecated;
|
||||
|
|
@ -481,6 +545,7 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
|||
const_doc.name = name;
|
||||
const_doc.value = _docvalue_from_variant(m_enum_val.value);
|
||||
const_doc.is_value_valid = true;
|
||||
const_doc.type = "int";
|
||||
const_doc.enumeration = "@unnamed_enums";
|
||||
const_doc.description = m_enum_val.doc_data.description;
|
||||
const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated;
|
||||
|
|
@ -508,3 +573,14 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
|
|||
_generate_docs(p_script, p_class);
|
||||
singletons.clear();
|
||||
}
|
||||
|
||||
// This method is needed for the editor, since during autocompletion the script is not compiled, only analyzed.
|
||||
void GDScriptDocGen::doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) {
|
||||
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) {
|
||||
if (E.value.is_singleton) {
|
||||
singletons[E.value.path] = E.key;
|
||||
}
|
||||
}
|
||||
_doctype_from_gdtype(p_gdtype, r_type, r_enum, p_is_return);
|
||||
singletons.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,12 @@ class GDScriptDocGen {
|
|||
static String _get_class_name(const GDP::ClassNode &p_class);
|
||||
static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
|
||||
static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1);
|
||||
static String _docvalue_from_expression(const GDP::ExpressionNode *p_expression);
|
||||
static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
|
||||
|
||||
public:
|
||||
static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class);
|
||||
static void doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false);
|
||||
static String docvalue_from_expression(const GDP::ExpressionNode *p_expression);
|
||||
};
|
||||
|
||||
#endif // GDSCRIPT_DOCGEN_H
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include "core/config/project_settings.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/themes/editor_theme_manager.h"
|
||||
#include "scene/gui/text_edit.h"
|
||||
|
||||
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
|
||||
Dictionary color_map;
|
||||
|
|
@ -93,7 +94,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
|
|||
in_region = color_region_cache[p_line - 1];
|
||||
}
|
||||
|
||||
const String &str = text_edit->get_line(p_line);
|
||||
const String &str = text_edit->get_line_with_ime(p_line);
|
||||
const int line_length = str.length();
|
||||
Color prev_color;
|
||||
|
||||
|
|
@ -163,7 +164,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
|
|||
}
|
||||
if (from + end_key_length > line_length) {
|
||||
// If it's key length and there is a '\', dont skip to highlight esc chars.
|
||||
if (str.find("\\", from) >= 0) {
|
||||
if (str.find_char('\\', from) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +237,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
|
|||
for (; from < line_length; from++) {
|
||||
if (line_length - from < end_key_length) {
|
||||
// Don't break if '\' to highlight esc chars.
|
||||
if (str.find("\\", from) < 0) {
|
||||
if (str.find_char('\\', from) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -350,15 +351,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
|
|||
|
||||
// Special cases for numbers.
|
||||
if (in_number && !is_a_digit) {
|
||||
if (str[j] == 'b' && str[j - 1] == '0') {
|
||||
if ((str[j] == 'b' || str[j] == 'B') && str[j - 1] == '0') {
|
||||
is_bin_notation = true;
|
||||
} else if (str[j] == 'x' && str[j - 1] == '0') {
|
||||
} else if ((str[j] == 'x' || str[j] == 'X') && str[j - 1] == '0') {
|
||||
is_hex_notation = true;
|
||||
} else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) &&
|
||||
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) &&
|
||||
!(str[j] == 'e' && (prev_is_digit || str[j - 1] == '_')) &&
|
||||
} else if (!((str[j] == '-' || str[j] == '+') && (str[j - 1] == 'e' || str[j - 1] == 'E') && !prev_is_digit) &&
|
||||
!(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'B' || str[j - 1] == 'x' || str[j - 1] == 'X' || str[j - 1] == '.')) &&
|
||||
!((str[j] == 'e' || str[j] == 'E') && (prev_is_digit || str[j - 1] == '_')) &&
|
||||
!(str[j] == '.' && (prev_is_digit || (!prev_is_binary_op && (j > 0 && (str[j - 1] == '_' || str[j - 1] == '-' || str[j - 1] == '+' || str[j - 1] == '~'))))) &&
|
||||
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e')) {
|
||||
!((str[j] == '-' || str[j] == '+' || str[j] == '~') && !is_binary_op && !prev_is_binary_op && str[j - 1] != 'e' && str[j - 1] != 'E')) {
|
||||
/* This condition continues number highlighting in special cases.
|
||||
1st row: '+' or '-' after scientific notation (like 3e-4);
|
||||
2nd row: '_' as a numeric separator;
|
||||
|
|
@ -560,12 +561,17 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
|
|||
}
|
||||
}
|
||||
|
||||
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
|
||||
// Set color of StringName, keeping symbol color for binary '&&' and '&'.
|
||||
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
|
||||
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
|
||||
is_binary_op = true;
|
||||
} else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) {
|
||||
if (j + 1 <= line_length - 1 && (str[j + 1] == '\'' || str[j + 1] == '"')) {
|
||||
in_string_name = true;
|
||||
// Cover edge cases of i.e. '+&""' and '&&&""', so the StringName is properly colored.
|
||||
if (prev_is_binary_op && j >= 2 && str[j - 1] == '&' && str[j - 2] != '&') {
|
||||
in_string_name = false;
|
||||
is_binary_op = true;
|
||||
}
|
||||
} else {
|
||||
is_binary_op = true;
|
||||
}
|
||||
} else if (in_region != -1 || is_a_symbol) {
|
||||
in_string_name = false;
|
||||
|
|
@ -701,7 +707,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
|
|||
List<StringName> types;
|
||||
ClassDB::get_class_list(&types);
|
||||
for (const StringName &E : types) {
|
||||
class_names[E] = types_color;
|
||||
if (ClassDB::is_class_exposed(E)) {
|
||||
class_names[E] = types_color;
|
||||
}
|
||||
}
|
||||
|
||||
/* User types. */
|
||||
|
|
@ -813,7 +821,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
|
|||
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
|
||||
continue;
|
||||
}
|
||||
if (prop_name.contains("/")) {
|
||||
if (prop_name.contains_char('/')) {
|
||||
continue;
|
||||
}
|
||||
member_keywords[prop_name] = member_variable_color;
|
||||
|
|
@ -852,6 +860,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
|
|||
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
|
||||
}
|
||||
|
||||
// TODO: Move to editor_settings.cpp
|
||||
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
|
||||
EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
|
||||
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
#define GDSCRIPT_HIGHLIGHTER_H
|
||||
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
#include "scene/gui/text_edit.h"
|
||||
|
||||
class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter {
|
||||
GDCLASS(GDScriptSyntaxHighlighter, EditorSyntaxHighlighter)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin
|
|||
GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions);
|
||||
}
|
||||
|
||||
Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) {
|
||||
Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<Vector<String>> *r_translations) {
|
||||
// Extract all translatable strings using the parsed tree from GDScriptParser.
|
||||
// The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e
|
||||
// Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc.
|
||||
|
|
@ -49,8 +49,8 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
|
|||
Ref<Resource> loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
|
||||
ERR_FAIL_COND_V_MSG(err, err, "Failed to load " + p_path);
|
||||
|
||||
ids = r_ids;
|
||||
ids_ctx_plural = r_ids_ctx_plural;
|
||||
translations = r_translations;
|
||||
|
||||
Ref<GDScript> gdscript = loaded_res;
|
||||
String source_code = gdscript->get_source_code();
|
||||
|
||||
|
|
@ -62,16 +62,81 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
|
|||
err = analyzer.analyze();
|
||||
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");
|
||||
|
||||
comment_data = &parser.comment_data;
|
||||
|
||||
// Traverse through the parsed tree from GDScriptParser.
|
||||
GDScriptParser::ClassNode *c = parser.get_tree();
|
||||
_traverse_class(c);
|
||||
|
||||
comment_data = nullptr;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
|
||||
ERR_FAIL_NULL_V(p_expression, false);
|
||||
return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME);
|
||||
return p_expression->is_constant && p_expression->reduced_value.is_string();
|
||||
}
|
||||
|
||||
String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const {
|
||||
// Parse inline comment.
|
||||
if (comment_data->has(p_line)) {
|
||||
const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges();
|
||||
|
||||
if (stripped_comment.begins_with("TRANSLATORS:")) {
|
||||
return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
|
||||
}
|
||||
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
|
||||
r_skip = true;
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse multiline comment.
|
||||
String multiline_comment;
|
||||
for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) {
|
||||
const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges();
|
||||
|
||||
if (stripped_comment.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multiline_comment.is_empty()) {
|
||||
multiline_comment = stripped_comment;
|
||||
} else {
|
||||
multiline_comment = stripped_comment + "\n" + multiline_comment;
|
||||
}
|
||||
|
||||
if (stripped_comment.begins_with("TRANSLATORS:")) {
|
||||
return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
|
||||
}
|
||||
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
|
||||
r_skip = true;
|
||||
return String();
|
||||
}
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) {
|
||||
bool skip = false;
|
||||
const String comment = _parse_comment(p_line, skip);
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
translations->push_back({ p_id, String(), String(), comment });
|
||||
}
|
||||
|
||||
void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) {
|
||||
bool skip = false;
|
||||
const String comment = _parse_comment(p_line, skip);
|
||||
if (skip) {
|
||||
return;
|
||||
}
|
||||
|
||||
translations->push_back({ p_id_ctx_plural[0], p_id_ctx_plural[1], p_id_ctx_plural[2], comment });
|
||||
}
|
||||
|
||||
void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
|
||||
|
|
@ -253,7 +318,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar
|
|||
|
||||
if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
|
||||
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
|
||||
ids->push_back(p_assignment->assigned_value->reduced_value);
|
||||
_add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line);
|
||||
} else if (assignee_name == fd_filters) {
|
||||
// Extract from `get_node("FileDialog").filters = <filter array>`.
|
||||
_extract_fd_filter_array(p_assignment->assigned_value);
|
||||
|
|
@ -287,7 +352,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
|
|||
}
|
||||
}
|
||||
if (extract_id_ctx_plural) {
|
||||
ids_ctx_plural->push_back(id_ctx_plural);
|
||||
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
|
||||
}
|
||||
} else if (function_name == trn_func || function_name == atrn_func) {
|
||||
// Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`.
|
||||
|
|
@ -307,20 +372,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
|
|||
}
|
||||
}
|
||||
if (extract_id_ctx_plural) {
|
||||
ids_ctx_plural->push_back(id_ctx_plural);
|
||||
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
|
||||
}
|
||||
} else if (first_arg_patterns.has(function_name)) {
|
||||
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
|
||||
ids->push_back(p_call->arguments[0]->reduced_value);
|
||||
_add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line);
|
||||
}
|
||||
} else if (second_arg_patterns.has(function_name)) {
|
||||
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
|
||||
ids->push_back(p_call->arguments[1]->reduced_value);
|
||||
_add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line);
|
||||
}
|
||||
} else if (function_name == fd_add_filter) {
|
||||
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
|
||||
if (!p_call->arguments.is_empty()) {
|
||||
_extract_fd_filter_string(p_call->arguments[0]);
|
||||
_extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line);
|
||||
}
|
||||
} else if (function_name == fd_set_filter) {
|
||||
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
|
||||
|
|
@ -330,12 +395,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
|
|||
}
|
||||
}
|
||||
|
||||
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
|
||||
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) {
|
||||
// Extract the name in "extension ; name".
|
||||
if (_is_constant_string(p_expression)) {
|
||||
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
|
||||
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
|
||||
ids->push_back(arr[1].strip_edges());
|
||||
_add_id(arr[1].strip_edges(), p_line);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +420,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr
|
|||
|
||||
if (array_node) {
|
||||
for (int i = 0; i < array_node->elements.size(); i++) {
|
||||
_extract_fd_filter_string(array_node->elements[i]);
|
||||
_extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,18 @@
|
|||
#define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H
|
||||
|
||||
#include "../gdscript_parser.h"
|
||||
#include "../gdscript_tokenizer.h"
|
||||
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "editor/editor_translation_parser.h"
|
||||
|
||||
class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin {
|
||||
GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin);
|
||||
|
||||
Vector<String> *ids = nullptr;
|
||||
Vector<Vector<String>> *ids_ctx_plural = nullptr;
|
||||
const HashMap<int, GDScriptTokenizer::CommentData> *comment_data = nullptr;
|
||||
|
||||
Vector<Vector<String>> *translations = nullptr;
|
||||
|
||||
// List of patterns used for extracting translation strings.
|
||||
StringName tr_func = "tr";
|
||||
|
|
@ -57,6 +60,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
|
|||
|
||||
static bool _is_constant_string(const GDScriptParser::ExpressionNode *p_expression);
|
||||
|
||||
String _parse_comment(int p_line, bool &r_skip) const;
|
||||
|
||||
void _add_id(const String &p_id, int p_line);
|
||||
void _add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line);
|
||||
|
||||
void _traverse_class(const GDScriptParser::ClassNode *p_class);
|
||||
void _traverse_function(const GDScriptParser::FunctionNode *p_func);
|
||||
void _traverse_block(const GDScriptParser::SuiteNode *p_suite);
|
||||
|
|
@ -65,11 +73,11 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug
|
|||
void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment);
|
||||
void _assess_call(const GDScriptParser::CallNode *p_call);
|
||||
|
||||
void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression);
|
||||
void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line);
|
||||
void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression);
|
||||
|
||||
public:
|
||||
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override;
|
||||
virtual Error parse_file(const String &p_path, Vector<Vector<String>> *r_translations) override;
|
||||
virtual void get_recognized_extensions(List<String> *r_extensions) const override;
|
||||
|
||||
GDScriptEditorTranslationParserPlugin();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
import editor.template_builders as build_template_gd
|
||||
|
||||
env["BUILDERS"]["MakeGDTemplateBuilder"] = Builder(
|
||||
action=env.Run(build_template_gd.make_templates),
|
||||
suffix=".h",
|
||||
src_suffix=".gd",
|
||||
)
|
||||
|
||||
# Template files
|
||||
templates_sources = Glob("*/*.gd")
|
||||
|
||||
env.Alias("editor_template_gd", [env.MakeGDTemplateBuilder("templates.gen.h", templates_sources)])
|
||||
env.CommandNoCache("templates.gen.h", Glob("*/*.gd"), env.Run(build_template_gd.make_templates))
|
||||
|
|
|
|||
|
|
@ -50,12 +50,12 @@
|
|||
#include "core/config/project_settings.h"
|
||||
#include "core/core_constants.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/file_access_encrypted.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "core/extension/gdextension_manager.h"
|
||||
#include "editor/editor_paths.h"
|
||||
#endif
|
||||
|
||||
|
|
@ -74,9 +74,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
|
|||
if (ok) {
|
||||
r_ret = v;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
MethodBind *method = ClassDB::get_method(name, p_name);
|
||||
if (method && method->is_static()) {
|
||||
// Native static method.
|
||||
r_ret = Callable(this, p_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GDScriptNativeClass::_bind_methods() {
|
||||
|
|
@ -136,7 +143,7 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance
|
|||
}
|
||||
}
|
||||
ERR_FAIL_NULL(p_script->implicit_initializer);
|
||||
if (likely(valid)) {
|
||||
if (likely(p_script->valid)) {
|
||||
p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
|
||||
} else {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
|
||||
|
|
@ -247,7 +254,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr
|
|||
|
||||
bool GDScript::can_instantiate() const {
|
||||
#ifdef TOOLS_ENABLED
|
||||
return valid && (tool || ScriptServer::is_scripting_enabled());
|
||||
return valid && (tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_recovery_mode_hint();
|
||||
#else
|
||||
return valid;
|
||||
#endif
|
||||
|
|
@ -470,23 +477,25 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List
|
|||
}
|
||||
}
|
||||
|
||||
void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) {
|
||||
if (_owner) { // Only the top-level class stores doc info
|
||||
_owner->_add_doc(p_inner_class);
|
||||
} else { // Remove old docs, add new
|
||||
void GDScript::_add_doc(const DocData::ClassDoc &p_doc) {
|
||||
doc_class_name = p_doc.name;
|
||||
if (_owner) { // Only the top-level class stores doc info.
|
||||
_owner->_add_doc(p_doc);
|
||||
} else { // Remove old docs, add new.
|
||||
for (int i = 0; i < docs.size(); i++) {
|
||||
if (docs[i].name == p_inner_class.name) {
|
||||
if (docs[i].name == p_doc.name) {
|
||||
docs.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
docs.append(p_inner_class);
|
||||
docs.append(p_doc);
|
||||
}
|
||||
}
|
||||
|
||||
void GDScript::_clear_doc() {
|
||||
docs.clear();
|
||||
doc_class_name = StringName();
|
||||
doc = DocData::ClassDoc();
|
||||
docs.clear();
|
||||
}
|
||||
|
||||
String GDScript::get_class_icon_path() const {
|
||||
|
|
@ -691,10 +700,16 @@ void GDScript::_static_default_init() {
|
|||
continue;
|
||||
}
|
||||
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
|
||||
const GDScriptDataType element_type = type.get_container_element_type(0);
|
||||
Array default_value;
|
||||
const GDScriptDataType &element_type = type.get_container_element_type(0);
|
||||
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
|
||||
static_variables.write[E.value.index] = default_value;
|
||||
} else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
|
||||
const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
|
||||
Dictionary default_value;
|
||||
default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
|
||||
static_variables.write[E.value.index] = default_value;
|
||||
} else {
|
||||
Variant default_value;
|
||||
Callable::CallError err;
|
||||
|
|
@ -878,6 +893,11 @@ Error GDScript::reload(bool p_keep_state) {
|
|||
if (can_run && p_keep_state) {
|
||||
_restore_old_static_data();
|
||||
}
|
||||
|
||||
if (p_keep_state) {
|
||||
// Update the properties in the inspector.
|
||||
update_exports();
|
||||
}
|
||||
#endif
|
||||
|
||||
reloading = false;
|
||||
|
|
@ -904,7 +924,7 @@ void GDScript::get_members(HashSet<StringName> *p_members) {
|
|||
}
|
||||
}
|
||||
|
||||
const Variant GDScript::get_rpc_config() const {
|
||||
Variant GDScript::get_rpc_config() const {
|
||||
return rpc_config;
|
||||
}
|
||||
|
||||
|
|
@ -952,7 +972,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
|
|||
if (E) {
|
||||
if (likely(top->valid) && E->value.getter) {
|
||||
Callable::CallError ce;
|
||||
r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
|
||||
const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
|
||||
r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
|
||||
return true;
|
||||
}
|
||||
r_ret = top->static_variables[E->value.index];
|
||||
|
|
@ -1102,7 +1123,7 @@ Error GDScript::load_source_code(const String &p_path) {
|
|||
w[len] = 0;
|
||||
|
||||
String s;
|
||||
if (s.parse_utf8((const char *)w) != OK) {
|
||||
if (s.parse_utf8((const char *)w, len) != OK) {
|
||||
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
|
||||
}
|
||||
|
||||
|
|
@ -1725,10 +1746,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
|
|||
if (E) {
|
||||
if (likely(script->valid) && E->value.getter) {
|
||||
Callable::CallError err;
|
||||
r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
|
||||
if (err.error == Callable::CallError::CALL_OK) {
|
||||
return true;
|
||||
}
|
||||
const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
|
||||
r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant();
|
||||
return true;
|
||||
}
|
||||
r_ret = members[E->value.index];
|
||||
return true;
|
||||
|
|
@ -1750,7 +1770,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
|
|||
if (E) {
|
||||
if (likely(sptr->valid) && E->value.getter) {
|
||||
Callable::CallError ce;
|
||||
r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
|
||||
const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
|
||||
r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
|
||||
return true;
|
||||
}
|
||||
r_ret = sptr->static_variables[E->value.index];
|
||||
|
|
@ -1793,7 +1814,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
|
|||
const Variant *args[1] = { &name };
|
||||
|
||||
Callable::CallError err;
|
||||
Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err);
|
||||
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), (const Variant **)args, 1, err);
|
||||
if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
|
||||
r_ret = ret;
|
||||
return true;
|
||||
|
|
@ -1821,14 +1842,14 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
|
|||
}
|
||||
|
||||
void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
|
||||
Variant property = (Dictionary)p_property;
|
||||
const Variant *args[1] = { &property };
|
||||
|
||||
const GDScript *sptr = script.ptr();
|
||||
while (sptr) {
|
||||
if (likely(sptr->valid)) {
|
||||
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
|
||||
if (E) {
|
||||
Variant property = (Dictionary)p_property;
|
||||
const Variant *args[1] = { &property };
|
||||
|
||||
Callable::CallError err;
|
||||
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
|
||||
if (err.error == Callable::CallError::CALL_OK) {
|
||||
|
|
@ -1852,7 +1873,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
|
|||
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
|
||||
if (E) {
|
||||
Callable::CallError err;
|
||||
Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
|
||||
Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
|
||||
if (err.error == Callable::CallError::CALL_OK) {
|
||||
ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
|
||||
|
||||
|
|
@ -2176,9 +2197,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
|
|||
global_array.write[globals[p_name]] = p_value;
|
||||
return;
|
||||
}
|
||||
globals[p_name] = global_array.size();
|
||||
global_array.push_back(p_value);
|
||||
_global_array = global_array.ptrw();
|
||||
|
||||
if (global_array_empty_indexes.size()) {
|
||||
int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1];
|
||||
globals[p_name] = index;
|
||||
global_array.write[index] = p_value;
|
||||
global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1);
|
||||
} else {
|
||||
globals[p_name] = global_array.size();
|
||||
global_array.push_back(p_value);
|
||||
_global_array = global_array.ptrw();
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptLanguage::_remove_global(const StringName &p_name) {
|
||||
if (!globals.has(p_name)) {
|
||||
return;
|
||||
}
|
||||
global_array_empty_indexes.push_back(globals[p_name]);
|
||||
global_array.write[globals[p_name]] = Variant::NIL;
|
||||
globals.erase(p_name);
|
||||
}
|
||||
|
||||
void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
|
||||
|
|
@ -2236,11 +2274,40 @@ void GDScriptLanguage::init() {
|
|||
_add_global(E.name, E.ptr);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded));
|
||||
GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TESTS_ENABLED
|
||||
GDScriptTests::GDScriptTestRunner::handle_cmdline();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) {
|
||||
List<StringName> class_list;
|
||||
ClassDB::get_extension_class_list(p_extension, &class_list);
|
||||
for (const StringName &n : class_list) {
|
||||
if (globals.has(n)) {
|
||||
continue;
|
||||
}
|
||||
Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
|
||||
_add_global(n, nc);
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) {
|
||||
List<StringName> class_list;
|
||||
ClassDB::get_extension_class_list(p_extension, &class_list);
|
||||
for (const StringName &n : class_list) {
|
||||
_remove_global(n);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
String GDScriptLanguage::get_type() const {
|
||||
return "GDScript";
|
||||
}
|
||||
|
|
@ -2486,11 +2553,11 @@ void GDScriptLanguage::reload_all_scripts() {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
reload_scripts(scripts, true);
|
||||
#endif
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
|
||||
|
|
@ -2503,7 +2570,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
|
|||
SelfList<GDScript> *elem = script_list.first();
|
||||
while (elem) {
|
||||
// Scripts will reload all subclasses, so only reload root scripts.
|
||||
if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
|
||||
if (elem->self()->is_root_script() && !elem->self()->get_path().is_empty()) {
|
||||
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
|
||||
}
|
||||
elem = elem->next();
|
||||
|
|
@ -2560,7 +2627,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
|
||||
map[F.key] = F.value; //pending to reload, use this one instead
|
||||
|
|
@ -2571,7 +2638,19 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
|
|||
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
|
||||
Ref<GDScript> scr = E.key;
|
||||
print_verbose("GDScript: Reloading: " + scr->get_path());
|
||||
scr->load_source_code(scr->get_path());
|
||||
if (scr->is_built_in()) {
|
||||
// TODO: It would be nice to do it more efficiently than loading the whole scene again.
|
||||
Ref<PackedScene> scene = ResourceLoader::load(scr->get_path().get_slice("::", 0), "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP);
|
||||
ERR_CONTINUE(scene.is_null());
|
||||
|
||||
Ref<SceneState> state = scene->get_state();
|
||||
Ref<GDScript> fresh = state->get_sub_resource(scr->get_path());
|
||||
ERR_CONTINUE(fresh.is_null());
|
||||
|
||||
scr->set_source_code(fresh->get_source_code());
|
||||
} else {
|
||||
scr->load_source_code(scr->get_path());
|
||||
}
|
||||
scr->reload(p_soft_reload);
|
||||
|
||||
//restore state if saved
|
||||
|
|
@ -2616,7 +2695,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
|
|||
//if instance states were saved, set them!
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
|
||||
|
|
@ -2626,8 +2705,6 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so
|
|||
}
|
||||
|
||||
void GDScriptLanguage::frame() {
|
||||
calls = 0;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (profiling) {
|
||||
MutexLock lock(mutex);
|
||||
|
|
@ -2734,7 +2811,7 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
|
|||
return p_type == "GDScript";
|
||||
}
|
||||
|
||||
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
|
||||
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path, bool *r_is_abstract, bool *r_is_tool) const {
|
||||
Error err;
|
||||
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
|
||||
if (err) {
|
||||
|
|
@ -2835,13 +2912,18 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
|
|||
if (r_icon_path) {
|
||||
*r_icon_path = c->simplified_icon_path;
|
||||
}
|
||||
if (r_is_abstract) {
|
||||
*r_is_abstract = false;
|
||||
}
|
||||
if (r_is_tool) {
|
||||
*r_is_tool = parser.is_tool();
|
||||
}
|
||||
return c->identifier != nullptr ? String(c->identifier->name) : String();
|
||||
}
|
||||
|
||||
thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
|
||||
|
||||
GDScriptLanguage::GDScriptLanguage() {
|
||||
calls = 0;
|
||||
ERR_FAIL_COND(singleton);
|
||||
singleton = this;
|
||||
strings._init = StaticCString::create("_init");
|
||||
|
|
@ -2857,8 +2939,11 @@ GDScriptLanguage::GDScriptLanguage() {
|
|||
_debug_parse_err_line = -1;
|
||||
_debug_parse_err_file = "";
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
profiling = false;
|
||||
profile_native_calls = false;
|
||||
script_frame_time = 0;
|
||||
#endif
|
||||
|
||||
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
|
||||
|
||||
|
|
@ -2873,6 +2958,7 @@ GDScriptLanguage::GDScriptLanguage() {
|
|||
#ifdef DEBUG_ENABLED
|
||||
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
|
||||
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
|
||||
GLOBAL_DEF("debug/gdscript/warnings/renamed_in_godot_4_hint", true);
|
||||
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
|
||||
GDScriptWarning::Code code = (GDScriptWarning::Code)i;
|
||||
Variant default_enabled = GDScriptWarning::get_default_value(code);
|
||||
|
|
|
|||
|
|
@ -110,11 +110,13 @@ class GDScript : public Script {
|
|||
HashMap<StringName, MethodInfo> _signals;
|
||||
Dictionary rpc_config;
|
||||
|
||||
public:
|
||||
struct LambdaInfo {
|
||||
int capture_count;
|
||||
bool use_self;
|
||||
};
|
||||
|
||||
private:
|
||||
HashMap<GDScriptFunction *, LambdaInfo> lambda_info;
|
||||
|
||||
public:
|
||||
|
|
@ -157,16 +159,18 @@ private:
|
|||
bool placeholder_fallback_enabled = false;
|
||||
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
|
||||
|
||||
StringName doc_class_name;
|
||||
DocData::ClassDoc doc;
|
||||
Vector<DocData::ClassDoc> docs;
|
||||
void _add_doc(const DocData::ClassDoc &p_doc);
|
||||
void _clear_doc();
|
||||
void _add_doc(const DocData::ClassDoc &p_inner_class);
|
||||
#endif
|
||||
|
||||
GDScriptFunction *implicit_initializer = nullptr;
|
||||
GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate
|
||||
GDScriptFunction *implicit_ready = nullptr;
|
||||
GDScriptFunction *static_initializer = nullptr;
|
||||
GDScriptFunction *initializer = nullptr; // Direct pointer to `new()`/`_init()` member function, faster to locate.
|
||||
|
||||
GDScriptFunction *implicit_initializer = nullptr; // `@implicit_new()` special function.
|
||||
GDScriptFunction *implicit_ready = nullptr; // `@implicit_ready()` special function.
|
||||
GDScriptFunction *static_initializer = nullptr; // `@static_initializer()` special function.
|
||||
|
||||
Error _static_init();
|
||||
void _static_default_init(); // Initialize static variables with default values based on their types.
|
||||
|
|
@ -257,9 +261,15 @@ public:
|
|||
CRASH_COND(!member_indices.has(p_member));
|
||||
return member_indices[p_member].data_type;
|
||||
}
|
||||
const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
|
||||
const Ref<GDScriptNativeClass> &get_native() const { return native; }
|
||||
|
||||
_FORCE_INLINE_ const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; }
|
||||
_FORCE_INLINE_ const HashMap<GDScriptFunction *, LambdaInfo> &get_lambda_info() const { return lambda_info; }
|
||||
|
||||
_FORCE_INLINE_ const GDScriptFunction *get_implicit_initializer() const { return implicit_initializer; }
|
||||
_FORCE_INLINE_ const GDScriptFunction *get_implicit_ready() const { return implicit_ready; }
|
||||
_FORCE_INLINE_ const GDScriptFunction *get_static_initializer() const { return static_initializer; }
|
||||
|
||||
RBSet<GDScript *> get_dependencies();
|
||||
HashMap<GDScript *, RBSet<GDScript *>> get_all_dependencies();
|
||||
RBSet<GDScript *> get_must_clear_dependencies();
|
||||
|
|
@ -292,9 +302,8 @@ public:
|
|||
virtual void update_exports() override;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual Vector<DocData::ClassDoc> get_documentation() const override {
|
||||
return docs;
|
||||
}
|
||||
virtual StringName get_doc_class_name() const override { return doc_class_name; }
|
||||
virtual Vector<DocData::ClassDoc> get_documentation() const override { return docs; }
|
||||
virtual String get_class_icon_path() const override;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
|
|
@ -334,7 +343,7 @@ public:
|
|||
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
|
||||
virtual void get_members(HashSet<StringName> *p_members) override;
|
||||
|
||||
virtual const Variant get_rpc_config() const override;
|
||||
virtual Variant get_rpc_config() const override;
|
||||
|
||||
void unload_static() const;
|
||||
|
||||
|
|
@ -417,6 +426,7 @@ class GDScriptLanguage : public ScriptLanguage {
|
|||
Vector<Variant> global_array;
|
||||
HashMap<StringName, int> globals;
|
||||
HashMap<StringName, Variant> named_globals;
|
||||
Vector<int> global_array_empty_indexes;
|
||||
|
||||
struct CallLevel {
|
||||
Variant *stack = nullptr;
|
||||
|
|
@ -448,6 +458,7 @@ class GDScriptLanguage : public ScriptLanguage {
|
|||
int _debug_max_call_stack = 0;
|
||||
|
||||
void _add_global(const StringName &p_name, const Variant &p_value);
|
||||
void _remove_global(const StringName &p_name);
|
||||
|
||||
friend class GDScriptInstance;
|
||||
|
||||
|
|
@ -459,15 +470,20 @@ class GDScriptLanguage : public ScriptLanguage {
|
|||
friend class GDScriptFunction;
|
||||
|
||||
SelfList<GDScriptFunction>::List function_list;
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool profiling;
|
||||
bool profile_native_calls;
|
||||
uint64_t script_frame_time;
|
||||
#endif
|
||||
|
||||
HashMap<String, ObjectID> orphan_subclasses;
|
||||
|
||||
public:
|
||||
int calls;
|
||||
#ifdef TOOLS_ENABLED
|
||||
void _extension_loaded(const Ref<GDExtension> &p_extension);
|
||||
void _extension_unloading(const Ref<GDExtension> &p_extension);
|
||||
#endif
|
||||
|
||||
public:
|
||||
bool debug_break(const String &p_error, bool p_allow_continue = true);
|
||||
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
|
||||
|
||||
|
|
@ -621,7 +637,7 @@ public:
|
|||
/* GLOBAL CLASSES */
|
||||
|
||||
virtual bool handles_global_class_type(const String &p_type) const override;
|
||||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr) const override;
|
||||
virtual String get_global_class_name(const String &p_path, String *r_base_type = nullptr, String *r_icon_path = nullptr, bool *r_is_abstract = nullptr, bool *r_is_tool = nullptr) const override;
|
||||
|
||||
void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass);
|
||||
Ref<GDScript> get_orphan_subclass(const String &p_qualified_name);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
#include "core/object/class_db.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
#if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED)
|
||||
#define SUGGEST_GODOT4_RENAMES
|
||||
|
|
@ -148,6 +148,15 @@ static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, co
|
|||
return type;
|
||||
}
|
||||
|
||||
static GDScriptParser::DataType make_class_enum_type(const StringName &p_enum_name, GDScriptParser::ClassNode *p_class, const String &p_script_path, bool p_meta = true) {
|
||||
GDScriptParser::DataType type = make_enum_type(p_enum_name, p_class->fqcn, p_meta);
|
||||
|
||||
type.class_type = p_class;
|
||||
type.script_path = p_script_path;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, bool p_meta = true) {
|
||||
// Find out which base class declared the enum, so the name is always the same even when coming from other contexts.
|
||||
StringName native_base = p_native_class;
|
||||
|
|
@ -160,7 +169,9 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
|
|||
|
||||
GDScriptParser::DataType type = make_enum_type(p_enum_name, native_base, p_meta);
|
||||
if (p_meta) {
|
||||
type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
|
||||
// Native enum types are not dictionaries.
|
||||
type.builtin_type = Variant::NIL;
|
||||
type.is_pseudo_type = true;
|
||||
}
|
||||
|
||||
List<StringName> enum_values;
|
||||
|
|
@ -173,10 +184,29 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_n
|
|||
return type;
|
||||
}
|
||||
|
||||
static GDScriptParser::DataType make_builtin_enum_type(const StringName &p_enum_name, Variant::Type p_type, bool p_meta = true) {
|
||||
GDScriptParser::DataType type = make_enum_type(p_enum_name, Variant::get_type_name(p_type), p_meta);
|
||||
if (p_meta) {
|
||||
// Built-in enum types are not dictionaries.
|
||||
type.builtin_type = Variant::NIL;
|
||||
type.is_pseudo_type = true;
|
||||
}
|
||||
|
||||
List<StringName> enum_values;
|
||||
Variant::get_enumerations_for_enum(p_type, p_enum_name, &enum_values);
|
||||
|
||||
for (const StringName &E : enum_values) {
|
||||
type.enum_values[E] = Variant::get_enum_value(p_type, p_enum_name, E);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static GDScriptParser::DataType make_global_enum_type(const StringName &p_enum_name, const StringName &p_base, bool p_meta = true) {
|
||||
GDScriptParser::DataType type = make_enum_type(p_enum_name, p_base, p_meta);
|
||||
if (p_meta) {
|
||||
type.builtin_type = Variant::NIL; // Native enum types are not Dictionaries.
|
||||
// Global enum types are not dictionaries.
|
||||
type.builtin_type = Variant::NIL;
|
||||
type.is_pseudo_type = true;
|
||||
}
|
||||
|
||||
|
|
@ -418,6 +448,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
|
|||
return err;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) {
|
||||
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
|
||||
}
|
||||
#endif
|
||||
|
||||
base = ext_parser->get_parser()->head->get_datatype();
|
||||
} else {
|
||||
if (p_class->extends.is_empty()) {
|
||||
|
|
@ -445,6 +481,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
|
|||
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!parser->_is_tool && base_parser->get_parser()->_is_tool) {
|
||||
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
|
||||
}
|
||||
#endif
|
||||
|
||||
base = base_parser->get_parser()->head->get_datatype();
|
||||
}
|
||||
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
|
||||
|
|
@ -465,6 +508,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
|
|||
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!parser->_is_tool && info_parser->get_parser()->_is_tool) {
|
||||
parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
|
||||
}
|
||||
#endif
|
||||
|
||||
base = info_parser->get_parser()->head->get_datatype();
|
||||
} else if (class_exists(name)) {
|
||||
if (Engine::get_singleton()->has_singleton(name)) {
|
||||
|
|
@ -674,8 +724,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
if (first == SNAME("Variant")) {
|
||||
if (p_type->type_chain.size() == 2) {
|
||||
// May be nested enum.
|
||||
StringName enum_name = p_type->type_chain[1]->name;
|
||||
StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
|
||||
const StringName enum_name = p_type->type_chain[1]->name;
|
||||
const StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
|
||||
if (CoreConstants::is_global_enum(qualified_name)) {
|
||||
result = make_global_enum_type(enum_name, first, true);
|
||||
return result;
|
||||
|
|
@ -690,20 +740,45 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
result.kind = GDScriptParser::DataType::VARIANT;
|
||||
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
|
||||
// Built-in types.
|
||||
if (p_type->type_chain.size() > 1) {
|
||||
push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
|
||||
const Variant::Type builtin_type = GDScriptParser::get_builtin_type(first);
|
||||
|
||||
if (p_type->type_chain.size() == 2) {
|
||||
// May be nested enum.
|
||||
const StringName enum_name = p_type->type_chain[1]->name;
|
||||
if (Variant::has_enum(builtin_type, enum_name)) {
|
||||
result = make_builtin_enum_type(enum_name, builtin_type, true);
|
||||
return result;
|
||||
} else {
|
||||
push_error(vformat(R"(Name "%s" is not a nested type of "%s".)", enum_name, first), p_type->type_chain[1]);
|
||||
return bad_type;
|
||||
}
|
||||
} else if (p_type->type_chain.size() > 2) {
|
||||
push_error(R"(Built-in types only contain enum types, which do not have nested types.)", p_type->type_chain[2]);
|
||||
return bad_type;
|
||||
}
|
||||
result.kind = GDScriptParser::DataType::BUILTIN;
|
||||
result.builtin_type = GDScriptParser::get_builtin_type(first);
|
||||
|
||||
if (result.builtin_type == Variant::ARRAY) {
|
||||
result.kind = GDScriptParser::DataType::BUILTIN;
|
||||
result.builtin_type = builtin_type;
|
||||
|
||||
if (builtin_type == Variant::ARRAY) {
|
||||
GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
|
||||
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
|
||||
container_type.is_constant = false;
|
||||
result.set_container_element_type(0, container_type);
|
||||
}
|
||||
}
|
||||
if (builtin_type == Variant::DICTIONARY) {
|
||||
GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
|
||||
if (key_type.kind != GDScriptParser::DataType::VARIANT) {
|
||||
key_type.is_constant = false;
|
||||
result.set_container_element_type(0, key_type);
|
||||
}
|
||||
GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
|
||||
if (value_type.kind != GDScriptParser::DataType::VARIANT) {
|
||||
value_type.is_constant = false;
|
||||
result.set_container_element_type(1, value_type);
|
||||
}
|
||||
}
|
||||
} else if (class_exists(first)) {
|
||||
// Native engine classes.
|
||||
result.kind = GDScriptParser::DataType::NATIVE;
|
||||
|
|
@ -717,7 +792,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
String ext = path.get_extension();
|
||||
if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
|
||||
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(path);
|
||||
if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
|
||||
if (ref.is_null() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
|
||||
push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
|
||||
return bad_type;
|
||||
}
|
||||
|
|
@ -864,11 +939,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
|
|||
if (!p_type->container_types.is_empty()) {
|
||||
if (result.builtin_type == Variant::ARRAY) {
|
||||
if (p_type->container_types.size() != 1) {
|
||||
push_error("Arrays require exactly one collection element type.", p_type);
|
||||
push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
|
||||
return bad_type;
|
||||
}
|
||||
} else if (result.builtin_type == Variant::DICTIONARY) {
|
||||
if (p_type->container_types.size() != 2) {
|
||||
push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
|
||||
return bad_type;
|
||||
}
|
||||
} else {
|
||||
push_error("Only arrays can specify collection element types.", p_type);
|
||||
push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
|
||||
return bad_type;
|
||||
}
|
||||
}
|
||||
|
|
@ -894,8 +974,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
Finally finally([&]() {
|
||||
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
|
||||
GDScriptParser::DataType member_type = member.get_datatype();
|
||||
if (member_type.has_container_element_type(0)) {
|
||||
ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
|
||||
for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
|
||||
ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1013,7 +1093,12 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
}
|
||||
}
|
||||
if (is_get_node) {
|
||||
parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, is_using_shorthand ? "$" : "get_node()");
|
||||
String offending_syntax = "get_node()";
|
||||
if (is_using_shorthand) {
|
||||
GDScriptParser::GetNodeNode *get_node_node = static_cast<GDScriptParser::GetNodeNode *>(expr);
|
||||
offending_syntax = get_node_node->use_dollar ? "$" : "%";
|
||||
}
|
||||
parser->push_warning(member.variable, GDScriptWarning::GET_NODE_DEFAULT_WITHOUT_ONREADY, offending_syntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1064,7 +1149,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum);
|
||||
|
||||
member.m_enum->set_datatype(resolving_datatype);
|
||||
GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true);
|
||||
GDScriptParser::DataType enum_type = make_class_enum_type(member.m_enum->identifier->name, p_class, parser->script_path, true);
|
||||
|
||||
const GDScriptParser::EnumNode *prev_enum = current_enum;
|
||||
current_enum = member.m_enum;
|
||||
|
|
@ -1157,7 +1242,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
// Also update the original references.
|
||||
member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value);
|
||||
|
||||
member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false));
|
||||
member.enum_value.identifier->set_datatype(make_class_enum_type(UNNAMED_ENUM, p_class, parser->script_path, false));
|
||||
} break;
|
||||
case GDScriptParser::ClassNode::Member::CLASS:
|
||||
check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class);
|
||||
|
|
@ -1906,6 +1991,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
|
|||
if (has_specified_type && specified_type.has_container_element_type(0)) {
|
||||
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
|
||||
}
|
||||
} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
|
||||
GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
|
||||
if (has_specified_type && specified_type.has_container_element_types()) {
|
||||
update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (is_constant && !p_assignable->initializer->is_constant) {
|
||||
|
|
@ -1967,7 +2057,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
|
|||
} else {
|
||||
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
|
||||
}
|
||||
} else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
|
||||
} else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
|
||||
mark_node_unsafe(p_assignable->initializer);
|
||||
#ifdef DEBUG_ENABLED
|
||||
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
|
||||
|
|
@ -2209,8 +2299,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
|
|||
} else if (!is_type_compatible(specified_type, variable_type)) {
|
||||
p_for->use_conversion_assign = true;
|
||||
}
|
||||
if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
|
||||
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
|
||||
if (p_for->list) {
|
||||
if (p_for->list->type == GDScriptParser::Node::ARRAY) {
|
||||
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
|
||||
} else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
|
||||
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
|
||||
}
|
||||
}
|
||||
}
|
||||
p_for->variable->set_datatype(specified_type);
|
||||
|
|
@ -2412,6 +2506,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
|
|||
} else {
|
||||
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
|
||||
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
|
||||
} else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
|
||||
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
|
||||
expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
|
||||
}
|
||||
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
|
||||
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
|
||||
|
|
@ -2658,6 +2755,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
|
|||
p_array->set_datatype(array_type);
|
||||
}
|
||||
|
||||
// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
|
||||
// This function determines which type is that (if any).
|
||||
void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
|
||||
GDScriptParser::DataType expected_key_type = p_key_element_type;
|
||||
GDScriptParser::DataType expected_value_type = p_value_element_type;
|
||||
expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
|
||||
expected_value_type.container_element_types.clear();
|
||||
|
||||
for (int i = 0; i < p_dictionary->elements.size(); i++) {
|
||||
GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
|
||||
if (key_element_node->is_constant) {
|
||||
update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
|
||||
}
|
||||
const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
|
||||
if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
|
||||
mark_node_unsafe(key_element_node);
|
||||
} else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
|
||||
if (is_type_compatible(actual_key_type, expected_key_type)) {
|
||||
mark_node_unsafe(key_element_node);
|
||||
} else {
|
||||
push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
|
||||
if (value_element_node->is_constant) {
|
||||
update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
|
||||
}
|
||||
const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
|
||||
if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
|
||||
mark_node_unsafe(value_element_node);
|
||||
} else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
|
||||
if (is_type_compatible(actual_value_type, expected_value_type)) {
|
||||
mark_node_unsafe(value_element_node);
|
||||
} else {
|
||||
push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
|
||||
dictionary_type.set_container_element_type(0, expected_key_type);
|
||||
dictionary_type.set_container_element_type(1, expected_value_type);
|
||||
p_dictionary->set_datatype(dictionary_type);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
|
||||
reduce_expression(p_assignment->assigned_value);
|
||||
|
||||
|
|
@ -2750,9 +2895,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
}
|
||||
}
|
||||
|
||||
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
|
||||
// Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
|
||||
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
|
||||
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
|
||||
} else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
|
||||
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
|
||||
assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
|
||||
}
|
||||
|
||||
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
|
||||
|
|
@ -2830,8 +2978,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
// weak non-variant assignee and incompatible result
|
||||
downgrades_assignee = true;
|
||||
}
|
||||
} else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
|
||||
// typed array assignee and untyped array result
|
||||
} else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
|
||||
// Typed assignee and untyped result.
|
||||
mark_node_unsafe(p_assignment);
|
||||
}
|
||||
}
|
||||
|
|
@ -3029,10 +3177,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
|
|||
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
|
||||
bool all_is_constant = true;
|
||||
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
|
||||
HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
|
||||
for (int i = 0; i < p_call->arguments.size(); i++) {
|
||||
reduce_expression(p_call->arguments[i]);
|
||||
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
|
||||
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
|
||||
} else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
|
||||
dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
|
||||
}
|
||||
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
|
||||
}
|
||||
|
|
@ -3225,6 +3376,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
|||
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
// Consider `Signal(self, "my_signal")` as an implicit use of the signal.
|
||||
if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) {
|
||||
const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0];
|
||||
if (object_arg && object_arg->type == GDScriptParser::Node::SELF) {
|
||||
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1];
|
||||
if (signal_arg && signal_arg->is_constant) {
|
||||
const StringName &signal_name = signal_arg->reduced_value;
|
||||
if (parser->current_class->has_member(signal_name)) {
|
||||
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
|
||||
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
|
||||
member.signal->usages++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
p_call->set_datatype(call_type);
|
||||
return;
|
||||
} else if (GDScriptUtilityFunctions::function_exists(function_name)) {
|
||||
|
|
@ -3417,6 +3588,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
|||
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
|
||||
}
|
||||
}
|
||||
for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
|
||||
int index = E.key;
|
||||
if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
|
||||
GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
|
||||
GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
|
||||
update_dictionary_literal_element_type(E.value, key, value);
|
||||
}
|
||||
}
|
||||
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
|
||||
|
||||
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
|
||||
|
|
@ -3459,6 +3638,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
|||
|
||||
parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type);
|
||||
}
|
||||
|
||||
// Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal.
|
||||
if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) {
|
||||
const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0];
|
||||
if (signal_arg && signal_arg->is_constant) {
|
||||
const StringName &signal_name = signal_arg->reduced_value;
|
||||
if (parser->current_class->has_member(signal_name)) {
|
||||
const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name);
|
||||
if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) {
|
||||
member.signal->usages++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
call_type = return_type;
|
||||
|
|
@ -3502,7 +3695,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
|||
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
|
||||
#ifdef SUGGEST_GODOT4_RENAMES
|
||||
String rename_hint;
|
||||
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
|
||||
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
|
||||
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
|
||||
if (renamed_function_name) {
|
||||
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()");
|
||||
|
|
@ -3547,6 +3740,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
|
|||
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
|
||||
}
|
||||
|
||||
if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
|
||||
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
|
||||
cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
|
||||
}
|
||||
|
||||
if (!cast_type.is_variant()) {
|
||||
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
|
||||
if (op_type.is_variant() || !op_type.is_hard_type()) {
|
||||
|
|
@ -3657,6 +3855,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
|
|||
}
|
||||
|
||||
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
|
||||
// Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
|
||||
// Search dependencies for the parser that owns `p_class` and make a cache entry for it.
|
||||
// Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
|
||||
// Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
|
||||
// Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
|
||||
|
||||
if (p_class == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -3673,8 +3877,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
|
|||
p_from_class = parser->head;
|
||||
}
|
||||
|
||||
String script_path = p_class->get_datatype().script_path;
|
||||
|
||||
Ref<GDScriptParserRef> parser_ref;
|
||||
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
|
||||
if (parser->has_class(look_class)) {
|
||||
|
|
@ -3755,8 +3957,9 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(c
|
|||
|
||||
Ref<GDScript> GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) {
|
||||
// To keep a local cache of the parser for resolving external nodes later.
|
||||
parser->get_depended_parser_for(p_path);
|
||||
Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path);
|
||||
const String path = ResourceUID::ensure_path(p_path);
|
||||
parser->get_depended_parser_for(path);
|
||||
Ref<GDScript> scr = GDScriptCache::get_shallow_script(path, r_error, parser->script_path);
|
||||
return scr;
|
||||
}
|
||||
|
||||
|
|
@ -3807,24 +4010,47 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||
|
||||
if (base.kind == GDScriptParser::DataType::BUILTIN) {
|
||||
if (base.is_meta_type) {
|
||||
bool valid = true;
|
||||
Variant result = Variant::get_constant_value(base.builtin_type, name, &valid);
|
||||
if (valid) {
|
||||
bool valid = false;
|
||||
|
||||
if (Variant::has_constant(base.builtin_type, name)) {
|
||||
valid = true;
|
||||
|
||||
const Variant constant_value = Variant::get_constant_value(base.builtin_type, name);
|
||||
|
||||
p_identifier->is_constant = true;
|
||||
p_identifier->reduced_value = result;
|
||||
p_identifier->set_datatype(type_from_variant(result, p_identifier));
|
||||
} else if (base.is_hard_type()) {
|
||||
p_identifier->reduced_value = constant_value;
|
||||
p_identifier->set_datatype(type_from_variant(constant_value, p_identifier));
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
const StringName enum_name = Variant::get_enum_for_enumeration(base.builtin_type, name);
|
||||
if (enum_name != StringName()) {
|
||||
valid = true;
|
||||
|
||||
p_identifier->is_constant = true;
|
||||
p_identifier->reduced_value = Variant::get_enum_value(base.builtin_type, enum_name, name);
|
||||
p_identifier->set_datatype(make_builtin_enum_type(enum_name, base.builtin_type, false));
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid && Variant::has_enum(base.builtin_type, name)) {
|
||||
valid = true;
|
||||
|
||||
p_identifier->set_datatype(make_builtin_enum_type(name, base.builtin_type, true));
|
||||
}
|
||||
|
||||
if (!valid && base.is_hard_type()) {
|
||||
#ifdef SUGGEST_GODOT4_RENAMES
|
||||
String rename_hint;
|
||||
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
|
||||
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
|
||||
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
|
||||
if (renamed_identifier_name) {
|
||||
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
|
||||
}
|
||||
}
|
||||
push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
|
||||
push_error(vformat(R"(Cannot find member "%s" in base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
|
||||
#else
|
||||
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
|
||||
push_error(vformat(R"(Cannot find member "%s" in base "%s".)", name, base.to_string()), p_identifier);
|
||||
#endif // SUGGEST_GODOT4_RENAMES
|
||||
}
|
||||
} else {
|
||||
|
|
@ -3860,15 +4086,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||
if (base.is_hard_type()) {
|
||||
#ifdef SUGGEST_GODOT4_RENAMES
|
||||
String rename_hint;
|
||||
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
|
||||
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
|
||||
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
|
||||
if (renamed_identifier_name) {
|
||||
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
|
||||
}
|
||||
}
|
||||
push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
|
||||
push_error(vformat(R"(Cannot find member "%s" in base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
|
||||
#else
|
||||
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
|
||||
push_error(vformat(R"(Cannot find member "%s" in base "%s".)", name, base.to_string()), p_identifier);
|
||||
#endif // SUGGEST_GODOT4_RENAMES
|
||||
}
|
||||
}
|
||||
|
|
@ -4099,7 +4325,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
|||
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
|
||||
if (element.identifier->name == p_identifier->name) {
|
||||
StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM;
|
||||
GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false);
|
||||
GDScriptParser::DataType type = make_class_enum_type(enum_name, parser->current_class, parser->script_path, false);
|
||||
if (element.parent_enum->identifier) {
|
||||
type.enum_type = element.parent_enum->identifier->name;
|
||||
}
|
||||
|
|
@ -4288,11 +4514,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
|||
result.builtin_type = Variant::OBJECT;
|
||||
result.native_type = SNAME("Node");
|
||||
if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") {
|
||||
Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(autoload.path);
|
||||
if (singl_parser.is_valid()) {
|
||||
Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
|
||||
Ref<GDScriptParserRef> single_parser = parser->get_depended_parser_for(autoload.path);
|
||||
if (single_parser.is_valid()) {
|
||||
Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
|
||||
if (err == OK) {
|
||||
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
|
||||
result = type_from_metatype(single_parser->get_parser()->head->get_datatype());
|
||||
}
|
||||
}
|
||||
} else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") {
|
||||
|
|
@ -4302,11 +4528,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
|||
if (node != nullptr) {
|
||||
Ref<GDScript> scr = node->get_script();
|
||||
if (scr.is_valid()) {
|
||||
Ref<GDScriptParserRef> singl_parser = parser->get_depended_parser_for(scr->get_script_path());
|
||||
if (singl_parser.is_valid()) {
|
||||
Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
|
||||
Ref<GDScriptParserRef> single_parser = parser->get_depended_parser_for(scr->get_script_path());
|
||||
if (single_parser.is_valid()) {
|
||||
Error err = single_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
|
||||
if (err == OK) {
|
||||
result = type_from_metatype(singl_parser->get_parser()->head->get_datatype());
|
||||
result = type_from_metatype(single_parser->get_parser()->head->get_datatype());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4376,7 +4602,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
|
|||
// Not found.
|
||||
#ifdef SUGGEST_GODOT4_RENAMES
|
||||
String rename_hint;
|
||||
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) {
|
||||
if (GLOBAL_GET("debug/gdscript/warnings/renamed_in_godot_4_hint")) {
|
||||
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
|
||||
if (renamed_identifier_name) {
|
||||
rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name);
|
||||
|
|
@ -4571,10 +4797,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
|
|||
reduce_identifier_from_base(p_subscript->attribute, &base_type);
|
||||
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
|
||||
if (attr_type.is_set()) {
|
||||
valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
|
||||
result_type = attr_type;
|
||||
p_subscript->is_constant = p_subscript->attribute->is_constant;
|
||||
p_subscript->reduced_value = p_subscript->attribute->reduced_value;
|
||||
if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
|
||||
Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
|
||||
valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
|
||||
if (base_type.has_container_element_type(1)) {
|
||||
result_type = base_type.get_container_element_type(1);
|
||||
result_type.type_source = base_type.type_source;
|
||||
} else {
|
||||
result_type.builtin_type = Variant::NIL;
|
||||
result_type.kind = GDScriptParser::DataType::VARIANT;
|
||||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
}
|
||||
} else {
|
||||
valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
|
||||
result_type = attr_type;
|
||||
p_subscript->is_constant = p_subscript->attribute->is_constant;
|
||||
p_subscript->reduced_value = p_subscript->attribute->reduced_value;
|
||||
}
|
||||
} else if (!base_type.is_meta_type || !base_type.is_constant) {
|
||||
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
|
@ -4681,8 +4920,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
|
|||
case Variant::SIGNAL:
|
||||
case Variant::STRING_NAME:
|
||||
break;
|
||||
// Here for completeness.
|
||||
// Support depends on if the dictionary has a typed key, otherwise anything is valid.
|
||||
case Variant::DICTIONARY:
|
||||
if (base_type.has_container_element_type(0)) {
|
||||
GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
|
||||
switch (index_type.builtin_type) {
|
||||
// Null value will be treated as an empty object, allow.
|
||||
case Variant::NIL:
|
||||
error = key_type.builtin_type != Variant::OBJECT;
|
||||
break;
|
||||
// Objects are parsed for validity in a similar manner to container types.
|
||||
case Variant::OBJECT:
|
||||
if (key_type.builtin_type == Variant::OBJECT) {
|
||||
error = !key_type.can_reference(index_type);
|
||||
} else {
|
||||
error = key_type.builtin_type != Variant::NIL;
|
||||
}
|
||||
break;
|
||||
// String and StringName interchangeable in this context.
|
||||
case Variant::STRING:
|
||||
case Variant::STRING_NAME:
|
||||
error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
|
||||
break;
|
||||
// Ints are valid indices for floats, but not the other way around.
|
||||
case Variant::INT:
|
||||
error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
|
||||
break;
|
||||
// All other cases require the types to match exactly.
|
||||
default:
|
||||
error = key_type.builtin_type != index_type.builtin_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Here for completeness.
|
||||
case Variant::VARIANT_MAX:
|
||||
break;
|
||||
}
|
||||
|
|
@ -4771,7 +5042,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
|
|||
case Variant::PROJECTION:
|
||||
case Variant::PLANE:
|
||||
case Variant::COLOR:
|
||||
case Variant::DICTIONARY:
|
||||
case Variant::OBJECT:
|
||||
result_type.kind = GDScriptParser::DataType::VARIANT;
|
||||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
|
|
@ -4786,6 +5056,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
|
|||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
}
|
||||
break;
|
||||
// Can have two element types, but we only care about the value.
|
||||
case Variant::DICTIONARY:
|
||||
if (base_type.has_container_element_type(1)) {
|
||||
result_type = base_type.get_container_element_type(1);
|
||||
result_type.type_source = base_type.type_source;
|
||||
} else {
|
||||
result_type.kind = GDScriptParser::DataType::VARIANT;
|
||||
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
}
|
||||
break;
|
||||
// Here for completeness.
|
||||
case Variant::VARIANT_MAX:
|
||||
break;
|
||||
|
|
@ -4965,7 +5245,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
|
|||
}
|
||||
|
||||
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
|
||||
Dictionary dictionary;
|
||||
Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
|
||||
? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
|
||||
: Dictionary();
|
||||
|
||||
for (int i = 0; i < p_dictionary->elements.size(); i++) {
|
||||
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
|
||||
|
|
@ -5052,6 +5334,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
|
|||
return array;
|
||||
}
|
||||
|
||||
Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
|
||||
Dictionary dictionary;
|
||||
StringName key_name;
|
||||
Variant key_script;
|
||||
StringName value_name;
|
||||
Variant value_script;
|
||||
|
||||
if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
|
||||
Ref<Script> script_type = p_key_element_datatype.script_type;
|
||||
if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
|
||||
Error err = OK;
|
||||
Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
|
||||
if (err) {
|
||||
push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
|
||||
return dictionary;
|
||||
}
|
||||
script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
|
||||
}
|
||||
|
||||
key_name = p_key_element_datatype.native_type;
|
||||
key_script = script_type;
|
||||
}
|
||||
|
||||
if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
|
||||
Ref<Script> script_type = p_value_element_datatype.script_type;
|
||||
if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
|
||||
Error err = OK;
|
||||
Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
|
||||
if (err) {
|
||||
push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
|
||||
return dictionary;
|
||||
}
|
||||
script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
|
||||
}
|
||||
|
||||
value_name = p_value_element_datatype.native_type;
|
||||
value_script = script_type;
|
||||
}
|
||||
|
||||
dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
|
||||
Variant result = Variant();
|
||||
|
||||
|
|
@ -5067,6 +5392,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
|
|||
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
|
||||
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
|
||||
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
|
||||
} else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
|
||||
GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
|
||||
GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
|
||||
result = make_dictionary_from_element_datatype(key, value);
|
||||
} else {
|
||||
VariantInternal::initialize(&result, datatype.builtin_type);
|
||||
}
|
||||
|
|
@ -5095,6 +5424,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
|
|||
} else if (array.get_typed_builtin() != Variant::NIL) {
|
||||
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
|
||||
}
|
||||
} else if (p_value.get_type() == Variant::DICTIONARY) {
|
||||
const Dictionary &dict = p_value;
|
||||
if (dict.get_typed_key_script()) {
|
||||
result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
|
||||
} else if (dict.get_typed_key_class_name()) {
|
||||
result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
|
||||
} else if (dict.get_typed_key_builtin() != Variant::NIL) {
|
||||
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
|
||||
}
|
||||
if (dict.get_typed_value_script()) {
|
||||
result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
|
||||
} else if (dict.get_typed_value_class_name()) {
|
||||
result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
|
||||
} else if (dict.get_typed_value_builtin() != Variant::NIL) {
|
||||
result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
|
||||
}
|
||||
} else if (p_value.get_type() == Variant::OBJECT) {
|
||||
// Object is treated as a native type, not a builtin type.
|
||||
result.kind = GDScriptParser::DataType::NATIVE;
|
||||
|
|
@ -5227,6 +5572,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
|
|||
}
|
||||
elem_type.is_constant = false;
|
||||
result.set_container_element_type(0, elem_type);
|
||||
} else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
|
||||
// Check element type.
|
||||
StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
|
||||
GDScriptParser::DataType key_elem_type;
|
||||
key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||
|
||||
Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
|
||||
if (key_elem_builtin_type < Variant::VARIANT_MAX) {
|
||||
// Builtin type.
|
||||
key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
|
||||
key_elem_type.builtin_type = key_elem_builtin_type;
|
||||
} else if (class_exists(key_elem_type_name)) {
|
||||
key_elem_type.kind = GDScriptParser::DataType::NATIVE;
|
||||
key_elem_type.builtin_type = Variant::OBJECT;
|
||||
key_elem_type.native_type = key_elem_type_name;
|
||||
} else if (ScriptServer::is_global_class(key_elem_type_name)) {
|
||||
// Just load this as it shouldn't be a GDScript.
|
||||
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
|
||||
key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
|
||||
key_elem_type.builtin_type = Variant::OBJECT;
|
||||
key_elem_type.native_type = script->get_instance_base_type();
|
||||
key_elem_type.script_type = script;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
|
||||
}
|
||||
key_elem_type.is_constant = false;
|
||||
|
||||
StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
|
||||
GDScriptParser::DataType value_elem_type;
|
||||
value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||
|
||||
Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
|
||||
if (value_elem_builtin_type < Variant::VARIANT_MAX) {
|
||||
// Builtin type.
|
||||
value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
|
||||
value_elem_type.builtin_type = value_elem_builtin_type;
|
||||
} else if (class_exists(value_elem_type_name)) {
|
||||
value_elem_type.kind = GDScriptParser::DataType::NATIVE;
|
||||
value_elem_type.builtin_type = Variant::OBJECT;
|
||||
value_elem_type.native_type = value_elem_type_name;
|
||||
} else if (ScriptServer::is_global_class(value_elem_type_name)) {
|
||||
// Just load this as it shouldn't be a GDScript.
|
||||
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
|
||||
value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
|
||||
value_elem_type.builtin_type = Variant::OBJECT;
|
||||
value_elem_type.native_type = script->get_instance_base_type();
|
||||
value_elem_type.script_type = script;
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
|
||||
}
|
||||
value_elem_type.is_constant = false;
|
||||
|
||||
result.set_container_element_type(0, key_elem_type);
|
||||
result.set_container_element_type(1, value_elem_type);
|
||||
} else if (p_property.type == Variant::INT) {
|
||||
// Check if it's enum.
|
||||
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
|
||||
|
|
@ -5236,7 +5635,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
|
|||
} else {
|
||||
Vector<String> names = String(p_property.class_name).split(ENUM_SEPARATOR);
|
||||
if (names.size() == 2) {
|
||||
result = make_native_enum_type(names[1], names[0], false);
|
||||
result = make_enum_type(names[1], names[0], false);
|
||||
result.is_constant = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -5473,8 +5872,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
|
|||
#ifdef DEBUG_ENABLED
|
||||
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
|
||||
const StringName &name = p_identifier->name;
|
||||
GDScriptParser::DataType base = parser->current_class->get_datatype();
|
||||
GDScriptParser::ClassNode *base_class = base.class_type;
|
||||
|
||||
{
|
||||
List<MethodInfo> gdscript_funcs;
|
||||
|
|
@ -5502,40 +5899,56 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
|
|||
}
|
||||
}
|
||||
|
||||
const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
|
||||
if (p_in_local_scope) {
|
||||
while (base_class != nullptr) {
|
||||
GDScriptParser::ClassNode *base_class = current_class_type.class_type;
|
||||
|
||||
if (base_class != nullptr) {
|
||||
if (base_class->has_member(name)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
|
||||
return;
|
||||
}
|
||||
base_class = base_class->base_type.class_type;
|
||||
}
|
||||
|
||||
while (base_class != nullptr) {
|
||||
if (base_class->has_member(name)) {
|
||||
String base_class_name = base_class->get_global_name();
|
||||
if (base_class_name.is_empty()) {
|
||||
base_class_name = base_class->fqcn;
|
||||
}
|
||||
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
|
||||
return;
|
||||
}
|
||||
base_class = base_class->base_type.class_type;
|
||||
}
|
||||
}
|
||||
|
||||
StringName parent = base.native_type;
|
||||
while (parent != StringName()) {
|
||||
ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class.");
|
||||
StringName native_base_class = current_class_type.native_type;
|
||||
while (native_base_class != StringName()) {
|
||||
ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
|
||||
|
||||
if (ClassDB::has_method(parent, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent);
|
||||
if (ClassDB::has_method(native_base_class, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
|
||||
return;
|
||||
} else if (ClassDB::has_signal(parent, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent);
|
||||
} else if (ClassDB::has_signal(native_base_class, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
|
||||
return;
|
||||
} else if (ClassDB::has_property(parent, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent);
|
||||
} else if (ClassDB::has_property(native_base_class, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
|
||||
return;
|
||||
} else if (ClassDB::has_integer_constant(parent, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent);
|
||||
} else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
|
||||
return;
|
||||
} else if (ClassDB::has_enum(parent, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent);
|
||||
} else if (ClassDB::has_enum(native_base_class, name, true)) {
|
||||
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
|
||||
return;
|
||||
}
|
||||
parent = ClassDB::get_parent_class(parent);
|
||||
native_base_class = ClassDB::get_parent_class(native_base_class);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
|
||||
// Unary version.
|
||||
|
|
@ -5647,6 +6060,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
|
|||
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
|
||||
}
|
||||
}
|
||||
if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
|
||||
// Check the element types.
|
||||
if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
|
||||
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
|
||||
}
|
||||
if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
|
||||
valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
|
||||
#include "core/object/object.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
|
||||
class GDScriptAnalyzer {
|
||||
GDScriptParser *parser = nullptr;
|
||||
|
|
@ -125,8 +124,8 @@ class GDScriptAnalyzer {
|
|||
|
||||
// Helpers.
|
||||
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
|
||||
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
|
||||
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
|
||||
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
|
||||
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
|
||||
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
|
||||
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr);
|
||||
|
|
@ -137,6 +136,7 @@ class GDScriptAnalyzer {
|
|||
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
|
||||
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
|
||||
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
|
||||
void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
|
||||
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
|
||||
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
|
||||
void mark_node_unsafe(const GDScriptParser::Node *p_node);
|
||||
|
|
@ -161,7 +161,9 @@ public:
|
|||
Error analyze();
|
||||
|
||||
Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable);
|
||||
|
||||
static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
|
||||
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
|
||||
|
||||
GDScriptAnalyzer(GDScriptParser *p_parser);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -585,8 +585,25 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
|
|||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) {
|
||||
// Avoid validated evaluator for modulo and division when operands are int, since there's no check for division by zero.
|
||||
if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand) && ((p_operator != Variant::OP_DIVIDE && p_operator != Variant::OP_MODULE) || p_left_operand.type.builtin_type != Variant::INT || p_right_operand.type.builtin_type != Variant::INT)) {
|
||||
bool valid = HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand);
|
||||
|
||||
// Avoid validated evaluator for modulo and division when operands are int or integer vector, since there's no check for division by zero.
|
||||
if (valid && (p_operator == Variant::OP_DIVIDE || p_operator == Variant::OP_MODULE)) {
|
||||
switch (p_left_operand.type.builtin_type) {
|
||||
case Variant::INT:
|
||||
valid = p_right_operand.type.builtin_type != Variant::INT;
|
||||
break;
|
||||
case Variant::VECTOR2I:
|
||||
case Variant::VECTOR3I:
|
||||
case Variant::VECTOR4I:
|
||||
valid = p_right_operand.type.builtin_type != Variant::INT && p_right_operand.type.builtin_type != p_left_operand.type.builtin_type;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
if (p_target.mode == Address::TEMPORARY) {
|
||||
Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type);
|
||||
Variant::Type temp_type = temporaries[p_target.address].type;
|
||||
|
|
@ -634,6 +651,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
|
|||
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(element_type.builtin_type);
|
||||
append(element_type.native_type);
|
||||
} else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
|
||||
const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
|
||||
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(key_element_type.builtin_type);
|
||||
append(key_element_type.native_type);
|
||||
append(value_element_type.builtin_type);
|
||||
append(value_element_type.native_type);
|
||||
} else {
|
||||
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
|
||||
append(p_target);
|
||||
|
|
@ -889,6 +918,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
|
|||
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(element_type.builtin_type);
|
||||
append(element_type.native_type);
|
||||
} else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
|
||||
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
|
||||
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(key_type.builtin_type);
|
||||
append(key_type.native_type);
|
||||
append(value_type.builtin_type);
|
||||
append(value_type.native_type);
|
||||
} else {
|
||||
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
|
||||
append(p_target);
|
||||
|
|
@ -935,6 +976,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
|
|||
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(element_type.builtin_type);
|
||||
append(element_type.native_type);
|
||||
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
|
||||
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
|
||||
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
|
||||
append(p_target);
|
||||
append(p_source);
|
||||
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(key_type.builtin_type);
|
||||
append(key_type.native_type);
|
||||
append(value_type.builtin_type);
|
||||
append(value_type.native_type);
|
||||
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
|
||||
// Need conversion.
|
||||
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
|
||||
|
|
@ -1434,6 +1487,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
|
|||
ct.cleanup();
|
||||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
|
||||
append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
|
||||
for (int i = 0; i < p_arguments.size(); i++) {
|
||||
append(p_arguments[i]);
|
||||
}
|
||||
CallTarget ct = get_call_target(p_target);
|
||||
append(ct.target);
|
||||
append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
|
||||
append(p_key_type.builtin_type);
|
||||
append(p_key_type.native_type);
|
||||
append(p_value_type.builtin_type);
|
||||
append(p_value_type.native_type);
|
||||
ct.cleanup();
|
||||
}
|
||||
|
||||
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
|
||||
append_opcode(GDScriptFunction::OPCODE_AWAIT);
|
||||
append(p_operand);
|
||||
|
|
@ -1711,6 +1781,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
|
|||
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(element_type.builtin_type);
|
||||
append(element_type.native_type);
|
||||
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
|
||||
function->return_type.has_container_element_types()) {
|
||||
// Typed dictionary.
|
||||
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
|
||||
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
|
||||
append(p_return_value);
|
||||
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(key_type.builtin_type);
|
||||
append(key_type.native_type);
|
||||
append(value_type.builtin_type);
|
||||
append(value_type.native_type);
|
||||
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
|
||||
// Add conversion.
|
||||
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
|
||||
|
|
@ -1735,6 +1818,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
|
|||
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(element_type.builtin_type);
|
||||
append(element_type.native_type);
|
||||
} else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
|
||||
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
|
||||
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
|
||||
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
|
||||
append(p_return_value);
|
||||
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
|
||||
append(key_type.builtin_type);
|
||||
append(key_type.native_type);
|
||||
append(value_type.builtin_type);
|
||||
append(value_type.native_type);
|
||||
} else {
|
||||
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
|
||||
append(p_return_value);
|
||||
|
|
@ -1785,7 +1879,7 @@ void GDScriptByteCodeGenerator::end_block() {
|
|||
|
||||
void GDScriptByteCodeGenerator::clear_temporaries() {
|
||||
for (int slot_idx : temporaries_pending_clear) {
|
||||
// The temporary may have been re-used as something else since it was added to the list.
|
||||
// The temporary may have been reused as something else since it was added to the list.
|
||||
// In that case, there's **no** need to clear it.
|
||||
if (temporaries[slot_idx].can_contain_object) {
|
||||
clear_address(Address(Address::TEMPORARY, slot_idx)); // Can contain `RefCounted`, so clear it.
|
||||
|
|
@ -1803,6 +1897,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
|
|||
case Variant::BOOL:
|
||||
write_assign_false(p_address);
|
||||
break;
|
||||
case Variant::DICTIONARY:
|
||||
if (p_address.type.has_container_element_types()) {
|
||||
write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
|
||||
} else {
|
||||
write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
|
||||
}
|
||||
break;
|
||||
case Variant::ARRAY:
|
||||
if (p_address.type.has_container_element_type(0)) {
|
||||
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
|
||||
|
|
@ -1827,7 +1928,7 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
|
|||
}
|
||||
}
|
||||
|
||||
// Returns `true` if the local has been re-used and not cleaned up with `clear_address()`.
|
||||
// Returns `true` if the local has been reused and not cleaned up with `clear_address()`.
|
||||
bool GDScriptByteCodeGenerator::is_local_dirty(const Address &p_address) const {
|
||||
ERR_FAIL_COND_V(p_address.mode != Address::LOCAL_VARIABLE, false);
|
||||
return dirty_locals.has(p_address.address);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue