diff options
Diffstat (limited to 'modules')
70 files changed, 909 insertions, 661 deletions
diff --git a/modules/astcenc/SCsub b/modules/astcenc/SCsub new file mode 100644 index 0000000000..0f04f2bc28 --- /dev/null +++ b/modules/astcenc/SCsub @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_astcenc = env_modules.Clone() + +# Thirdparty source files + +thirdparty_obj = [] + +thirdparty_dir = "#thirdparty/astcenc/" +thirdparty_sources = [ + "astcenc_averages_and_directions.cpp", + "astcenc_block_sizes.cpp", + "astcenc_color_quantize.cpp", + "astcenc_color_unquantize.cpp", + "astcenc_compress_symbolic.cpp", + "astcenc_compute_variance.cpp", + "astcenc_decompress_symbolic.cpp", + "astcenc_diagnostic_trace.cpp", + "astcenc_entry.cpp", + "astcenc_find_best_partitioning.cpp", + "astcenc_ideal_endpoints_and_weights.cpp", + "astcenc_image.cpp", + "astcenc_integer_sequence.cpp", + "astcenc_mathlib.cpp", + "astcenc_mathlib_softfloat.cpp", + "astcenc_partition_tables.cpp", + "astcenc_percentile_tables.cpp", + "astcenc_pick_best_endpoint_format.cpp", + "astcenc_platform_isa_detection.cpp", + "astcenc_quantization.cpp", + "astcenc_symbolic_physical.cpp", + "astcenc_weight_align.cpp", + "astcenc_weight_quant_xfer_tables.cpp", +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_astcenc.Prepend(CPPPATH=[thirdparty_dir]) + +env_thirdparty = env_astcenc.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) +env.modules_sources += thirdparty_obj + +# Godot source files + +module_obj = [] + +env_astcenc.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/astcenc/config.py b/modules/astcenc/config.py new file mode 100644 index 0000000000..eb565b85b9 --- /dev/null +++ b/modules/astcenc/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env.editor_build + + +def configure(env): + pass diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp new file mode 100644 index 0000000000..ce10201343 --- /dev/null +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -0,0 +1,251 @@ +/**************************************************************************/ +/* image_compress_astcenc.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_compress_astcenc.h" + +#include "core/os/os.h" +#include "core/string/print_string.h" + +#include <astcenc.h> + +void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format) { + 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 (img_format >= Image::FORMAT_DXT1) { + return; // Do not compress, already compressed. + } + + bool is_hdr = false; + if ((img_format >= Image::FORMAT_RH) && (img_format <= Image::FORMAT_RGBE9995)) { + is_hdr = true; + r_img->convert(Image::FORMAT_RGBAF); + } else { + r_img->convert(Image::FORMAT_RGBA8); + } + + // Determine encoder output format from our enum. + + Image::Format target_format = Image::FORMAT_RGBA8; + astcenc_profile profile = ASTCENC_PRF_LDR; + 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; + } + block_x = 8; + block_y = 8; + } + + // Compress image data and (if required) mipmaps. + + const bool mipmaps = r_img->has_mipmaps(); + int width = r_img->get_width(); + int height = r_img->get_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" : "")); + + // Initialize astcenc. + + astcenc_config config; + config.block_x = block_x; + config.block_y = block_y; + config.profile = profile; + const float quality = ASTCENC_PRE_MEDIUM; + + astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config); + ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status))); + + // Context allocation. + + astcenc_context *context; + const unsigned int thread_count = OS::get_singleton()->get_processor_count(); + + 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))); + + // Compress image. + + Vector<uint8_t> image_data = r_img->get_data(); + uint8_t *slices = image_data.ptrw(); + + astcenc_image image; + image.dim_x = width; + image.dim_y = height; + image.dim_z = 1; + image.data_type = ASTCENC_TYPE_U8; + if (is_hdr) { + image.data_type = ASTCENC_TYPE_F32; + } + image.data = reinterpret_cast<void **>(&slices); + + // Compute the number of ASTC blocks in each dimension. + unsigned int block_count_x = (width + block_x - 1) / block_x; + unsigned int block_count_y = (height + block_y - 1) / block_y; + size_t comp_len = block_count_x * block_count_y * 16; + + Vector<uint8_t> compressed_data; + compressed_data.resize(comp_len); + compressed_data.fill(0); + + const astcenc_swizzle swizzle = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + status = astcenc_compress_image(context, &image, &swizzle, compressed_data.ptrw(), comp_len, 0); + ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status))); + + // Replace original image with compressed one. + + r_img->set_data(width, height, mipmaps, target_format, compressed_data); + + print_verbose(vformat("astcenc: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); +} + +void _decompress_astc(Image *r_img) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + + // Determine decompression parameters from image 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."); + } + + // Initialize astcenc. + + astcenc_profile profile = ASTCENC_PRF_LDR; + if (is_hdr) { + profile = ASTCENC_PRF_HDR; + } + astcenc_config config; + const float quality = ASTCENC_PRE_MEDIUM; + + astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config); + ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status))); + + // Context allocation. + + astcenc_context *context = nullptr; + const unsigned int thread_count = OS::get_singleton()->get_processor_count(); + + 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))); + + // Decompress image. + + const bool mipmaps = r_img->has_mipmaps(); + int width = r_img->get_width(); + int height = r_img->get_height(); + + astcenc_image image; + image.dim_x = width; + image.dim_y = height; + image.dim_z = 1; + image.data_type = ASTCENC_TYPE_U8; + Image::Format target_format = Image::FORMAT_RGBA8; + if (is_hdr) { + target_format = Image::FORMAT_RGBAF; + image.data_type = ASTCENC_TYPE_F32; + } + + Vector<uint8_t> image_data = r_img->get_data(); + + Vector<uint8_t> new_image_data; + new_image_data.resize(Image::get_image_data_size(width, height, target_format, false)); + new_image_data.fill(0); + uint8_t *slices = new_image_data.ptrw(); + image.data = reinterpret_cast<void **>(&slices); + + const astcenc_swizzle swizzle = { + ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A + }; + + status = astcenc_decompress_image(context, image_data.ptr(), image_data.size(), &image, &swizzle, 0); + ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS, + vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status))); + ERR_FAIL_COND_MSG(image.dim_z > 1, + "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported."); + + // Replace original image with compressed one. + + Image::Format image_format = Image::FORMAT_RGBA8; + if (image.data_type == ASTCENC_TYPE_F32) { + image_format = Image::FORMAT_RGBAF; + } else if (image.data_type == ASTCENC_TYPE_U8) { + image_format = Image::FORMAT_RGBA8; + } else if (image.data_type == ASTCENC_TYPE_F16) { + image_format = Image::FORMAT_RGBAH; + } else { + ERR_FAIL_MSG("astcenc: ASTC decompression failed with an unknown format."); + } + + r_img->set_data(image.dim_x, image.dim_y, mipmaps, image_format, new_image_data); + + print_verbose(vformat("astcenc: Decompression took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); +} diff --git a/modules/astcenc/image_compress_astcenc.h b/modules/astcenc/image_compress_astcenc.h new file mode 100644 index 0000000000..a197a91e0d --- /dev/null +++ b/modules/astcenc/image_compress_astcenc.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* image_compress_astcenc.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef IMAGE_COMPRESS_ASTCENC_H +#define IMAGE_COMPRESS_ASTCENC_H + +#include "core/io/image.h" + +void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format); +void _decompress_astc(Image *r_img); + +#endif // IMAGE_COMPRESS_ASTCENC_H diff --git a/modules/astcenc/register_types.cpp b/modules/astcenc/register_types.cpp new file mode 100644 index 0000000000..0bb1c3432f --- /dev/null +++ b/modules/astcenc/register_types.cpp @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* register_types.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "register_types.h" + +#include "image_compress_astcenc.h" + +void initialize_astcenc_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + Image::_image_compress_astc_func = _compress_astc; + Image::_image_decompress_astc = _decompress_astc; +} + +void uninitialize_astcenc_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } +} diff --git a/modules/astcenc/register_types.h b/modules/astcenc/register_types.h new file mode 100644 index 0000000000..636da9ff8b --- /dev/null +++ b/modules/astcenc/register_types.h @@ -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 ASTCENC_REGISTER_TYPES_H +#define ASTCENC_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_astcenc_module(ModuleInitializationLevel p_level); +void uninitialize_astcenc_module(ModuleInitializationLevel p_level); + +#endif // ASTCENC_REGISTER_TYPES_H diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 3b789a235f..5b451fbf6b 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -58,6 +58,13 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, 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, @@ -88,7 +95,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, const uint32_t line_width = (width_bytes + 3) & ~3; // The actual data traversal is determined by - // the data width in case of 8/4/1 bit images + // the data width in case of 8/4/2/1 bit images const uint32_t w = bits_per_pixel >= 24 ? 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; @@ -114,6 +121,17 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, index += 8; line_ptr += 1; } break; + case 2: { + uint8_t color_index = *line_ptr; + + 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; + } break; case 4: { uint8_t color_index = *line_ptr; diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index b5192bd664..a6aeec54cc 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -33,8 +33,8 @@ #include "core/os/os.h" #include "core/string/print_string.h" -#include "thirdparty/etcpak/ProcessDxtc.hpp" -#include "thirdparty/etcpak/ProcessRGB.hpp" +#include <ProcessDxtc.hpp> +#include <ProcessRGB.hpp> EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { switch (p_channels) { @@ -130,7 +130,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) { target_format = Image::FORMAT_DXT5; } else { - ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format."); + ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); } // Compress image data and (if required) mipmaps. @@ -171,7 +171,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua const uint8_t *src_read = r_img->get_data().ptr(); - print_verbose(vformat("ETCPAK: Encoding image size %dx%d to format %s.", width, height, Image::get_format_name(target_format))); + print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : "")); int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); Vector<uint8_t> dest_data; @@ -232,12 +232,12 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); } else { - ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format."); + ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format."); } } // Replace original image with compressed one. r_img->set_data(width, height, mipmaps, target_format, dest_data); - print_verbose(vformat("ETCPAK encode took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); + print_verbose(vformat("etcpak: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); } diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index fd748ea569..3fe741a582 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -553,6 +553,7 @@ @icon("res://path/to/class/icon.svg") [/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 [code]@icon[/code] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@onready"> @@ -585,6 +586,7 @@ @tool extends Node [/codeblock] + [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 1b488d560c..e04a962dcb 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1722,21 +1722,52 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { if (list_resolved) { variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; variable_type.kind = GDScriptParser::DataType::BUILTIN; - variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else? - p_for->variable->set_datatype(variable_type); + variable_type.builtin_type = Variant::INT; } else if (p_for->list) { resolve_node(p_for->list, false); - if (p_for->list->datatype.has_container_element_type()) { - variable_type = p_for->list->datatype.get_container_element_type(); - variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } else if (p_for->list->datatype.is_typed_container_type()) { - variable_type = p_for->list->datatype.get_typed_container_type(); - variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } else { - // Last resort - // TODO: Must other cases be handled? Must we mark as unsafe? - variable_type.type_source = GDScriptParser::DataType::UNDETECTED; + GDScriptParser::DataType list_type = p_for->list->get_datatype(); + if (!list_type.is_hard_type()) { + mark_node_unsafe(p_for->list); + } + if (list_type.is_variant()) { + variable_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_for->list); + } else if (list_type.has_container_element_type()) { + variable_type = list_type.get_container_element_type(); + variable_type.type_source = list_type.type_source; + } else if (list_type.is_typed_container_type()) { + variable_type = list_type.get_typed_container_type(); + variable_type.type_source = list_type.type_source; + } else if (list_type.builtin_type == Variant::INT || list_type.builtin_type == Variant::FLOAT || list_type.builtin_type == Variant::STRING) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = list_type.builtin_type; + } else if (list_type.builtin_type == Variant::VECTOR2I || list_type.builtin_type == Variant::VECTOR3I) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::INT; + } else if (list_type.builtin_type == Variant::VECTOR2 || list_type.builtin_type == Variant::VECTOR3) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::FLOAT; + } else if (list_type.builtin_type == Variant::OBJECT) { + GDScriptParser::DataType return_type; + List<GDScriptParser::DataType> par_types; + int default_arg_count = 0; + bool is_static = false; + bool is_vararg = false; + if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, is_static, is_vararg)) { + variable_type = return_type; + variable_type.type_source = list_type.type_source; + } else if (!list_type.is_hard_type()) { + variable_type.kind = GDScriptParser::DataType::VARIANT; + } else { + push_error(vformat(R"(Unable to iterate on object of type "%s".)", list_type.to_string()), p_for->list); + } + } else if (list_type.builtin_type == Variant::ARRAY || list_type.builtin_type == Variant::DICTIONARY || !list_type.is_hard_type()) { variable_type.kind = GDScriptParser::DataType::VARIANT; + } else { + push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list); } } if (p_for->variable) { @@ -2910,8 +2941,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - GDScript *scr = GDScriptCache::get_full_script(p_identifier_datatype.script_path, err).ptr(); - ERR_FAIL_COND_MSG(err != OK, "Error while getting full script."); + GDScript *scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err).ptr(); + ERR_FAIL_COND_MSG(err != OK, vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path)); scr = scr->find_class(p_identifier_datatype.class_type->fqcn); p_identifier->reduced_value = scr; p_identifier->is_constant = true; @@ -3140,7 +3171,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident for (int i = 0; i < current_enum->values.size(); i++) { const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; if (element.identifier->name == p_identifier->name) { - StringName enum_name = current_enum->identifier->name ? current_enum->identifier->name : UNNAMED_ENUM; + 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); if (element.parent_enum->identifier) { type.enum_type = element.parent_enum->identifier->name; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 77c6690d20..d63a1b4536 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -213,7 +213,7 @@ static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScr } GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { - if (p_expression->is_constant && !p_expression->get_datatype().is_meta_type) { + if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 97d5c1d8b2..f5d3306376 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -540,43 +540,28 @@ void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); head->fqcn = script_path; current_class = head; + bool can_have_class_or_extends = true; - // If we happen to parse an annotation before extends or class_name keywords, track it. - // @tool is allowed, but others should fail. - AnnotationNode *premature_annotation = nullptr; - - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @tool, script-level, or standalone annotation. + while (match(GDScriptTokenizer::Token::ANNOTATION)) { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == SNAME("@tool")) { - // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). - _is_tool = true; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@tool" annotation.)"); - } - // @tool annotation has no specific target. - annotation->apply(this, nullptr); - } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - premature_annotation = annotation; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } + if (annotation->applies_to(AnnotationInfo::SCRIPT)) { annotation->apply(this, head); } else { - premature_annotation = annotation; annotation_stack.push_back(annotation); + // This annotation must appear after script-level annotations + // and class_name/extends (ex: could be @onready or @export), + // so we stop looking for script-level stuff. + can_have_class_or_extends = false; + break; } } } - for (bool should_break = false; !should_break;) { + while (can_have_class_or_extends) { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (premature_annotation != nullptr) { - push_error(R"("class_name" should be used before annotations (except @tool).)"); - } advance(); if (head->identifier != nullptr) { push_error(R"("class_name" can only be used once.)"); @@ -585,9 +570,6 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (premature_annotation != nullptr) { - push_error(R"("extends" should be used before annotations (except @tool).)"); - } advance(); if (head->extends_used) { push_error(R"("extends" can only be used once.)"); @@ -597,7 +579,8 @@ void GDScriptParser::parse_program() { } break; default: - should_break = true; + // No tokens are allowed between script annotations and class/extends. + can_have_class_or_extends = false; break; } @@ -606,21 +589,6 @@ void GDScriptParser::parse_program() { } } - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for a script-level, or standalone annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); - if (annotation != nullptr) { - if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } - annotation->apply(this, head); - } else { - annotation_stack.push_back(annotation); - } - } - } - parse_class_body(true); complete_extents(head); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd new file mode 100644 index 0000000000..cf56a0a933 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd @@ -0,0 +1,6 @@ +const constant_float = 1.0 + +func test(): + for x in constant_float: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out new file mode 100644 index 0000000000..e309831b3e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "float" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd new file mode 100644 index 0000000000..5ee8ac19e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd @@ -0,0 +1,6 @@ +const constant_int = 1 + +func test(): + for x in constant_int: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd new file mode 100644 index 0000000000..b3db4f3b49 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd @@ -0,0 +1,6 @@ +enum { enum_value = 1 } + +func test(): + for x in enum_value: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd new file mode 100644 index 0000000000..87c54f7402 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd @@ -0,0 +1,6 @@ +func test(): + var hard_float := 1.0 + + for x in hard_float: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out new file mode 100644 index 0000000000..e309831b3e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "float" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd new file mode 100644 index 0000000000..2a43f5a930 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd @@ -0,0 +1,6 @@ +func test(): + var hard_int := 1 + + for x in hard_int: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd new file mode 100644 index 0000000000..c3920d35b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd @@ -0,0 +1,14 @@ +class Iterator: + func _iter_init(_count): + return true + func _iter_next(_count): + return false + func _iter_get(_count) -> StringName: + return &'custom' + +func test(): + var hard_iterator := Iterator.new() + + for x in hard_iterator: + if x is int: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out new file mode 100644 index 0000000000..a48591a3b4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "StringName" so it can't be of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd new file mode 100644 index 0000000000..b36d87aabe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd @@ -0,0 +1,6 @@ +func test(): + var hard_string := 'a' + + for x in hard_string: + if x is int: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out new file mode 100644 index 0000000000..92c5ebc599 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "String" so it can't be of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd new file mode 100644 index 0000000000..060a8bedf9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd @@ -0,0 +1,3 @@ +func test(): + for x in true: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out new file mode 100644 index 0000000000..94cb038885 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Unable to iterate on value of type "bool". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd new file mode 100644 index 0000000000..6cfc822482 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd @@ -0,0 +1,4 @@ +func test(): + for x in 1: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd new file mode 100644 index 0000000000..f351fc1f7b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd @@ -0,0 +1,7 @@ +enum { + V1, + V2 = V1, +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd new file mode 100644 index 0000000000..7b74be6f2c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd @@ -0,0 +1,15 @@ +func test(): + var variant_int: Variant = 1 + var weak_int = 1 + + for x in variant_int: + if x is String: + print('never') + print(x) + + for x in weak_int: + if x is String: + print('never') + print(x) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out new file mode 100644 index 0000000000..7677671cfd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out @@ -0,0 +1,4 @@ +GDTEST_OK +0 +0 +ok diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd index 179e454073..0085b3f367 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -1,6 +1,6 @@ -# Error here. `class_name` should be used *before* annotations, not after (except @tool). -@icon("res://path/to/optional/icon.svg") +# Error here. Annotations should be used before `class_name`, not after. class_name HelloWorld +@icon("res://path/to/optional/icon.svg") func test(): pass diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out index 02b33c8692..a598ff8424 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -"class_name" should be used before annotations (except @tool). +Annotation "@icon" is not allowed in this level. diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.gd b/modules/gdscript/tests/scripts/parser/features/class_name.gd index 8bd188e247..19009e433d 100644 --- a/modules/gdscript/tests/scripts/parser/features/class_name.gd +++ b/modules/gdscript/tests/scripts/parser/features/class_name.gd @@ -1,5 +1,5 @@ -class_name HelloWorld @icon("res://path/to/optional/icon.svg") +class_name HelloWorld func test(): pass diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd new file mode 100644 index 0000000000..81355e0255 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd @@ -0,0 +1,51 @@ +const constant_float = 1.0 +const constant_int = 1 +enum { enum_value = 1 } + +class Iterator: + func _iter_init(_count): + return true + func _iter_next(_count): + return false + func _iter_get(_count) -> StringName: + return &'custom' + +func test(): + var hard_float := 1.0 + var hard_int := 1 + var hard_string := '0' + var hard_iterator := Iterator.new() + + var variant_float: Variant = hard_float + var variant_int: Variant = hard_int + var variant_string: Variant = hard_string + var variant_iterator: Variant = hard_iterator + + for i in 1.0: + print(typeof(i) == TYPE_FLOAT) + for i in 1: + print(typeof(i) == TYPE_INT) + for i in 'a': + print(typeof(i) == TYPE_STRING) + for i in Iterator.new(): + print(typeof(i) == TYPE_STRING_NAME) + + for i in hard_float: + print(typeof(i) == TYPE_FLOAT) + for i in hard_int: + print(typeof(i) == TYPE_INT) + for i in hard_string: + print(typeof(i) == TYPE_STRING) + for i in hard_iterator: + print(typeof(i) == TYPE_STRING_NAME) + + for i in variant_float: + print(typeof(i) == TYPE_FLOAT) + for i in variant_int: + print(typeof(i) == TYPE_INT) + for i in variant_string: + print(typeof(i) == TYPE_STRING) + for i in variant_iterator: + print(typeof(i) == TYPE_STRING_NAME) + + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out new file mode 100644 index 0000000000..b3e82d52ef --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out @@ -0,0 +1,14 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true +true +true +ok diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index e39127e0a1..7606465b8a 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -113,6 +113,11 @@ void CSharpLanguage::init() { BindingsGenerator::handle_cmdline_args(cmdline_args); #endif + GLOBAL_DEF("dotnet/project/assembly_name", ""); +#ifdef TOOLS_ENABLED + GLOBAL_DEF("dotnet/project/solution_directory", ""); +#endif + gdmono = memnew(GDMono); gdmono->initialize(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 745a8b73f8..db96003baf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -17,6 +17,8 @@ namespace GodotTools.Export { public partial class ExportPlugin : EditorExportPlugin { + public override string _GetName() => "C#"; + private List<string> _tempFolders = new List<string>(); public void RegisterExportSettings() diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index de10c04e31..08147d9f6a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -411,8 +411,6 @@ namespace GodotTools _editorSettings = editorInterface.GetEditorSettings(); - GodotSharpDirs.RegisterProjectSettings(); - _errorDialog = new AcceptDialog(); editorBaseControl.AddChild(_errorDialog); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 4e892be55c..7624989092 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -48,12 +48,6 @@ namespace GodotTools.Internals } } - public static void RegisterProjectSettings() - { - GlobalDef("dotnet/project/assembly_name", ""); - GlobalDef("dotnet/project/solution_directory", ""); - } - public static void DetermineProjectLocation() { static string DetermineProjectName() diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index c0d88553ad..03cbfda1bd 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1864,12 +1864,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append("\n" OPEN_BLOCK_L1); if (getter) { - p_output.append(INDENT2 "get\n" - - // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) - "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - - OPEN_BLOCK_L2 INDENT3); + p_output.append(INDENT2 "get\n" OPEN_BLOCK_L2 INDENT3); p_output.append("return "); p_output.append(getter->proxy_name + "("); @@ -1884,21 +1879,11 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(itos(p_iprop.index)); } } - p_output.append(");\n" - - CLOSE_BLOCK_L2 - - // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) - "#pragma warning restore CS0618\n"); + p_output.append(");\n" CLOSE_BLOCK_L2); } if (setter) { - p_output.append(INDENT2 "set\n" - - // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) - "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - - OPEN_BLOCK_L2 INDENT3); + p_output.append(INDENT2 "set\n" OPEN_BLOCK_L2 INDENT3); p_output.append(setter->proxy_name + "("); if (p_iprop.index != -1) { @@ -1912,12 +1897,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(itos(p_iprop.index) + ", "); } } - p_output.append("value);\n" - - CLOSE_BLOCK_L2 - - // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) - "#pragma warning restore CS0618\n"); + p_output.append("value);\n" CLOSE_BLOCK_L2); } p_output.append(CLOSE_BLOCK_L1); @@ -3056,12 +3036,10 @@ bool BindingsGenerator::_populate_object_type_interfaces() { HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname); if (accessor) { - const PropertyInterface *accessor_property = itype.find_property_by_name(accessor->value); - - // We only deprecate an accessor method if it's in the same class as the property. It's easier this way, but also - // we don't know if an accessor method in a different class could have other purposes, so better leave those untouched. - imethod.is_deprecated = true; - imethod.deprecation_message = imethod.proxy_name + " is deprecated. Use the " + accessor_property->proxy_name + " property instead."; + // We only make internal an accessor method if it's in the same class as the property. + // It's easier this way, but also we don't know if an accessor method in a different class + // could have other purposes, so better leave those untouched. + imethod.is_internal = true; } if (itype.class_doc) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 19f7c54847..b57317e1d0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -816,26 +816,26 @@ namespace Godot public Basis(Vector3 axis, real_t angle) { Vector3 axisSq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); - real_t cosine = Mathf.Cos(angle); - Row0.x = axisSq.x + cosine * (1.0f - axisSq.x); - Row1.y = axisSq.y + cosine * (1.0f - axisSq.y); - Row2.z = axisSq.z + cosine * (1.0f - axisSq.z); + (real_t sin, real_t cos) = Mathf.SinCos(angle); - real_t sine = Mathf.Sin(angle); - real_t t = 1.0f - cosine; + Row0.x = axisSq.x + cos * (1.0f - axisSq.x); + Row1.y = axisSq.y + cos * (1.0f - axisSq.y); + Row2.z = axisSq.z + cos * (1.0f - axisSq.z); + + real_t t = 1.0f - cos; real_t xyzt = axis.x * axis.y * t; - real_t zyxs = axis.z * sine; + real_t zyxs = axis.z * sin; Row0.y = xyzt - zyxs; Row1.x = xyzt + zyxs; xyzt = axis.x * axis.z * t; - zyxs = axis.y * sine; + zyxs = axis.y * sin; Row0.z = xyzt + zyxs; Row2.x = xyzt - zyxs; xyzt = axis.y * axis.z * t; - zyxs = axis.x * sine; + zyxs = axis.x * sin; Row1.z = xyzt - zyxs; Row2.y = xyzt + zyxs; } @@ -885,19 +885,29 @@ namespace Godot /// <param name="order">The order to compose the Euler angles.</param> public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.Yxz) { - real_t c, s; - - c = Mathf.Cos(euler.x); - s = Mathf.Sin(euler.x); - Basis xmat = new Basis(new Vector3(1, 0, 0), new Vector3(0, c, s), new Vector3(0, -s, c)); + (real_t sin, real_t cos) = Mathf.SinCos(euler.x); + Basis xmat = new Basis + ( + new Vector3(1, 0, 0), + new Vector3(0, cos, sin), + new Vector3(0, -sin, cos) + ); - c = Mathf.Cos(euler.y); - s = Mathf.Sin(euler.y); - Basis ymat = new Basis(new Vector3(c, 0, -s), new Vector3(0, 1, 0), new Vector3(s, 0, c)); + (sin, cos) = Mathf.SinCos(euler.y); + Basis ymat = new Basis + ( + new Vector3(cos, 0, -sin), + new Vector3(0, 1, 0), + new Vector3(sin, 0, cos) + ); - c = Mathf.Cos(euler.z); - s = Mathf.Sin(euler.z); - Basis zmat = new Basis(new Vector3(c, s, 0), new Vector3(-s, c, 0), new Vector3(0, 0, 1)); + (sin, cos) = Mathf.SinCos(euler.z); + Basis zmat = new Basis + ( + new Vector3(cos, sin, 0), + new Vector3(-sin, cos, 0), + new Vector3(0, 0, 1) + ); switch (order) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 4075a878d2..2effdecf40 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -359,24 +359,6 @@ namespace Godot } /// <summary> - /// Returns the result of the linear interpolation between - /// this color and <paramref name="to"/> by color amount <paramref name="weight"/>. - /// </summary> - /// <param name="to">The destination color for interpolation.</param> - /// <param name="weight">A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> - /// <returns>The resulting color of the interpolation.</returns> - public readonly Color Lerp(Color to, Color weight) - { - return new Color - ( - (float)Mathf.Lerp(r, to.r, weight.r), - (float)Mathf.Lerp(g, to.g, weight.g), - (float)Mathf.Lerp(b, to.b, weight.b), - (float)Mathf.Lerp(a, to.a, weight.a) - ); - } - - /// <summary> /// Returns the color converted to the sRGB color space. /// This method assumes the original color is in the linear color space. /// See also <see cref="SrgbToLinear"/> which performs the opposite operation. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index ea05c1547c..72a1868964 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -83,6 +83,17 @@ namespace Godot } /// <summary> + /// Returns the sine and cosine of angle <paramref name="s"/> in radians. + /// </summary> + /// <param name="s">The angle in radians.</param> + /// <returns>The sine and cosine of that angle.</returns> + public static (real_t Sin, real_t Cos) SinCos(real_t s) + { + (double sin, double cos) = Math.SinCos(s); + return ((real_t)sin, (real_t)cos); + } + + /// <summary> /// Returns <see langword="true"/> if <paramref name="a"/> and <paramref name="b"/> are approximately /// equal to each other. /// The comparison is done using the provided tolerance value. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index fd37f8d9e8..f11b3c553a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -381,14 +381,14 @@ namespace Godot } real_t radians = Mathf.DegToRad(fovyDegrees / (real_t)2.0); real_t deltaZ = zFar - zNear; - real_t sine = Mathf.Sin(radians); + (real_t sin, real_t cos) = Mathf.SinCos(radians); - if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) + if ((deltaZ == 0) || (sin == 0) || (aspect == 0)) { return Zero; } - real_t cotangent = Mathf.Cos(radians) / sine; + real_t cotangent = cos / sin; Projection proj = Projection.Identity; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 47106bb402..8e4f9178f7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -542,14 +542,13 @@ namespace Godot } else { - real_t sinAngle = Mathf.Sin(angle * 0.5f); - real_t cosAngle = Mathf.Cos(angle * 0.5f); - real_t s = sinAngle / d; + (real_t sin, real_t cos) = Mathf.SinCos(angle * 0.5f); + real_t s = sin / d; x = axis.x * s; y = axis.y * s; z = axis.z * s; - w = cosAngle; + w = cos; } } @@ -593,12 +592,9 @@ namespace Godot // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6) // a3 is the angle of the first rotation, following the notation in this reference. - real_t cosA1 = Mathf.Cos(halfA1); - real_t sinA1 = Mathf.Sin(halfA1); - real_t cosA2 = Mathf.Cos(halfA2); - real_t sinA2 = Mathf.Sin(halfA2); - real_t cosA3 = Mathf.Cos(halfA3); - real_t sinA3 = Mathf.Sin(halfA3); + (real_t sinA1, real_t cosA1) = Mathf.SinCos(halfA1); + (real_t sinA2, real_t cosA2) = Mathf.SinCos(halfA2); + (real_t sinA3, real_t cosA3) = Mathf.SinCos(halfA3); return new Quaternion( (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3), diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 6dda150c2b..fa060e3a53 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -195,8 +195,10 @@ namespace Godot Vector2 s2 = transform.GetScale(); // Slerp rotation - var v1 = new Vector2(Mathf.Cos(r1), Mathf.Sin(r1)); - var v2 = new Vector2(Mathf.Cos(r2), Mathf.Sin(r2)); + (real_t sin1, real_t cos1) = Mathf.SinCos(r1); + (real_t sin2, real_t cos2) = Mathf.SinCos(r2); + var v1 = new Vector2(cos1, sin1); + var v2 = new Vector2(cos2, sin2); real_t dot = v1.Dot(v2); @@ -213,7 +215,8 @@ namespace Godot { real_t angle = weight * Mathf.Acos(dot); Vector2 v3 = (v2 - (v1 * dot)).Normalized(); - v = (v1 * Mathf.Cos(angle)) + (v3 * Mathf.Sin(angle)); + (real_t sine, real_t cos) = Mathf.SinCos(angle); + v = (v1 * sine) + (v3 * cos); } // Extract parameters @@ -434,8 +437,9 @@ namespace Godot /// <param name="origin">The origin vector, or column index 2.</param> public Transform2D(real_t rotation, Vector2 origin) { - x.x = y.y = Mathf.Cos(rotation); - x.y = y.x = Mathf.Sin(rotation); + (real_t sin, real_t cos) = Mathf.SinCos(rotation); + x.x = y.y = cos; + x.y = y.x = sin; y.x *= -1; this.origin = origin; } @@ -451,10 +455,12 @@ namespace Godot /// <param name="origin">The origin vector, or column index 2.</param> public Transform2D(real_t rotation, Vector2 scale, real_t skew, Vector2 origin) { - x.x = Mathf.Cos(rotation) * scale.x; - y.y = Mathf.Cos(rotation + skew) * scale.y; - y.x = -Mathf.Sin(rotation + skew) * scale.y; - x.y = Mathf.Sin(rotation) * scale.x; + (real_t rotationSin, real_t rotationCos) = Mathf.SinCos(rotation); + (real_t rotationSkewSin, real_t rotationSkewCos) = Mathf.SinCos(rotation + skew); + x.x = rotationCos * scale.x; + y.y = rotationSkewCos * scale.y; + y.x = -rotationSkewSin * scale.y; + x.y = rotationSin * scale.x; this.origin = origin; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 57cbef1c5c..07cb34cadd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -390,24 +390,6 @@ namespace Godot } /// <summary> - /// Returns the result of the linear interpolation between - /// this vector and <paramref name="to"/> by the vector amount <paramref name="weight"/>. - /// </summary> - /// <param name="to">The destination vector for interpolation.</param> - /// <param name="weight"> - /// A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation. - /// </param> - /// <returns>The resulting vector of the interpolation.</returns> - public readonly Vector2 Lerp(Vector2 to, Vector2 weight) - { - return new Vector2 - ( - Mathf.Lerp(x, to.x, weight.x), - Mathf.Lerp(y, to.y, weight.y) - ); - } - - /// <summary> /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> @@ -539,11 +521,12 @@ namespace Godot /// <returns>The rotated vector.</returns> public readonly Vector2 Rotated(real_t angle) { - real_t sine = Mathf.Sin(angle); - real_t cosi = Mathf.Cos(angle); - return new Vector2( - x * cosi - y * sine, - x * sine + y * cosi); + (real_t sin, real_t cos) = Mathf.SinCos(angle); + return new Vector2 + ( + x * cos - y * sin, + x * sin + y * cos + ); } /// <summary> @@ -693,7 +676,8 @@ namespace Godot /// <returns>The resulting vector.</returns> public static Vector2 FromAngle(real_t angle) { - return new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)); + (real_t sin, real_t cos) = Mathf.SinCos(angle); + return new Vector2(cos, sin); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index 91be548a21..740fedec66 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -95,38 +95,6 @@ namespace Godot } /// <summary> - /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians. - /// - /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when - /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. - /// </summary> - /// <returns>The angle of this vector, in radians.</returns> - public readonly real_t Angle() - { - return Mathf.Atan2(y, x); - } - - /// <summary> - /// Returns the angle to the given vector, in radians. - /// </summary> - /// <param name="to">The other vector to compare this vector to.</param> - /// <returns>The angle between the two vectors, in radians.</returns> - public readonly real_t AngleTo(Vector2i to) - { - return Mathf.Atan2(Cross(to), Dot(to)); - } - - /// <summary> - /// Returns the angle between the line connecting the two points and the X axis, in radians. - /// </summary> - /// <param name="to">The other vector to compare this vector to.</param> - /// <returns>The angle between the two vectors, in radians.</returns> - public readonly real_t AngleToPoint(Vector2i to) - { - return Mathf.Atan2(to.y - y, to.x - x); - } - - /// <summary> /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> @@ -153,48 +121,6 @@ namespace Godot } /// <summary> - /// Returns the cross product of this vector and <paramref name="with"/>. - /// </summary> - /// <param name="with">The other vector.</param> - /// <returns>The cross product vector.</returns> - public readonly int Cross(Vector2i with) - { - return x * with.y - y * with.x; - } - - /// <summary> - /// Returns the squared distance between this vector and <paramref name="to"/>. - /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if - /// you need to compare vectors or need the squared distance for some formula. - /// </summary> - /// <param name="to">The other vector to use.</param> - /// <returns>The squared distance between the two vectors.</returns> - public readonly int DistanceSquaredTo(Vector2i to) - { - return (to - this).LengthSquared(); - } - - /// <summary> - /// Returns the distance between this vector and <paramref name="to"/>. - /// </summary> - /// <param name="to">The other vector to use.</param> - /// <returns>The distance between the two vectors.</returns> - public readonly real_t DistanceTo(Vector2i to) - { - return (to - this).Length(); - } - - /// <summary> - /// Returns the dot product of this vector and <paramref name="with"/>. - /// </summary> - /// <param name="with">The other vector to use.</param> - /// <returns>The dot product of the two vectors.</returns> - public readonly int Dot(Vector2i with) - { - return x * with.x + y * with.y; - } - - /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> /// <seealso cref="LengthSquared"/> @@ -242,38 +168,6 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components - /// and <paramref name="mod"/>. - /// </summary> - /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns> - /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. - /// </returns> - public readonly Vector2i PosMod(int mod) - { - Vector2i v = this; - v.x = Mathf.PosMod(v.x, mod); - v.y = Mathf.PosMod(v.y, mod); - return v; - } - - /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components - /// and <paramref name="modv"/>'s components. - /// </summary> - /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns> - /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. - /// </returns> - public readonly Vector2i PosMod(Vector2i modv) - { - Vector2i v = this; - v.x = Mathf.PosMod(v.x, modv.x); - v.y = Mathf.PosMod(v.y, modv.y); - return v; - } - - /// <summary> /// Returns a vector with each component set to one or negative one, depending /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(int)"/> on each component. @@ -287,16 +181,6 @@ namespace Godot return v; } - /// <summary> - /// Returns a perpendicular vector rotated 90 degrees counter-clockwise - /// compared to the original, with the same length. - /// </summary> - /// <returns>The perpendicular vector.</returns> - public readonly Vector2i Orthogonal() - { - return new Vector2i(y, -x); - } - // Constants private static readonly Vector2i _zero = new Vector2i(0, 0); private static readonly Vector2i _one = new Vector2i(1, 1); @@ -467,7 +351,7 @@ namespace Godot /// with the components of the given <see langword="int"/>. /// This operation uses truncated division, which is often not desired /// as it does not work well with negative numbers. - /// Consider using <see cref="PosMod(int)"/> instead + /// Consider using <see cref="Mathf.PosMod(int, int)"/> instead /// if you want to handle negative numbers. /// </summary> /// <example> @@ -490,7 +374,7 @@ namespace Godot /// with the components of the given <see cref="Vector2i"/>. /// This operation uses truncated division, which is often not desired /// as it does not work well with negative numbers. - /// Consider using <see cref="PosMod(Vector2i)"/> instead + /// Consider using <see cref="Mathf.PosMod(int, int)"/> instead /// if you want to handle negative numbers. /// </summary> /// <example> @@ -509,34 +393,6 @@ namespace Godot } /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector2i"/> - /// and the given <see langword="int"/>. - /// </summary> - /// <param name="vec">The vector to AND with.</param> - /// <param name="and">The integer to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector2i operator &(Vector2i vec, int and) - { - vec.x &= and; - vec.y &= and; - return vec; - } - - /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector2i"/> - /// and the given <see cref="Vector2i"/>. - /// </summary> - /// <param name="vec">The left vector to AND with.</param> - /// <param name="andv">The right vector to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector2i operator &(Vector2i vec, Vector2i andv) - { - vec.x &= andv.x; - vec.y &= andv.y; - return vec; - } - - /// <summary> /// Returns <see langword="true"/> if the vectors are equal. /// </summary> /// <param name="left">The left vector.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 031464dcc6..b017ba5853 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -396,23 +396,6 @@ namespace Godot } /// <summary> - /// Returns the result of the linear interpolation between - /// this vector and <paramref name="to"/> by the vector amount <paramref name="weight"/>. - /// </summary> - /// <param name="to">The destination vector for interpolation.</param> - /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> - /// <returns>The resulting vector of the interpolation.</returns> - public readonly Vector3 Lerp(Vector3 to, Vector3 weight) - { - return new Vector3 - ( - Mathf.Lerp(x, to.x, weight.x), - Mathf.Lerp(y, to.y, weight.y), - Mathf.Lerp(z, to.z, weight.z) - ); - } - - /// <summary> /// Returns the vector with a maximum length by limiting its length to <paramref name="length"/>. /// </summary> /// <param name="length">The length to limit to.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index e631a9f443..de0c6d27e7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -129,39 +129,6 @@ namespace Godot } /// <summary> - /// Returns the squared distance between this vector and <paramref name="to"/>. - /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if - /// you need to compare vectors or need the squared distance for some formula. - /// </summary> - /// <param name="to">The other vector to use.</param> - /// <returns>The squared distance between the two vectors.</returns> - public readonly int DistanceSquaredTo(Vector3i to) - { - return (to - this).LengthSquared(); - } - - /// <summary> - /// Returns the distance between this vector and <paramref name="to"/>. - /// </summary> - /// <seealso cref="DistanceSquaredTo(Vector3i)"/> - /// <param name="to">The other vector to use.</param> - /// <returns>The distance between the two vectors.</returns> - public readonly real_t DistanceTo(Vector3i to) - { - return (to - this).Length(); - } - - /// <summary> - /// Returns the dot product of this vector and <paramref name="with"/>. - /// </summary> - /// <param name="with">The other vector to use.</param> - /// <returns>The dot product of the two vectors.</returns> - public readonly int Dot(Vector3i with) - { - return x * with.x + y * with.y + z * with.z; - } - - /// <summary> /// Returns the length (magnitude) of this vector. /// </summary> /// <seealso cref="LengthSquared"/> @@ -211,40 +178,6 @@ namespace Godot } /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components - /// and <paramref name="mod"/>. - /// </summary> - /// <param name="mod">A value representing the divisor of the operation.</param> - /// <returns> - /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. - /// </returns> - public readonly Vector3i PosMod(int mod) - { - Vector3i v = this; - v.x = Mathf.PosMod(v.x, mod); - v.y = Mathf.PosMod(v.y, mod); - v.z = Mathf.PosMod(v.z, mod); - return v; - } - - /// <summary> - /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components - /// and <paramref name="modv"/>'s components. - /// </summary> - /// <param name="modv">A vector representing the divisors of the operation.</param> - /// <returns> - /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. - /// </returns> - public readonly Vector3i PosMod(Vector3i modv) - { - Vector3i v = this; - v.x = Mathf.PosMod(v.x, modv.x); - v.y = Mathf.PosMod(v.y, modv.y); - v.z = Mathf.PosMod(v.z, modv.z); - return v; - } - - /// <summary> /// Returns a vector with each component set to one or negative one, depending /// on the signs of this vector's components, or zero if the component is zero, /// by calling <see cref="Mathf.Sign(int)"/> on each component. @@ -455,7 +388,7 @@ namespace Godot /// with the components of the given <see langword="int"/>. /// This operation uses truncated division, which is often not desired /// as it does not work well with negative numbers. - /// Consider using <see cref="PosMod(int)"/> instead + /// Consider using <see cref="Mathf.PosMod(int, int)"/> instead /// if you want to handle negative numbers. /// </summary> /// <example> @@ -479,7 +412,7 @@ namespace Godot /// with the components of the given <see cref="Vector3i"/>. /// This operation uses truncated division, which is often not desired /// as it does not work well with negative numbers. - /// Consider using <see cref="PosMod(Vector3i)"/> instead + /// Consider using <see cref="Mathf.PosMod(int, int)"/> instead /// if you want to handle negative numbers. /// </summary> /// <example> @@ -499,36 +432,6 @@ namespace Godot } /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector3i"/> - /// and the given <see langword="int"/>. - /// </summary> - /// <param name="vec">The vector to AND with.</param> - /// <param name="and">The integer to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector3i operator &(Vector3i vec, int and) - { - vec.x &= and; - vec.y &= and; - vec.z &= and; - return vec; - } - - /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector3i"/> - /// and the given <see cref="Vector3i"/>. - /// </summary> - /// <param name="vec">The left vector to AND with.</param> - /// <param name="andv">The right vector to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector3i operator &(Vector3i vec, Vector3i andv) - { - vec.x &= andv.x; - vec.y &= andv.y; - vec.z &= andv.z; - return vec; - } - - /// <summary> /// Returns <see langword="true"/> if the vectors are equal. /// </summary> /// <param name="left">The left vector.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs index 8146991fd7..00ecc64856 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs @@ -433,38 +433,6 @@ namespace Godot } /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector4i"/> - /// and the given <see langword="int"/>. - /// </summary> - /// <param name="vec">The vector to AND with.</param> - /// <param name="and">The integer to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector4i operator &(Vector4i vec, int and) - { - vec.x &= and; - vec.y &= and; - vec.z &= and; - vec.w &= and; - return vec; - } - - /// <summary> - /// Performs a bitwise AND operation with this <see cref="Vector4i"/> - /// and the given <see cref="Vector4i"/>. - /// </summary> - /// <param name="vec">The left vector to AND with.</param> - /// <param name="andv">The right vector to AND with.</param> - /// <returns>The result of the bitwise AND.</returns> - public static Vector4i operator &(Vector4i vec, Vector4i andv) - { - vec.x &= andv.x; - vec.y &= andv.y; - vec.z &= andv.z; - vec.w &= andv.w; - return vec; - } - - /// <summary> /// Returns <see langword="true"/> if the vectors are equal. /// </summary> /// <param name="left">The left vector.</param> diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index 9b071ecc02..66e12a338a 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -250,14 +250,12 @@ ReplicationEditor::ReplicationEditor() { tree->add_child(drop_label); drop_label->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - tree->set_drag_forwarding_compat(this); + SET_DRAG_FORWARDING_CDU(tree, ReplicationEditor); } void ReplicationEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_config"), &ReplicationEditor::_update_config); ClassDB::bind_method(D_METHOD("_update_checked", "property", "column", "checked"), &ReplicationEditor::_update_checked); - ClassDB::bind_method("_can_drop_data_fw", &ReplicationEditor::_can_drop_data_fw); - ClassDB::bind_method("_drop_data_fw", &ReplicationEditor::_drop_data_fw); } bool ReplicationEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 7ed69a84d0..0aa54b69f9 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -199,10 +199,6 @@ void MultiplayerSpawner::_notification(int p_what) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E.key)); ERR_CONTINUE(!node); node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit)); - // This is unlikely, but might still crash the engine. - if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) { - node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready)); - } get_multiplayer()->object_configuration_remove(node, this); } tracked_nodes.clear(); @@ -244,11 +240,11 @@ void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_s if (!tracked_nodes.has(oid)) { tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id); p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT); - p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready).bind(p_node->get_instance_id()), CONNECT_ONE_SHOT); + _spawn_notify(p_node->get_instance_id()); } } -void MultiplayerSpawner::_node_ready(ObjectID p_id) { +void MultiplayerSpawner::_spawn_notify(ObjectID p_id) { get_multiplayer()->object_configuration_add(ObjectDB::get_instance(p_id), this); } diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index 8d401a6818..8a54140e32 100644 --- a/modules/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -77,7 +77,7 @@ private: void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID); void _node_added(Node *p_node); void _node_exit(ObjectID p_id); - void _node_ready(ObjectID p_id); + void _spawn_notify(ObjectID p_id); Vector<String> _get_spawnable_scenes() const; void _set_spawnable_scenes(const Vector<String> &p_scenes); diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index e1b7b0c346..233ff76c7d 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -125,6 +125,20 @@ void SceneReplicationInterface::on_reset() { } void SceneReplicationInterface::on_network_process() { + // Prevent endless stalling in case of unforseen spawn errors. + if (spawn_queue.size()) { + ERR_PRINT("An error happened during last spawn, this usually means the 'ready' signal was not emitted by the spawned node."); + for (const ObjectID &oid : spawn_queue) { + Node *node = get_id_as<Node>(oid); + ERR_CONTINUE(!node); + if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready))) { + node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready)); + } + } + spawn_queue.clear(); + } + + // Process timed syncs. uint64_t msec = OS::get_singleton()->get_ticks_msec(); for (KeyValue<int, PeerInfo> &E : peers_info) { const HashSet<ObjectID> to_sync = E.value.sync_nodes; @@ -144,17 +158,39 @@ Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) { // Track node. const ObjectID oid = node->get_instance_id(); TrackedNode &tobj = _track(oid); + + // Spawn state needs to be callected after "ready", but the spawn order follows "enter_tree". ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE); tobj.spawner = spawner->get_instance_id(); - spawned_nodes.insert(oid); + spawn_queue.insert(oid); + node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &SceneReplicationInterface::_node_ready).bind(oid), Node::CONNECT_ONE_SHOT); + return OK; +} + +void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) { + ERR_FAIL_COND(!spawn_queue.has(p_oid)); // Bug. - if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { - if (tobj.net_id == 0) { - tobj.net_id = ++last_net_id; + // If we are a nested spawn, we need to wait until the parent is ready. + if (p_oid != *(spawn_queue.begin())) { + return; + } + + for (const ObjectID &oid : spawn_queue) { + ERR_CONTINUE(!tracked_nodes.has(oid)); + + TrackedNode &tobj = tracked_nodes[oid]; + MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tobj.spawner); + ERR_CONTINUE(!spawner); + + spawned_nodes.insert(oid); + if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) { + if (tobj.net_id == 0) { + tobj.net_id = ++last_net_id; + } + _update_spawn_visibility(0, oid); } - _update_spawn_visibility(0, oid); } - return OK; + spawn_queue.clear(); } Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) { diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index a5e610cff6..cf45db2138 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -51,7 +51,6 @@ private: bool operator==(const ObjectID &p_other) { return id == p_other; } - _FORCE_INLINE_ MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; } TrackedNode() {} TrackedNode(const ObjectID &p_id) { id = p_id; } TrackedNode(const ObjectID &p_id, uint32_t p_net_id) { @@ -75,7 +74,10 @@ private: HashSet<ObjectID> spawned_nodes; HashSet<ObjectID> sync_nodes; - // Pending spawn information. + // Pending local spawn information (handles spawning nested nodes during ready). + HashSet<ObjectID> spawn_queue; + + // Pending remote spawn information. ObjectID pending_spawn; int pending_spawn_remote = 0; const uint8_t *pending_buffer = nullptr; @@ -89,6 +91,7 @@ private: TrackedNode &_track(const ObjectID &p_id); void _untrack(const ObjectID &p_id); + void _node_ready(const ObjectID &p_oid); void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_msec); Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len); diff --git a/modules/ogg/ogg_packet_sequence.cpp b/modules/ogg/ogg_packet_sequence.cpp index 0acaaf5fc9..d473f3b4a0 100644 --- a/modules/ogg/ogg_packet_sequence.cpp +++ b/modules/ogg/ogg_packet_sequence.cpp @@ -136,6 +136,8 @@ bool OggPacketSequencePlayback::next_ogg_packet(ogg_packet **p_packet) const { ERR_FAIL_COND_V(data_version != ogg_packet_sequence->data_version, false); ERR_FAIL_COND_V(ogg_packet_sequence->page_data.is_empty(), false); ERR_FAIL_COND_V(ogg_packet_sequence->page_granule_positions.is_empty(), false); + ERR_FAIL_COND_V(page_cursor >= ogg_packet_sequence->page_data.size(), false); + // Move on to the next page if need be. This happens first to help simplify seek logic. while (packet_cursor >= ogg_packet_sequence->page_data[page_cursor].size()) { packet_cursor = 0; diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index d556f475d2..6b8f140923 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -560,6 +560,12 @@ void OpenXRAPI::destroy_instance() { instance = XR_NULL_HANDLE; } enabled_extensions.clear(); + + if (graphics_extension != nullptr) { + unregister_extension_wrapper(graphics_extension); + memdelete(graphics_extension); + graphics_extension = nullptr; + } } bool OpenXRAPI::create_session() { @@ -1347,6 +1353,10 @@ void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_w registered_extension_wrappers.push_back(p_extension_wrapper); } +void OpenXRAPI::unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { + registered_extension_wrappers.erase(p_extension_wrapper); +} + void OpenXRAPI::register_extension_metadata() { for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { extension_wrapper->on_register_metadata(); diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5fb8de660e..52a1af5a09 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -312,6 +312,7 @@ public: void set_xr_interface(OpenXRInterface *p_xr_interface); static void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + static void unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); static void register_extension_metadata(); static void cleanup_extension_wrappers(); diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 4e2fe3dab5..1c4d53c43a 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -111,6 +111,10 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { ERR_FAIL_NULL(openxr_api); if (!openxr_api->initialize(Main::get_rendering_driver_name())) { + OS::get_singleton()->alert("OpenXR was requested but failed to start.\n" + "Please check if your HMD is connected.\n" + "When using Windows MR please note that WMR only has DirectX support, make sure SteamVR is your default OpenXR runtime.\n" + "Godot will start in normal mode.\n"); memdelete(openxr_api); openxr_api = nullptr; return; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index e52b87741e..79ca4a7024 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -423,11 +423,7 @@ bool TextServerAdvanced::_load_support_data(const String &p_filename) { return false; } uint64_t len = f->get_length(); -#ifdef GDEXTENSION PackedByteArray icu_data = f->get_buffer(len); -#else - PackedByteArray icu_data = f->_get_buffer(len); -#endif UErrorCode err = U_ZERO_ERROR; udata_setCommonData(icu_data.ptr(), &err); @@ -476,11 +472,7 @@ bool TextServerAdvanced::_save_support_data(const String &p_filename) const { PackedByteArray icu_data; icu_data.resize(U_ICUDATA_SIZE); memcpy(icu_data.ptrw(), U_ICUDATA_ENTRY_POINT, U_ICUDATA_SIZE); -#ifdef GDEXTENSION f->store_buffer(icu_data); -#else - f->_store_buffer(icu_data); -#endif return true; #else @@ -824,29 +816,17 @@ _FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_ // Could not find texture to fit, create one. int texsize = MAX(p_data->size.x * p_data->oversampling * 8, 256); -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(texsize); -#else texsize = next_power_of_2(texsize); -#endif if (p_msdf) { texsize = MIN(texsize, 2048); } else { texsize = MIN(texsize, 1024); } if (mw > texsize) { // Special case, adapt to it? -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(mw); -#else texsize = next_power_of_2(mw); -#endif } if (mh > texsize) { // Special case, adapt to it? -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(mh); -#else texsize = next_power_of_2(mh); -#endif } ShelfPackTexture tex = ShelfPackTexture(texsize, texsize); @@ -949,14 +929,14 @@ static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, con return 0; } -void TextServerAdvanced::_generateMTSDF_threaded(uint32_t y, void *p_td) const { +void TextServerAdvanced::_generateMTSDF_threaded(void *p_td, uint32_t p_y) { MSDFThreadData *td = static_cast<MSDFThreadData *>(p_td); msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape); - int row = td->shape->inverseYAxis ? td->output->height() - y - 1 : y; + int row = td->shape->inverseYAxis ? td->output->height() - p_y - 1 : p_y; for (int col = 0; col < td->output->width(); ++col) { - int x = (y % 2) ? td->output->width() - col - 1 : col; - msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, y + .5)); + int x = (p_y % 2) ? td->output->width() - col - 1 : col; + msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, p_y + .5)); msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p); td->distancePixelConversion->operator()(td->output->operator()(x, row), distance); } @@ -1026,14 +1006,8 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf( td.projection = &projection; td.distancePixelConversion = &distancePixelConversion; -#ifdef GDEXTENSION - for (int i = 0; i < h; i++) { - _generateMTSDF_threaded(i, &td); - } -#else - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &TextServerAdvanced::_generateMTSDF_threaded, &td, h, -1, true, SNAME("FontServerRasterizeMSDF")); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&TextServerAdvanced::_generateMTSDF_threaded, &td, h, -1, true, String("FontServerRasterizeMSDF")); WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); -#endif msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config); @@ -3680,6 +3654,7 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) { RID TextServerAdvanced::_create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) { _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V_MSG(p_direction == DIRECTION_INHERITED, RID(), "Invalid text direction."); ShapedTextDataAdvanced *sd = memnew(ShapedTextDataAdvanced); sd->hb_buffer = hb_buffer_create(); @@ -3705,6 +3680,7 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction."); ERR_FAIL_COND(!sd); MutexLock lock(sd->mutex); @@ -3764,8 +3740,12 @@ void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, con } sd->bidi_override.clear(); for (int i = 0; i < p_override.size(); i++) { - if (p_override[i].get_type() == Variant::VECTOR2I) { - sd->bidi_override.push_back(p_override[i]); + if (p_override[i].get_type() == Variant::VECTOR3I) { + const Vector3i &r = p_override[i]; + sd->bidi_override.push_back(r); + } else if (p_override[i].get_type() == Variant::VECTOR2I) { + const Vector2i &r = p_override[i]; + sd->bidi_override.push_back(Vector3i(r.x, r.y, DIRECTION_INHERITED)); } } invalidate(sd, false); @@ -5570,8 +5550,31 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length())); } + int base_para_direction = UBIDI_DEFAULT_LTR; + switch (sd->direction) { + case DIRECTION_LTR: { + sd->para_direction = DIRECTION_LTR; + base_para_direction = UBIDI_LTR; + } break; + case DIRECTION_RTL: { + sd->para_direction = DIRECTION_RTL; + base_para_direction = UBIDI_RTL; + } break; + case DIRECTION_INHERITED: + case DIRECTION_AUTO: { + UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length()); + if (direction != UBIDI_NEUTRAL) { + sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR; + base_para_direction = direction; + } else { + sd->para_direction = DIRECTION_LTR; + base_para_direction = UBIDI_DEFAULT_LTR; + } + } break; + } + if (sd->bidi_override.is_empty()) { - sd->bidi_override.push_back(Vector2i(sd->start, sd->end)); + sd->bidi_override.push_back(Vector3i(sd->start, sd->end, DIRECTION_INHERITED)); } for (int ov = 0; ov < sd->bidi_override.size(); ov++) { @@ -5587,23 +5590,22 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { UBiDi *bidi_iter = ubidi_openSized(end, 0, &err); ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err)); - switch (sd->direction) { + switch (static_cast<TextServer::Direction>(sd->bidi_override[ov].z)) { case DIRECTION_LTR: { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); - sd->para_direction = DIRECTION_LTR; } break; case DIRECTION_RTL: { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); - sd->para_direction = DIRECTION_RTL; + } break; + case DIRECTION_INHERITED: { + ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); } break; case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); if (direction != UBIDI_NEUTRAL) { ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); - sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR; } else { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_DEFAULT_LTR, nullptr, &err); - sd->para_direction = DIRECTION_LTR; } } break; } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index e8a3a10ab8..c7fe46d554 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -72,7 +72,6 @@ #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> - #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -350,7 +349,7 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const; _FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data); - void _generateMTSDF_threaded(uint32_t y, void *p_td) const; + static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); _FORCE_INLINE_ Vector2i _get_size(const FontAdvanced *p_font_data, int p_size) const { if (p_font_data->msdf) { @@ -500,7 +499,7 @@ class TextServerAdvanced : public TextServerExtension { /* Intermediate data */ Char16String utf16; Vector<UBiDi *> bidi_iter; - Vector<Vector2i> bidi_override; + Vector<Vector3i> bidi_override; ScriptIterator *script_iter = nullptr; hb_buffer_t *hb_buffer = nullptr; diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp index 9354d3f9b3..1406e3aaa0 100644 --- a/modules/text_server_adv/thorvg_svg_in_ot.cpp +++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp @@ -88,24 +88,13 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin if (!gl_state.ready) { Ref<XMLParser> parser; parser.instantiate(); -#ifdef GDEXTENSION - PackedByteArray data; - data.resize(document->svg_document_length); - memcpy(data.ptrw(), document->svg_document, document->svg_document_length); - parser->open_buffer(data); -#else parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); -#endif float aspect = 1.0f; String xml_body; while (parser->read() == OK) { if (parser->has_attribute("id")) { -#ifdef GDEXTENSION const String &gl_name = parser->get_named_attribute_value("id"); -#else - const String &gl_name = parser->get_attribute_value("id"); -#endif if (gl_name.begins_with("glyph")) { int dot_pos = gl_name.find("."); int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); @@ -117,11 +106,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { if (parser->has_attribute("viewBox")) { -#ifdef GDEXTENSION PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); -#else - Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); -#endif if (vb.size() == 4) { aspect = vb[2].to_float() / vb[3].to_float(); @@ -129,19 +114,6 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } continue; } -#ifdef GDEXTENSION - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - xml_body = xml_body + "<" + parser->get_node_name(); - for (int i = 0; i < parser->get_attribute_count(); i++) { - xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; - } - xml_body = xml_body + ">"; - } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { - xml_body = xml_body + parser->get_node_data(); - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { - xml_body = xml_body + "</" + parser->get_node_name() + ">"; - } -#else if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { xml_body += vformat("<%s", parser->get_node_name()); for (int i = 0; i < parser->get_attribute_count(); i++) { @@ -153,7 +125,6 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { xml_body += vformat("</%s>", parser->get_node_name()); } -#endif } String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; @@ -175,11 +146,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin new_h = (new_w / aspect); } -#ifdef GDEXTENSION gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; -#else - gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; -#endif picture = tvg::Picture::gen(); result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 034f88e387..b5d7d3a3cf 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -51,7 +51,6 @@ using namespace godot; #include "core/error/error_macros.h" #include "core/string/print_string.h" #include "core/string/translation.h" -#include "core/string/ucaps.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -248,11 +247,7 @@ _FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_ // Could not find texture to fit, create one. int texsize = MAX(p_data->size.x * p_data->oversampling * 8, 256); -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(texsize); -#else texsize = next_power_of_2(texsize); -#endif if (p_msdf) { texsize = MIN(texsize, 2048); @@ -260,18 +255,10 @@ _FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_ texsize = MIN(texsize, 1024); } if (mw > texsize) { // Special case, adapt to it? -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(mw); -#else texsize = next_power_of_2(mw); -#endif } if (mh > texsize) { // Special case, adapt to it? -#ifdef GDEXTENSION - texsize = Math::next_power_of_2(mh); -#else texsize = next_power_of_2(mh); -#endif } ShelfPackTexture tex = ShelfPackTexture(texsize, texsize); @@ -374,14 +361,14 @@ static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, con return 0; } -void TextServerFallback::_generateMTSDF_threaded(uint32_t y, void *p_td) const { +void TextServerFallback::_generateMTSDF_threaded(void *p_td, uint32_t p_y) { MSDFThreadData *td = static_cast<MSDFThreadData *>(p_td); msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape); - int row = td->shape->inverseYAxis ? td->output->height() - y - 1 : y; + int row = td->shape->inverseYAxis ? td->output->height() - p_y - 1 : p_y; for (int col = 0; col < td->output->width(); ++col) { - int x = (y % 2) ? td->output->width() - col - 1 : col; - msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, y + .5)); + int x = (p_y % 2) ? td->output->width() - col - 1 : col; + msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, p_y + .5)); msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p); td->distancePixelConversion->operator()(td->output->operator()(x, row), distance); } @@ -451,14 +438,8 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf( td.projection = &projection; td.distancePixelConversion = &distancePixelConversion; -#ifdef GDEXTENSION - for (int i = 0; i < h; i++) { - _generateMTSDF_threaded(i, &td); - } -#else - WorkerThreadPool::GroupID group_id = WorkerThreadPool::get_singleton()->add_template_group_task(this, &TextServerFallback::_generateMTSDF_threaded, &td, h, -1, true, SNAME("TextServerFBRenderMSDF")); - WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_id); -#endif + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&TextServerFallback::_generateMTSDF_threaded, &td, h, -1, true, String("TextServerFBRenderMSDF")); + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config); @@ -2683,6 +2664,7 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) { RID TextServerFallback::_create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) { _THREAD_SAFE_METHOD_ + ERR_FAIL_COND_V_MSG(p_direction == DIRECTION_INHERITED, RID(), "Invalid text direction."); ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback); sd->direction = p_direction; @@ -2706,6 +2688,7 @@ void TextServerFallback::_shaped_text_clear(const RID &p_shaped) { } void TextServerFallback::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) { + ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction."); if (p_direction == DIRECTION_RTL) { ERR_PRINT_ONCE("Right-to-left layout is not supported by this text server."); } @@ -4078,31 +4061,11 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha } String TextServerFallback::_string_to_upper(const String &p_string, const String &p_language) const { - String upper = p_string; - - for (int i = 0; i <= upper.length(); i++) { - const char32_t s = upper[i]; - const char32_t t = _find_upper(s); - if (s != t) { // avoid copy on write - upper[i] = t; - } - } - - return upper; + return p_string.to_upper(); } String TextServerFallback::_string_to_lower(const String &p_string, const String &p_language) const { - String lower = p_string; - - for (int i = 0; i <= lower.length(); i++) { - const char32_t s = lower[i]; - const char32_t t = _find_lower(s); - if (s != t) { // avoid copy on write - lower[i] = t; - } - } - - return lower; + return p_string.to_lower(); } PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_string, const String &p_language, int p_chars_per_line) const { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index b42f564534..9fb048a581 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -67,11 +67,11 @@ #include <godot_cpp/classes/image.hpp> #include <godot_cpp/classes/image_texture.hpp> #include <godot_cpp/classes/ref.hpp> +#include <godot_cpp/classes/worker_thread_pool.hpp> #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> -#include <godot_cpp/templates/thread_work_pool.hpp> #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -303,7 +303,7 @@ class TextServerFallback : public TextServerExtension { _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const; _FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data); - void _generateMTSDF_threaded(uint32_t y, void *p_td) const; + static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); _FORCE_INLINE_ Vector2i _get_size(const FontFallback *p_font_data, int p_size) const { if (p_font_data->msdf) { diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp index 9354d3f9b3..1406e3aaa0 100644 --- a/modules/text_server_fb/thorvg_svg_in_ot.cpp +++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp @@ -88,24 +88,13 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin if (!gl_state.ready) { Ref<XMLParser> parser; parser.instantiate(); -#ifdef GDEXTENSION - PackedByteArray data; - data.resize(document->svg_document_length); - memcpy(data.ptrw(), document->svg_document, document->svg_document_length); - parser->open_buffer(data); -#else parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); -#endif float aspect = 1.0f; String xml_body; while (parser->read() == OK) { if (parser->has_attribute("id")) { -#ifdef GDEXTENSION const String &gl_name = parser->get_named_attribute_value("id"); -#else - const String &gl_name = parser->get_attribute_value("id"); -#endif if (gl_name.begins_with("glyph")) { int dot_pos = gl_name.find("."); int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); @@ -117,11 +106,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { if (parser->has_attribute("viewBox")) { -#ifdef GDEXTENSION PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); -#else - Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); -#endif if (vb.size() == 4) { aspect = vb[2].to_float() / vb[3].to_float(); @@ -129,19 +114,6 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } continue; } -#ifdef GDEXTENSION - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - xml_body = xml_body + "<" + parser->get_node_name(); - for (int i = 0; i < parser->get_attribute_count(); i++) { - xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; - } - xml_body = xml_body + ">"; - } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { - xml_body = xml_body + parser->get_node_data(); - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { - xml_body = xml_body + "</" + parser->get_node_name() + ">"; - } -#else if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { xml_body += vformat("<%s", parser->get_node_name()); for (int i = 0; i < parser->get_attribute_count(); i++) { @@ -153,7 +125,6 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { xml_body += vformat("</%s>", parser->get_node_name()); } -#endif } String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; @@ -175,11 +146,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin new_h = (new_w / aspect); } -#ifdef GDEXTENSION gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; -#else - gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; -#endif picture = tvg::Picture::gen(); result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); |