summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/bmp/image_loader_bmp.cpp64
-rw-r--r--modules/bmp/image_loader_bmp.h19
-rw-r--r--modules/gdscript/editor/script_templates/RichTextEffect/default.gd17
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp25
-rw-r--r--modules/gdscript/gdscript_editor.cpp64
-rw-r--r--modules/gdscript/gdscript_parser.cpp53
-rw-r--r--modules/gdscript/gdscript_warning.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd12
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd48
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.out13
-rw-r--r--modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd36
-rw-r--r--modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out1
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/gltf_document.cpp8
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs253
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs116
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs3
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp2
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp3
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.h2
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml3
-rw-r--r--modules/openxr/editor/openxr_action_map_editor.cpp2
-rw-r--r--modules/openxr/openxr_api.cpp14
-rw-r--r--modules/openxr/openxr_api.h5
-rw-r--r--modules/openxr/openxr_interface.cpp21
-rw-r--r--modules/openxr/openxr_interface.h3
-rw-r--r--modules/squish/image_decompress_squish.cpp1
-rw-r--r--modules/text_server_adv/text_server_adv.cpp137
-rw-r--r--modules/text_server_adv/text_server_adv.h5
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--modules/vhacd/register_types.cpp28
-rw-r--r--modules/websocket/wsl_peer.cpp3
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml24
-rw-r--r--modules/webxr/godot_webxr.h4
-rw-r--r--modules/webxr/native/library_godot_webxr.js50
-rw-r--r--modules/webxr/native/webxr.externs.js16
-rw-r--r--modules/webxr/webxr_interface.cpp4
-rw-r--r--modules/webxr/webxr_interface.h3
-rw-r--r--modules/webxr/webxr_interface_js.cpp24
-rw-r--r--modules/webxr/webxr_interface_js.h4
56 files changed, 733 insertions, 457 deletions
diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp
index 5b451fbf6b..5605255367 100644
--- a/modules/bmp/image_loader_bmp.cpp
+++ b/modules/bmp/image_loader_bmp.cpp
@@ -32,6 +32,16 @@
#include "core/io/file_access_memory.h"
+static uint8_t get_mask_width(uint16_t mask) {
+ // Returns number of ones in the binary value of the parameter: mask.
+ // Uses a Simple pop_count.
+ uint8_t c = 0u;
+ for (; mask != 0u; mask &= mask - 1u) {
+ c++;
+ }
+ return c;
+}
+
Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
const uint8_t *p_buffer,
const uint8_t *p_color_buffer,
@@ -71,9 +81,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width)));
ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE,
vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height)));
-
- } else if (bits_per_pixel == 16) {
- ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported.");
}
// Image data (might be indexed)
@@ -96,7 +103,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
// The actual data traversal is determined by
// the data width in case of 8/4/2/1 bit images
- const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes;
+ const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes;
const uint8_t *line = p_buffer + (line_width * (height - 1));
const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset;
@@ -149,6 +156,34 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
index += 1;
line_ptr += 1;
} break;
+ case 16: {
+ uint16_t rgb = (static_cast<uint16_t>(line_ptr[1]) << 8) | line_ptr[0];
+ // A1R5G5B5/X1R5G5B5 => uint16_t
+ // [A/X]1R5G2 | G3B5 => uint8_t | uint8_t
+ uint8_t ba = (rgb & p_header.bmp_bitfield.alpha_mask) >> p_header.bmp_bitfield.alpha_offset; // Alpha 0b 1000 ...
+ uint8_t b0 = (rgb & p_header.bmp_bitfield.red_mask) >> p_header.bmp_bitfield.red_offset; // Red 0b 0111 1100 ...
+ uint8_t b1 = (rgb & p_header.bmp_bitfield.green_mask) >> p_header.bmp_bitfield.green_offset; // Green 0b 0000 0011 1110 ...
+ uint8_t b2 = (rgb & p_header.bmp_bitfield.blue_mask); // >> p_header.bmp_bitfield.blue_offset; // Blue 0b ... 0001 1111
+
+ // Next we apply some color scaling going from a variable value space to a 256 value space.
+ // This may be simplified some but left as is for legibility.
+ // float scaled_value = unscaled_value * byte_max_value / color_channel_maxium_value + rounding_offset;
+ float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f;
+ float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f;
+ float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f;
+ write_buffer[index + 0] = static_cast<uint8_t>(f0); // R
+ write_buffer[index + 1] = static_cast<uint8_t>(f1); // G
+ write_buffer[index + 2] = static_cast<uint8_t>(f2); // B
+
+ if (p_header.bmp_bitfield.alpha_mask_width > 0) {
+ write_buffer[index + 3] = ba * 0xFF; // Alpha value(Always true or false so no scaling)
+ } else {
+ write_buffer[index + 3] = 0xFF; // No Alpha channel, Show everything.
+ }
+
+ index += 4;
+ line_ptr += 2;
+ } break;
case 24: {
write_buffer[index + 2] = line_ptr[0];
write_buffer[index + 1] = line_ptr[1];
@@ -253,13 +288,32 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
bmp_header.bmp_info_header.bmp_important_colors = f->get_32();
switch (bmp_header.bmp_info_header.bmp_compression) {
+ case BI_BITFIELDS: {
+ bmp_header.bmp_bitfield.red_mask = f->get_32();
+ bmp_header.bmp_bitfield.green_mask = f->get_32();
+ bmp_header.bmp_bitfield.blue_mask = f->get_32();
+ bmp_header.bmp_bitfield.alpha_mask = f->get_32();
+
+ bmp_header.bmp_bitfield.red_mask_width = get_mask_width(bmp_header.bmp_bitfield.red_mask);
+ bmp_header.bmp_bitfield.green_mask_width = get_mask_width(bmp_header.bmp_bitfield.green_mask);
+ bmp_header.bmp_bitfield.blue_mask_width = get_mask_width(bmp_header.bmp_bitfield.blue_mask);
+ bmp_header.bmp_bitfield.alpha_mask_width = get_mask_width(bmp_header.bmp_bitfield.alpha_mask);
+
+ bmp_header.bmp_bitfield.alpha_offset = bmp_header.bmp_bitfield.red_mask_width + bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
+ bmp_header.bmp_bitfield.red_offset = bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
+ bmp_header.bmp_bitfield.green_offset = bmp_header.bmp_bitfield.blue_mask_width;
+
+ bmp_header.bmp_bitfield.red_max = (1 << bmp_header.bmp_bitfield.red_mask_width) - 1;
+ bmp_header.bmp_bitfield.green_max = (1 << bmp_header.bmp_bitfield.green_mask_width) - 1;
+ bmp_header.bmp_bitfield.blue_max = (1 << bmp_header.bmp_bitfield.blue_mask_width) - 1;
+ } break;
case BI_RLE8:
case BI_RLE4:
case BI_CMYKRLE8:
case BI_CMYKRLE4: {
// Stop parsing.
ERR_FAIL_V_MSG(ERR_UNAVAILABLE,
- vformat("Compressed BMP files are not supported: %s", f->get_path()));
+ vformat("RLE compressed BMP files are not yet supported: %s", f->get_path()));
} break;
}
// Don't rely on sizeof(bmp_file_header) as structure padding
diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h
index 08b9115c87..cb51d7f0de 100644
--- a/modules/bmp/image_loader_bmp.h
+++ b/modules/bmp/image_loader_bmp.h
@@ -74,6 +74,25 @@ protected:
uint32_t bmp_colors_used = 0;
uint32_t bmp_important_colors = 0;
} bmp_info_header;
+
+ struct bmp_bitfield_s {
+ uint16_t alpha_mask = 0x8000;
+ uint16_t red_mask = 0x7C00;
+ uint16_t green_mask = 0x03E0;
+ uint16_t blue_mask = 0x001F;
+ uint16_t alpha_mask_width = 1u;
+ uint16_t red_mask_width = 5u;
+ uint16_t green_mask_width = 5u;
+ uint16_t blue_mask_width = 5u;
+ uint8_t alpha_offset = 15u; // Used for bit shifting.
+ uint8_t red_offset = 10u; // Used for bit shifting.
+ uint8_t green_offset = 5u; // Used for bit shifting.
+ //uint8_t blue_offset = 0u; // Always LSB aligned no shifting needed.
+ //uint8_t alpha_max = 1u; // Always boolean or on, so no scaling needed.
+ uint8_t red_max = 32u; // Used for color space scaling.
+ uint8_t green_max = 32u; // Used for color space scaling.
+ uint8_t blue_max = 32u; // Used for color space scaling.
+ } bmp_bitfield;
};
static Error convert_to_image(Ref<Image> p_image,
diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd
new file mode 100644
index 0000000000..c79eeb91ec
--- /dev/null
+++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd
@@ -0,0 +1,17 @@
+# meta-description: Base template for rich text effects
+
+@tool
+class_name _CLASS_
+extends _BASE_
+
+
+# To use this effect:
+# - Enable BBCode on a RichTextLabel.
+# - Register this effect on the label.
+# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text.
+var bbcode := "_CLASS_"
+
+
+func _process_custom_fx(char_fx: CharFXTransform) -> bool:
+ var param: float = char_fx.env.get("param", 1.0)
+ return true
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index a2cab25ce8..aa91f8fe8d 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -479,7 +479,24 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
}
if (look_class->has_member(name)) {
resolve_class_member(look_class, name, id);
- base = look_class->get_member(name).get_datatype();
+ GDScriptParser::ClassNode::Member member = look_class->get_member(name);
+ GDScriptParser::DataType member_datatype = member.get_datatype();
+
+ switch (member.type) {
+ case GDScriptParser::ClassNode::Member::CLASS:
+ break; // OK.
+ case GDScriptParser::ClassNode::Member::CONSTANT:
+ if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::CLASS) {
+ push_error(vformat(R"(Constant "%s" is not a preloaded script or class.)", name), id);
+ return ERR_PARSE_ERROR;
+ }
+ break;
+ default:
+ push_error(vformat(R"(Cannot use %s "%s" in extends chain.)", member.get_type_name(), name), id);
+ return ERR_PARSE_ERROR;
+ }
+
+ base = member_datatype;
found = true;
break;
}
@@ -506,6 +523,9 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
if (!id_type.is_set()) {
push_error(vformat(R"(Could not find nested type "%s".)", id->name), id);
return ERR_PARSE_ERROR;
+ } else if (id_type.kind != GDScriptParser::DataType::SCRIPT && id_type.kind != GDScriptParser::DataType::CLASS) {
+ push_error(vformat(R"(Identifier "%s" is not a preloaded script or class.)", id->name), id);
+ return ERR_PARSE_ERROR;
}
base = id_type;
@@ -4546,7 +4566,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
result.set_container_element_type(elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
- if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) {
+ if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
if (CoreConstants::is_global_enum(p_property.class_name)) {
result = make_global_enum_type(p_property.class_name, StringName(), false);
result.is_constant = false;
@@ -4558,6 +4578,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
}
}
+ // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int.
}
}
return result;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index f3a86522ae..be33c7c591 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -54,6 +54,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("\" \"");
p_delimiters->push_back("' '");
p_delimiters->push_back("\"\"\" \"\"\"");
+ p_delimiters->push_back("''' '''");
}
bool GDScriptLanguage::is_using_templates() {
@@ -73,9 +74,11 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
.replace(": String", "")
.replace(": Array[String]", "")
.replace(": float", "")
+ .replace(": CharFXTransform", "")
.replace(":=", "=")
.replace(" -> String", "")
.replace(" -> int", "")
+ .replace(" -> bool", "")
.replace(" -> void", "");
}
@@ -578,29 +581,34 @@ static int _get_enum_constant_location(StringName p_class, StringName p_enum_con
// END LOCATION METHODS
-static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) {
- if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
- String enum_name = p_info.class_name;
- if (!enum_name.contains(".")) {
- return enum_name;
+static String _trim_parent_class(const String &p_class, const String &p_base_class) {
+ if (p_base_class.is_empty()) {
+ return p_class;
+ }
+ Vector<String> names = p_class.split(".", false, 1);
+ if (names.size() == 2) {
+ String first = names[0];
+ String rest = names[1];
+ if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) {
+ return rest;
}
- return enum_name.get_slice(".", 1);
}
+ return p_class;
+}
- String n = p_info.name;
- int idx = n.find(":");
- if (idx != -1) {
- return n.substr(idx + 1, n.length());
- }
+static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "") {
+ String class_name = p_info.class_name;
+ bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM;
+ // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int.
- if (p_info.type == Variant::OBJECT) {
- if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
- return p_info.hint_string;
- } else {
- return p_info.class_name.operator String();
+ if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) {
+ if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) {
+ return class_name;
}
- }
- if (p_info.type == Variant::NIL) {
+ return _trim_parent_class(class_name, p_base_class);
+ } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) {
+ return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]";
+ } else if (p_info.type == Variant::NIL) {
if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";
} else {
@@ -3001,26 +3009,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
arg = arg.substr(0, arg.find(":"));
}
method_hint += arg;
- if (use_type_hint && mi.arguments[i].type != Variant::NIL) {
- method_hint += ": ";
- if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) {
- method_hint += mi.arguments[i].class_name.operator String();
- } else {
- method_hint += Variant::get_type_name(mi.arguments[i].type);
- }
+ if (use_type_hint) {
+ method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name);
}
}
}
method_hint += ")";
- if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
- method_hint += " -> ";
- if (mi.return_val.type == Variant::NIL) {
- method_hint += "void";
- } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) {
- method_hint += mi.return_val.class_name.operator String();
- } else {
- method_hint += Variant::get_type_name(mi.return_val.type);
- }
+ if (use_type_hint) {
+ method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name);
}
method_hint += ":";
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index e2a37ab6e9..8a49398f1a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -1343,7 +1343,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
default_used = true;
} else {
if (default_used) {
- push_error("Cannot have a mandatory parameters after optional parameters.");
+ push_error("Cannot have mandatory parameters after optional parameters.");
continue;
}
}
@@ -1439,27 +1439,32 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ push_multiline(true);
+ advance();
// Arguments.
push_completion_call(annotation);
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
- if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
- push_multiline(true);
- int argument_index = 0;
- do {
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
- set_last_completion_call_arg(argument_index++);
- ExpressionNode *argument = parse_expression(false);
- if (argument == nullptr) {
- valid = false;
- continue;
- }
- annotation->arguments.push_back(argument);
- } while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
+ int argument_index = 0;
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
- }
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ set_last_completion_call_arg(argument_index++);
+ ExpressionNode *argument = parse_expression(false);
+ if (argument == nullptr) {
+ push_error("Expected expression as the annotation argument.");
+ valid = false;
+ continue;
+ }
+ annotation->arguments.push_back(argument);
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
pop_completion_call();
}
complete_extents(annotation);
@@ -1475,7 +1480,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
void GDScriptParser::clear_unused_annotations() {
for (const AnnotationNode *annotation : annotation_stack) {
- push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
+ push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);
}
annotation_stack.clear();
@@ -1817,7 +1822,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->list = parse_expression(false);
if (!n_for->list) {
- push_error(R"(Expected a list or range after "in".)");
+ push_error(R"(Expected iterable after "in".)");
}
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
@@ -2820,6 +2825,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
attribute->base = p_previous_operand;
+ if (current.is_node_name()) {
+ current.type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
complete_extents(attribute);
return attribute;
@@ -3856,7 +3864,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.to_string();
} else {
- push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
return false;
}
@@ -3901,8 +3909,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = enum_hint_string;
} break;
default:
- // TODO: Allow custom user resources.
- push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
break;
}
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index ef59a07f1a..0cb8e3a2af 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -183,7 +183,7 @@ String GDScriptWarning::get_message() const {
return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]);
}
case ONREADY_WITH_EXPORT: {
- return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)";
+ return R"("@onready" will set the default value after "@export" takes effect and will override it.)";
}
case WARNING_MAX:
break; // Can't happen, but silences warning
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 2ed444c7ad..07f9465516 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -337,7 +337,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
symbol.kind = lsp::SymbolKind::Variable;
symbol.name = parameter->identifier->name;
symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line);
- symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line);
+ symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column);
symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line);
symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column);
symbol.uri = uri;
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index acd75f039a..112db4df3a 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -46,7 +46,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() {
while (true) {
if (req_pos >= LSP_MAX_BUFFER_SIZE) {
req_pos = 0;
- ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big");
+ ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Response header too big");
}
Error err = connection->get_partial_data(&req_buf[req_pos], 1, read);
if (err != OK) {
@@ -237,6 +237,7 @@ void GDScriptLanguageProtocol::poll() {
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
while (E != clients.end()) {
Ref<LSPeer> peer = E->value;
+ peer->connection->poll();
StreamPeerTCP::Status status = peer->connection->get_status();
if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) {
on_client_disconnected(E->key);
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd
new file mode 100644
index 0000000000..72af099158
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd
@@ -0,0 +1,9 @@
+# GH-75870
+
+const A = 1
+
+class B extends A:
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out
new file mode 100644
index 0000000000..65d629a35b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Constant "A" is not a preloaded script or class.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd
new file mode 100644
index 0000000000..fe334f8cb7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd
@@ -0,0 +1,12 @@
+# GH-75870
+
+class A:
+ const X = 1
+
+const Y = A.X # A.X is now resolved.
+
+class B extends A.X:
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out
new file mode 100644
index 0000000000..951cfb1ea4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Identifier "X" is not a preloaded script or class.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd
new file mode 100644
index 0000000000..6574d4cf31
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd
@@ -0,0 +1,9 @@
+# GH-75870
+
+var A = 1
+
+class B extends A:
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out
new file mode 100644
index 0000000000..7b39af6979
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use variable "A" in extends chain.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out
index ff184f9f04..f861d52f2b 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out
@@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING
>> Line: 3
>> ONREADY_WITH_EXPORT
->> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.
+>> "@onready" will set the default value after "@export" takes effect and will override it.
warn
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd
new file mode 100644
index 0000000000..271a831732
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd
@@ -0,0 +1,4 @@
+@export_enum("A",, "B", "C") var a
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out
new file mode 100644
index 0000000000..70eee5b39f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Expected expression as the annotation argument.
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd
new file mode 100644
index 0000000000..13c89a0a09
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd
@@ -0,0 +1,48 @@
+extends Node
+
+@export_enum("A", "B", "C") var a0
+@export_enum("A", "B", "C",) var a1
+
+@export_enum(
+ "A",
+ "B",
+ "C"
+) var a2
+
+@export_enum(
+ "A",
+ "B",
+ "C",
+) var a3
+
+@export
+var a4: int
+
+@export()
+var a5: int
+
+@export() var a6: int
+@warning_ignore("onready_with_export") @onready @export var a7: int
+@warning_ignore("onready_with_export") @onready() @export() var a8: int
+
+@warning_ignore("onready_with_export")
+@onready
+@export
+var a9: int
+
+@warning_ignore("onready_with_export")
+@onready()
+@export()
+var a10: int
+
+@warning_ignore("onready_with_export")
+@onready()
+@export()
+
+var a11: int
+
+
+func test():
+ for property in get_property_list():
+ if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
+ print(property)
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out
new file mode 100644
index 0000000000..3af0436c53
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.out
@@ -0,0 +1,13 @@
+GDTEST_OK
+{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
+{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
+{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
+{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 }
+{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
+{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 }
diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd
new file mode 100644
index 0000000000..87f9479812
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd
@@ -0,0 +1,36 @@
+var dict = {}
+
+func test():
+ dict.if = 1
+ dict.elif = 1
+ dict.else = 1
+ dict.for = 1
+ dict.while = 1
+ dict.match = 1
+ dict.break = 1
+ dict.continue = 1
+ dict.pass = 1
+ dict.return = 1
+ dict.class = 1
+ dict.class_name = 1
+ dict.extends = 1
+ dict.is = 1
+ dict.in = 1
+ dict.as = 1
+ dict.self = 1
+ dict.signal = 1
+ dict.func = 1
+ dict.static = 1
+ dict.const = 1
+ dict.enum = 1
+ dict.var = 1
+ dict.breakpoint = 1
+ dict.preload = 1
+ dict.await = 1
+ dict.yield = 1
+ dict.assert = 1
+ dict.void = 1
+ dict.PI = 1
+ dict.TAU = 1
+ dict.INF = 1
+ dict.NAN = 1
diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
index 3b8d2cc701..5f6ec5904c 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp
@@ -34,9 +34,9 @@
#include "../gltf_document.h"
-#include "editor/editor_file_dialog.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
+#include "editor/gui/editor_file_dialog.h"
String SceneExporterGLTFPlugin::get_name() const {
return "ConvertGLTF2";
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index a736e36c6a..2efaaa7d4d 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -37,10 +37,10 @@
#include "editor_import_blend_runner.h"
#include "core/config/project_settings.h"
-#include "editor/editor_file_dialog.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/gui/editor_file_dialog.h"
#include "main/main.h"
#include "scene/gui/line_edit.h"
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index d93485a800..7087c30688 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -2820,7 +2820,13 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
if (j == 0) {
const Array &target_names = extras.has("targetNames") ? (Array)extras["targetNames"] : Array();
for (int k = 0; k < targets.size(); k++) {
- import_mesh->add_blend_shape(k < target_names.size() ? (String)target_names[k] : String("morph_") + itos(k));
+ String bs_name;
+ if (k < target_names.size() && ((String)target_names[k]).size() != 0) {
+ bs_name = (String)target_names[k];
+ } else {
+ bs_name = String("morph_") + itos(k);
+ }
+ import_mesh->add_blend_shape(bs_name);
}
}
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index bb5eb8e643..20f1aa357b 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -40,6 +40,8 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/label.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/separator.h"
#include "scene/main/window.h"
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 6c5c61acb9..b7e5b058c6 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -317,16 +317,6 @@ namespace GodotTools.Build
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
- try
- {
- // Make sure our packages are added to the fallback folder
- NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
- }
- catch (Exception e)
- {
- GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
- }
-
if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
return true; // Requested play from an external editor/IDE which already built the project
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
index cf1b84e37f..8fe7d3c2d7 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -34,16 +34,6 @@ namespace GodotTools.Build
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return; // No solution to build
- try
- {
- // Make sure our packages are added to the fallback folder
- NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
- }
- catch (Exception e)
- {
- GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
- }
-
if (!BuildManager.BuildProjectBlocking("Debug"))
return; // Build failed
@@ -62,16 +52,6 @@ namespace GodotTools.Build
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return; // No solution to build
- try
- {
- // Make sure our packages are added to the fallback folder
- NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
- }
- catch (Exception e)
- {
- GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
- }
-
if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true))
return; // Build failed
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
deleted file mode 100644
index fe309b8102..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
+++ /dev/null
@@ -1,253 +0,0 @@
-using System;
-using System.Globalization;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using System.Xml;
-using Godot;
-using GodotTools.Internals;
-using GodotTools.Shared;
-using Directory = GodotTools.Utils.Directory;
-using Environment = System.Environment;
-using File = GodotTools.Utils.File;
-
-namespace GodotTools.Build
-{
- public static class NuGetUtils
- {
- public const string GodotFallbackFolderName = "Godot Offline Packages";
-
- public static string GodotFallbackFolderPath
- => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder");
-
- /// <summary>
- /// Returns all the paths where the Godot.Offline.Config files can be found.
- /// Does not determine whether the returned files exist or not.
- /// </summary>
- private static string[] GetAllGodotNuGetConfigFilePaths()
- {
- // Where to find 'NuGet/config/Godot.Offline.Config':
- //
- // - Mono/.NETFramework (standalone NuGet):
- // Uses Environment.SpecialFolder.ApplicationData
- // - Windows: '%APPDATA%'
- // - Linux/macOS: '$HOME/.config'
- // - CoreCLR (dotnet CLI NuGet):
- // - Windows: '%APPDATA%'
- // - Linux/macOS: '$DOTNET_CLI_HOME/.nuget' otherwise '$HOME/.nuget'
-
- string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
-
- const string configFileName = "Godot.Offline.Config";
-
- if (Utils.OS.IsWindows)
- {
- // %APPDATA% for both
- return new[] { Path.Combine(applicationData, "NuGet", "config", configFileName) };
- }
-
- var paths = new string[2];
-
- // CoreCLR (dotnet CLI NuGet)
-
- string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME");
- if (!string.IsNullOrEmpty(dotnetCliHome))
- {
- paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "config", configFileName);
- }
- else
- {
- string home = Environment.GetEnvironmentVariable("HOME");
- if (string.IsNullOrEmpty(home))
- throw new InvalidOperationException("Required environment variable 'HOME' is not set.");
- paths[0] = Path.Combine(home, ".nuget", "NuGet", "config", configFileName);
- }
-
- // Mono/.NETFramework (standalone NuGet)
-
- // ApplicationData is $HOME/.config on Linux/macOS
- paths[1] = Path.Combine(applicationData, "NuGet", "config", configFileName);
-
- return paths;
- }
-
- // nupkg extraction
- //
- // Exclude: (NuGet.Client -> NuGet.Packaging.PackageHelper.ExcludePaths)
- // package/
- // _rels/
- // [Content_Types].xml
- //
- // Don't ignore files that begin with a dot (.)
- //
- // The nuspec is not lower case inside the nupkg but must be made lower case when extracted.
-
- /// <summary>
- /// Adds the specified fallback folder to the Godot.Offline.Config files,
- /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet.
- /// </summary>
- public static void AddFallbackFolderToGodotNuGetConfigs(string name, string path)
- {
- // Make sure the fallback folder exists to avoid error:
- // MSB4018: The "ResolvePackageAssets" task failed unexpectedly.
- System.IO.Directory.CreateDirectory(path);
-
- foreach (string nuGetConfigPath in GetAllGodotNuGetConfigFilePaths())
- {
- string defaultConfig = @$"<?xml version=""1.0"" encoding=""utf-8""?>
-<configuration>
- <fallbackPackageFolders>
- <add key=""{name}"" value=""{path}"" />
- </fallbackPackageFolders>
-</configuration>
-";
- System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath));
- System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM
- }
- }
-
- private static void AddPackageToFallbackFolder(string fallbackFolder,
- string nupkgPath, string packageId, string packageVersion)
- {
- // dotnet CLI provides no command for this, but we can do it manually.
- //
- // - The expected structure is as follows:
- // fallback_folder/
- // <package.name>/<version>/
- // <package.name>.<version>.nupkg
- // <package.name>.<version>.nupkg.sha512
- // <package.name>.nuspec
- // ... extracted nupkg files (check code for excluded files) ...
- //
- // - <package.name> and <version> must be in lower case.
- // - The sha512 of the nupkg is base64 encoded.
- // - We can get the nuspec from the nupkg which is a Zip file.
-
- string packageIdLower = packageId.ToLowerInvariant();
- string packageVersionLower = packageVersion.ToLowerInvariant();
-
- string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower);
- string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg");
- string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512");
- string nupkgMetadataDestPath = Path.Combine(destDir, ".nupkg.metadata");
-
- if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath))
- return; // Already added (for speed we don't check if every file is properly extracted)
-
- Directory.CreateDirectory(destDir);
-
- // Generate .nupkg.sha512 file
-
- byte[] hash = SHA512.HashData(File.ReadAllBytes(nupkgPath));
- string base64Hash = Convert.ToBase64String(hash);
- File.WriteAllText(nupkgSha512DestPath, base64Hash);
-
- // Generate .nupkg.metadata file
- // Spec: https://github.com/NuGet/Home/wiki/Nupkg-Metadata-File
-
- File.WriteAllText(nupkgMetadataDestPath, @$"{{
- ""version"": 2,
- ""contentHash"": ""{base64Hash}"",
- ""source"": null
-}}");
-
- // Extract nupkg
- ExtractNupkg(destDir, nupkgPath, packageId, packageVersion);
-
- // Copy .nupkg
- File.Copy(nupkgPath, nupkgDestPath);
- }
-
- private static readonly string[] NupkgExcludePaths =
- {
- "_rels/",
- "package/",
- "[Content_Types].xml"
- };
-
- private static void ExtractNupkg(string destDir, string nupkgPath, string packageId, string packageVersion)
- {
- // NOTE: Must use SimplifyGodotPath to make sure we don't extract files outside the destination directory.
-
- using (var archive = ZipFile.OpenRead(nupkgPath))
- {
- // Extract .nuspec manually as it needs to be in lower case
-
- var nuspecEntry = archive.GetEntry(packageId + ".nuspec");
-
- if (nuspecEntry == null)
- throw new InvalidOperationException(
- $"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file.");
-
- nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name
- .ToLowerInvariant().SimplifyGodotPath()));
-
- // Extract the other package files
-
- foreach (var entry in archive.Entries)
- {
- // NOTE: SimplifyGodotPath() removes trailing slash and backslash,
- // so we can't use the result to check if the entry is a directory.
-
- string entryFullName = entry.FullName.Replace('\\', '/');
-
- // Check if the file must be ignored
- if ( // Excluded files.
- NupkgExcludePaths.Any(e => entryFullName.StartsWith(e, StringComparison.OrdinalIgnoreCase)) ||
- // Nupkg hash files and nupkg metadata files on all directory.
- entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) ||
- entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) ||
- // Nuspec at root level. We already extracted it previously but in lower case.
- !entryFullName.Contains('/') && entryFullName.EndsWith(".nuspec"))
- {
- continue;
- }
-
- string entryFullNameSimplified = entryFullName.SimplifyGodotPath();
- string destFilePath = Path.Combine(destDir, entryFullNameSimplified);
- bool isDir = entryFullName.EndsWith("/");
-
- if (isDir)
- {
- Directory.CreateDirectory(destFilePath);
- }
- else
- {
- Directory.CreateDirectory(Path.GetDirectoryName(destFilePath));
- entry.ExtractToFile(destFilePath, overwrite: true);
- }
- }
- }
- }
-
- /// <summary>
- /// Copies and extracts all the Godot bundled packages to the Godot NuGet fallback folder.
- /// Does nothing if the packages were already copied.
- /// </summary>
- public static void AddBundledPackagesToFallbackFolder(string fallbackFolder)
- {
- GD.Print("Copying Godot Offline Packages...");
-
- string nupkgsLocation = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "nupkgs");
-
- void AddPackage(string packageId, string packageVersion)
- {
- string nupkgPath = Path.Combine(nupkgsLocation, $"{packageId}.{packageVersion}.nupkg");
- AddPackageToFallbackFolder(fallbackFolder, nupkgPath, packageId, packageVersion);
- }
-
- foreach (var (packageId, packageVersion) in PackagesToAdd)
- AddPackage(packageId, packageVersion);
- }
-
- private static readonly (string packageId, string packageVersion)[] PackagesToAdd =
- {
- ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
- ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
- ("GodotSharp", GeneratedGodotNupkgsVersions.GodotSharp),
- ("GodotSharpEditor", GeneratedGodotNupkgsVersions.GodotSharp),
- };
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index a284451a35..6837b617e0 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -21,10 +21,22 @@ namespace GodotTools.Export
private List<string> _tempFolders = new List<string>();
- public void RegisterExportSettings()
+ public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetExportOptions(EditorExportPlatform platform)
{
- // TODO: These would be better as export preset options, but that doesn't seem to be supported yet
- GlobalDef("dotnet/export/include_scripts_content", false);
+ return new Godot.Collections.Array<Godot.Collections.Dictionary>()
+ {
+ new Godot.Collections.Dictionary()
+ {
+ {
+ "option", new Godot.Collections.Dictionary()
+ {
+ { "name", "dotnet/include_scripts_content" },
+ { "type", (int)Variant.Type.Bool }
+ }
+ },
+ { "default_value", false }
+ }
+ };
}
private string _maybeLastExportError;
@@ -44,7 +56,7 @@ namespace GodotTools.Export
// TODO What if the source file is not part of the game's C# project
- bool includeScriptsContent = (bool)ProjectSettings.GetSetting("dotnet/export/include_scripts_content");
+ bool includeScriptsContent = (bool)GetOption("dotnet/include_scripts_content");
if (!includeScriptsContent)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
index 90d6eb960e..049cc58512 100644
--- a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
@@ -7,6 +7,7 @@ namespace GodotTools
VisualStudioForMac, // Mac-only
MonoDevelop,
VsCode,
- Rider
+ Rider,
+ CustomEditor
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index f50803af95..4e33b38ac2 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -25,6 +25,8 @@ namespace GodotTools
public static class Settings
{
public const string ExternalEditor = "dotnet/editor/external_editor";
+ public const string CustomExecPath = "dotnet/editor/custom_exec_path";
+ public const string CustomExecPathArgs = "dotnet/editor/custom_exec_path_args";
public const string VerbosityLevel = "dotnet/build/verbosity_level";
public const string NoConsoleLogging = "dotnet/build/no_console_logging";
public const string CreateBinaryLog = "dotnet/build/create_binary_log";
@@ -133,22 +135,6 @@ namespace GodotTools
}
break;
}
- case MenuOptions.SetupGodotNugetFallbackFolder:
- {
- try
- {
- string fallbackFolder = NuGetUtils.GodotFallbackFolderPath;
- NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
- fallbackFolder);
- NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder);
- }
- catch (Exception e)
- {
- ShowErrorDialog("Failed to setup Godot NuGet Offline Packages: " + e.Message);
- }
-
- break;
- }
default:
throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option");
}
@@ -168,7 +154,6 @@ namespace GodotTools
private enum MenuOptions
{
CreateSln,
- SetupGodotNugetFallbackFolder,
}
public void ShowErrorDialog(string message, string title = "Error")
@@ -202,6 +187,66 @@ namespace GodotTools
case ExternalEditorId.None:
// Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor.
return Error.Unavailable;
+ case ExternalEditorId.CustomEditor:
+ {
+ string file = ProjectSettings.GlobalizePath(script.ResourcePath);
+ var execCommand = _editorSettings.GetSetting(Settings.CustomExecPath).As<string>();
+ var execArgs = _editorSettings.GetSetting(Settings.CustomExecPathArgs).As<string>();
+ var args = new List<string>();
+ var from = 0;
+ var numChars = 0;
+ var insideQuotes = false;
+ var hasFileFlag = false;
+
+ execArgs = execArgs.ReplaceN("{line}", line.ToString());
+ execArgs = execArgs.ReplaceN("{col}", col.ToString());
+ execArgs = execArgs.StripEdges(true, true);
+ execArgs = execArgs.Replace("\\\\", "\\");
+
+ for (int i = 0; i < execArgs.Length; ++i)
+ {
+ if ((execArgs[i] == '"' && (i == 0 || execArgs[i - 1] != '\\')) && i != execArgs.Length - 1)
+ {
+ if (!insideQuotes)
+ {
+ from++;
+ }
+ insideQuotes = !insideQuotes;
+ }
+ else if ((execArgs[i] == ' ' && !insideQuotes) || i == execArgs.Length - 1)
+ {
+ if (i == execArgs.Length - 1 && !insideQuotes)
+ {
+ numChars++;
+ }
+
+ var arg = execArgs.Substr(from, numChars);
+ if (arg.Contains("{file}"))
+ {
+ hasFileFlag = true;
+ }
+
+ arg = arg.ReplaceN("{file}", file);
+ args.Add(arg);
+
+ from = i + 1;
+ numChars = 0;
+ }
+ else
+ {
+ numChars++;
+ }
+ }
+
+ if (!hasFileFlag)
+ {
+ args.Add(file);
+ }
+
+ OS.RunProcess(execCommand, args);
+
+ break;
+ }
case ExternalEditorId.VisualStudio:
{
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
@@ -480,6 +525,8 @@ namespace GodotTools
// External editor settings
EditorDef(Settings.ExternalEditor, Variant.From(ExternalEditorId.None));
+ EditorDef(Settings.CustomExecPath, "");
+ EditorDef(Settings.CustomExecPathArgs, "");
EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal));
EditorDef(Settings.NoConsoleLogging, false);
EditorDef(Settings.CreateBinaryLog, false);
@@ -491,20 +538,23 @@ namespace GodotTools
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}";
+ $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsMacOS)
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}";
+ $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",Custom:{(int)ExternalEditorId.CustomEditor}";
}
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
- $",JetBrains Rider:{(int)ExternalEditorId.Rider}";
+ $",JetBrains Rider:{(int)ExternalEditorId.Rider}" +
+ $",Custom:{(int)ExternalEditorId.CustomEditor}";
}
_editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
@@ -515,6 +565,20 @@ namespace GodotTools
["hint_string"] = settingsHintStr
});
+ _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = (int)Variant.Type.String,
+ ["name"] = Settings.CustomExecPath,
+ ["hint"] = (int)PropertyHint.GlobalFile,
+ });
+
+ _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = (int)Variant.Type.String,
+ ["name"] = Settings.CustomExecPathArgs,
+ });
+ _editorSettings.SetInitialValue(Settings.CustomExecPathArgs, "{file}", false);
+
var verbosityLevels = Enum.GetValues<VerbosityLevelId>().Select(level => $"{Enum.GetName(level)}:{(int)level}");
_editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
@@ -530,20 +594,8 @@ namespace GodotTools
// Export plugin
var exportPlugin = new ExportPlugin();
AddExportPlugin(exportPlugin);
- exportPlugin.RegisterExportSettings();
_exportPluginWeak = WeakRef(exportPlugin);
- try
- {
- // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included
- NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName,
- NuGetUtils.GodotFallbackFolderPath);
- }
- catch (Exception e)
- {
- GD.PushError("Failed to add Godot NuGet Offline Packages to NuGet.Config: " + e.Message);
- }
-
BuildManager.Initialize();
RiderPathManager.Initialize();
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 83621ce5af..5d1a2277f9 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -70,6 +70,8 @@ namespace GodotTools.Ides
return "VisualStudioForMac";
case ExternalEditorId.MonoDevelop:
return "MonoDevelop";
+ case ExternalEditorId.CustomEditor:
+ return "CustomEditor";
default:
throw new NotImplementedException();
}
@@ -105,6 +107,7 @@ namespace GodotTools.Ides
case ExternalEditorId.VisualStudio:
case ExternalEditorId.VsCode:
case ExternalEditorId.Rider:
+ case ExternalEditorId.CustomEditor:
throw new NotSupportedException();
case ExternalEditorId.VisualStudioForMac:
goto case ExternalEditorId.MonoDevelop;
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index 9e542828ee..1f707f1192 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -36,9 +36,9 @@
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
#include "editor/property_selector.h"
-#include "editor/scene_tree_editor.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index 557d45b386..dd2c539c95 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -38,6 +38,9 @@
#include "editor/editor_node.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/label.h"
void NavigationMeshEditor::_node_removed(Node *p_node) {
if (p_node == node) {
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h
index 010be411d6..b73d8d2e69 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.h
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h
@@ -36,7 +36,9 @@
#include "editor/editor_plugin.h"
class AcceptDialog;
+class Button;
class HBoxContainer;
+class Label;
class NavigationRegion3D;
class NavigationMeshEditor : public Control {
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index 16a16a7f24..b6d52464c0 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -43,6 +43,9 @@
<member name="display_refresh_rate" type="float" setter="set_display_refresh_rate" getter="get_display_refresh_rate" default="0.0">
The display refresh rate for the current HMD. Only functional if this feature is supported by the OpenXR runtime and after the interface has been initialized.
</member>
+ <member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0">
+ The render size multiplier for the current HMD. Must be set before the interface has been initialized.
+ </member>
</members>
<signals>
<signal name="pose_recentered">
diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp
index ad5a515a01..64e07eff21 100644
--- a/modules/openxr/editor/openxr_action_map_editor.cpp
+++ b/modules/openxr/editor/openxr_action_map_editor.cpp
@@ -31,10 +31,10 @@
#include "openxr_action_map_editor.h"
#include "core/config/project_settings.h"
-#include "editor/editor_file_dialog.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/gui/editor_file_dialog.h"
// TODO implement redo/undo system
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 4b39a6295c..5879fbd460 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -1282,7 +1282,7 @@ XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunctio
if (result != XR_SUCCESS) {
String error_message = String("Symbol ") + p_name + " not found in OpenXR instance.";
- ERR_FAIL_COND_V_MSG(true, result, error_message.utf8().get_data());
+ ERR_FAIL_V_MSG(result, error_message.utf8().get_data());
}
return result;
@@ -1423,8 +1423,8 @@ Size2 OpenXRAPI::get_recommended_target_size() {
Size2 target_size;
- target_size.width = view_configuration_views[0].recommendedImageRectWidth;
- target_size.height = view_configuration_views[0].recommendedImageRectHeight;
+ target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier;
+ target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier;
return target_size;
}
@@ -1964,6 +1964,14 @@ Array OpenXRAPI::get_available_display_refresh_rates() const {
return Array();
}
+double OpenXRAPI::get_render_target_size_multiplier() const {
+ return render_target_size_multiplier;
+}
+
+void OpenXRAPI::set_render_target_size_multiplier(double multiplier) {
+ render_target_size_multiplier = multiplier;
+}
+
OpenXRAPI::OpenXRAPI() {
// OpenXRAPI is only constructed if OpenXR is enabled.
singleton = this;
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index c8bef5d420..292e54228d 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -120,6 +120,7 @@ private:
XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
bool running = false;
XrFrameState frame_state = { XR_TYPE_FRAME_STATE, NULL, 0, 0, false };
+ double render_target_size_multiplier = 1.0;
OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr;
XrSystemGraphicsProperties graphics_properties;
@@ -368,6 +369,10 @@ public:
void set_display_refresh_rate(float p_refresh_rate);
Array get_available_display_refresh_rates() const;
+ // Render Target size multiplier
+ double get_render_target_size_multiplier() const;
+ void set_render_target_size_multiplier(double multiplier);
+
// action map
String get_default_action_map_resource_name();
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 27344c9da7..933148da87 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -47,6 +47,11 @@ void OpenXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate");
+ // Render Target size multiplier
+ ClassDB::bind_method(D_METHOD("get_render_target_size_multiplier"), &OpenXRInterface::get_render_target_size_multiplier);
+ ClassDB::bind_method(D_METHOD("set_render_target_size_multiplier", "multiplier"), &OpenXRInterface::set_render_target_size_multiplier);
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_target_size_multiplier"), "set_render_target_size_multiplier", "get_render_target_size_multiplier");
+
ClassDB::bind_method(D_METHOD("is_action_set_active", "name"), &OpenXRInterface::is_action_set_active);
ClassDB::bind_method(D_METHOD("set_action_set_active", "name", "active"), &OpenXRInterface::set_action_set_active);
ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRInterface::get_action_sets);
@@ -668,6 +673,22 @@ Array OpenXRInterface::get_action_sets() const {
return arr;
}
+double OpenXRInterface::get_render_target_size_multiplier() const {
+ if (openxr_api == nullptr) {
+ return 1.0;
+ } else {
+ return openxr_api->get_render_target_size_multiplier();
+ }
+}
+
+void OpenXRInterface::set_render_target_size_multiplier(double multiplier) {
+ if (openxr_api == nullptr) {
+ return;
+ } else {
+ openxr_api->set_render_target_size_multiplier(multiplier);
+ }
+}
+
Size2 OpenXRInterface::get_render_target_size() {
if (openxr_api == nullptr) {
return Size2();
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index de758a8c2d..f36318530c 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -128,6 +128,9 @@ public:
void set_action_set_active(const String &p_action_set, bool p_active);
Array get_action_sets() const;
+ double get_render_target_size_multiplier() const;
+ void set_render_target_size_multiplier(double multiplier);
+
virtual Size2 get_render_target_size() override;
virtual uint32_t get_view_count() override;
virtual Transform3D get_camera_transform() override;
diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp
index 01be72a27e..5b35a2643a 100644
--- a/modules/squish/image_decompress_squish.cpp
+++ b/modules/squish/image_decompress_squish.cpp
@@ -58,7 +58,6 @@ void image_decompress_squish(Image *p_image) {
squish_flags = squish::kBc5;
} else {
ERR_FAIL_MSG("Squish: Can't decompress unknown format: " + itos(p_image->get_format()) + ".");
- return;
}
for (int i = 0; i <= mm_count; i++) {
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index c6a21aeefe..dfc820050f 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
+ new_sd->base_para_direction = sd->base_para_direction;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
@@ -4182,22 +4183,61 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
ERR_FAIL_COND_V_MSG((start < 0 || end - start > p_new_sd->utf16.length()), false, "Invalid BiDi override range.");
// Create temporary line bidi & shape.
- UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err);
- ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err));
- ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err);
- if (U_FAILURE(err)) {
- ubidi_close(bidi_iter);
- ERR_FAIL_V_MSG(false, u_errorName(err));
+ UBiDi *bidi_iter = nullptr;
+ if (p_sd->bidi_iter[ov]) {
+ bidi_iter = ubidi_openSized(end - start, 0, &err);
+ if (U_SUCCESS(err)) {
+ ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err);
+ if (U_FAILURE(err)) {
+ // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead.
+ err = U_ZERO_ERROR;
+ const UChar *data = p_sd->utf16.get_data();
+ switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) {
+ case DIRECTION_LTR: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err);
+ } break;
+ case DIRECTION_RTL: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
+ } break;
+ case DIRECTION_INHERITED: {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->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);
+ } else {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err);
+ }
+ } break;
+ }
+ if (U_FAILURE(err)) {
+ ubidi_close(bidi_iter);
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err)));
+ }
+ }
+ } else {
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi iterator allocation for the line failed: %s", u_errorName(err)));
+ }
}
p_new_sd->bidi_iter.push_back(bidi_iter);
err = U_ZERO_ERROR;
- int bidi_run_count = ubidi_countRuns(bidi_iter, &err);
- ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err));
+ int bidi_run_count = 1;
+ if (bidi_iter) {
+ bidi_run_count = ubidi_countRuns(bidi_iter, &err);
+ if (U_FAILURE(err)) {
+ ERR_PRINT(u_errorName(err));
+ }
+ }
for (int i = 0; i < bidi_run_count; i++) {
int32_t _bidi_run_start = 0;
- int32_t _bidi_run_length = 0;
- ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length);
+ int32_t _bidi_run_length = end - start;
+ if (bidi_iter) {
+ ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length);
+ }
int32_t bidi_run_start = _convert_pos(p_sd, ov_start + start + _bidi_run_start);
int32_t bidi_run_end = _convert_pos(p_sd, ov_start + start + _bidi_run_start + _bidi_run_length);
@@ -5604,25 +5644,25 @@ 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;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
switch (sd->direction) {
case DIRECTION_LTR: {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_LTR;
+ sd->base_para_direction = UBIDI_LTR;
} break;
case DIRECTION_RTL: {
sd->para_direction = DIRECTION_RTL;
- base_para_direction = UBIDI_RTL;
+ sd->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;
+ sd->base_para_direction = direction;
} else {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_DEFAULT_LTR;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
}
} break;
}
@@ -5642,38 +5682,53 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
UErrorCode err = U_ZERO_ERROR;
UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err);
- ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err));
-
- 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);
- } break;
- case DIRECTION_RTL: {
- ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
- } 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);
- } else {
- ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err);
- }
- } break;
+ if (U_SUCCESS(err)) {
+ 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);
+ } break;
+ case DIRECTION_RTL: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
+ } break;
+ case DIRECTION_INHERITED: {
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->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);
+ } else {
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err);
+ }
+ } break;
+ }
+ if (U_FAILURE(err)) {
+ ubidi_close(bidi_iter);
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi reordering for the paragraph failed: %s", u_errorName(err)));
+ }
+ } else {
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi iterator allocation for the paragraph failed: %s", u_errorName(err)));
}
- ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err));
sd->bidi_iter.push_back(bidi_iter);
err = U_ZERO_ERROR;
- int bidi_run_count = ubidi_countRuns(bidi_iter, &err);
- ERR_FAIL_COND_V_MSG(U_FAILURE(err), false, u_errorName(err));
+ int bidi_run_count = 1;
+ if (bidi_iter) {
+ bidi_run_count = ubidi_countRuns(bidi_iter, &err);
+ if (U_FAILURE(err)) {
+ ERR_PRINT(u_errorName(err));
+ }
+ }
for (int i = 0; i < bidi_run_count; i++) {
int32_t _bidi_run_start = 0;
- int32_t _bidi_run_length = 0;
+ int32_t _bidi_run_length = end - start;
+ bool is_rtl = false;
hb_direction_t bidi_run_direction = HB_DIRECTION_INVALID;
- bool is_rtl = (ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length) == UBIDI_LTR);
+ if (bidi_iter) {
+ is_rtl = (ubidi_getVisualRun(bidi_iter, i, &_bidi_run_start, &_bidi_run_length) == UBIDI_LTR);
+ }
switch (sd->orientation) {
case ORIENTATION_HORIZONTAL: {
if (is_rtl) {
@@ -5730,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
gl.start = span.start;
gl.end = span.end;
gl.count = 1;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index b959dd8544..4b8c3f7cd3 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
+ int base_para_direction = UBIDI_DEFAULT_LTR;
bool valid = false; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
@@ -515,7 +516,9 @@ class TextServerAdvanced : public TextServerExtension {
~ShapedTextDataAdvanced() {
for (int i = 0; i < bidi_iter.size(); i++) {
- ubidi_close(bidi_iter[i]);
+ if (bidi_iter[i]) {
+ ubidi_close(bidi_iter[i]);
+ }
}
if (script_iter) {
memdelete(script_iter);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 9095be46af..8b210bdc38 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
gl.end = span.end;
gl.count = 1;
gl.index = 0;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/modules/vhacd/register_types.cpp b/modules/vhacd/register_types.cpp
index 6310becf1b..088f03ebe8 100644
--- a/modules/vhacd/register_types.cpp
+++ b/modules/vhacd/register_types.cpp
@@ -32,22 +32,22 @@
#include "scene/resources/mesh.h"
#include "thirdparty/vhacd/public/VHACD.h"
-static Vector<Vector<Vector3>> convex_decompose(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const Mesh::ConvexDecompositionSettings &p_settings, Vector<Vector<uint32_t>> *r_convex_indices) {
+static Vector<Vector<Vector3>> convex_decompose(const real_t *p_vertices, int p_vertex_count, const uint32_t *p_triangles, int p_triangle_count, const Ref<MeshConvexDecompositionSettings> &p_settings, Vector<Vector<uint32_t>> *r_convex_indices) {
VHACD::IVHACD::Parameters params;
- params.m_concavity = p_settings.max_concavity;
- params.m_alpha = p_settings.symmetry_planes_clipping_bias;
- params.m_beta = p_settings.revolution_axes_clipping_bias;
- params.m_minVolumePerCH = p_settings.min_volume_per_convex_hull;
- params.m_resolution = p_settings.resolution;
- params.m_maxNumVerticesPerCH = p_settings.max_num_vertices_per_convex_hull;
- params.m_planeDownsampling = p_settings.plane_downsampling;
- params.m_convexhullDownsampling = p_settings.convexhull_downsampling;
- params.m_pca = p_settings.normalize_mesh;
- params.m_mode = p_settings.mode;
- params.m_convexhullApproximation = p_settings.convexhull_approximation;
+ params.m_concavity = p_settings->get_max_concavity();
+ params.m_alpha = p_settings->get_symmetry_planes_clipping_bias();
+ params.m_beta = p_settings->get_revolution_axes_clipping_bias();
+ params.m_minVolumePerCH = p_settings->get_min_volume_per_convex_hull();
+ params.m_resolution = p_settings->get_resolution();
+ params.m_maxNumVerticesPerCH = p_settings->get_max_num_vertices_per_convex_hull();
+ params.m_planeDownsampling = p_settings->get_plane_downsampling();
+ params.m_convexhullDownsampling = p_settings->get_convex_hull_downsampling();
+ params.m_pca = p_settings->get_normalize_mesh();
+ params.m_mode = p_settings->get_mode();
+ params.m_convexhullApproximation = p_settings->get_convex_hull_approximation();
params.m_oclAcceleration = true;
- params.m_maxConvexHulls = p_settings.max_convex_hulls;
- params.m_projectHullVertices = p_settings.project_hull_vertices;
+ params.m_maxConvexHulls = p_settings->get_max_convex_hulls();
+ params.m_projectHullVertices = p_settings->get_project_hull_vertices();
VHACD::IVHACD *decomposer = VHACD::CreateVHACD();
decomposer->Compute(p_vertices, p_vertex_count, p_triangles, p_triangle_count, params);
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 8a150c8561..816d5276b9 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -379,7 +379,6 @@ void WSLPeer::_do_client_handshake() {
// Header is too big
close(-1);
ERR_FAIL_MSG("Response headers too big.");
- return;
}
uint8_t byte;
@@ -402,7 +401,6 @@ void WSLPeer::_do_client_handshake() {
if (!_verify_server_response()) {
close(-1);
ERR_FAIL_MSG("Invalid response headers.");
- return;
}
wslay_event_context_client_init(&wsl_ctx, &_wsl_callbacks, this);
wslay_event_config_set_max_recv_msg_length(wsl_ctx, inbound_buffer_size);
@@ -469,7 +467,6 @@ bool WSLPeer::_verify_server_response() {
}
if (!valid) {
ERR_FAIL_V_MSG(false, "Received unrequested sub-protocol -> " + selected_protocol);
- return false;
}
}
return true;
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index 758c37e2ad..ed162b1da2 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -93,6 +93,18 @@
<link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link>
</tutorials>
<methods>
+ <method name="get_available_display_refresh_rates" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the web browser and after the interface has been initialized.
+ </description>
+ </method>
+ <method name="get_display_refresh_rate" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It may not report an accurate value until after using [method set_display_refresh_rate].
+ </description>
+ </method>
<method name="get_input_source_target_ray_mode" qualifiers="const">
<return type="int" enum="WebXRInterface.TargetRayMode" />
<param index="0" name="input_source_id" type="int" />
@@ -132,6 +144,13 @@
This method returns nothing, instead it emits the [signal session_supported] signal with the result.
</description>
</method>
+ <method name="set_display_refresh_rate">
+ <return type="void" />
+ <param index="0" name="refresh_rate" type="float" />
+ <description>
+ Sets the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It won't take effect right away until after [signal display_refresh_rate_changed] is emitted.
+ </description>
+ </method>
</methods>
<members>
<member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features">
@@ -167,6 +186,11 @@
</member>
</members>
<signals>
+ <signal name="display_refresh_rate_changed">
+ <description>
+ Emitted after the display's refresh rate has changed.
+ </description>
+ </signal>
<signal name="reference_space_reset">
<description>
Emitted to indicate that the reference space has been reset or reconfigured.
diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h
index c636be1576..554ca0398b 100644
--- a/modules/webxr/godot_webxr.h
+++ b/modules/webxr/godot_webxr.h
@@ -90,6 +90,10 @@ extern bool godot_webxr_update_input_source(
extern char *godot_webxr_get_visibility_state();
extern int godot_webxr_get_bounds_geometry(float **r_points);
+extern float godot_webxr_get_frame_rate();
+extern void godot_webxr_update_target_frame_rate(float p_frame_rate);
+extern int godot_webxr_get_supported_frame_rates(float **r_frame_rates);
+
#ifdef __cplusplus
}
#endif
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 5c01d88a30..fe47de02b0 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -42,6 +42,7 @@ const GodotWebXR = {
view_count: 1,
input_sources: new Array(16),
touches: new Array(5),
+ onsimpleevent: null,
// Monkey-patch the requestAnimationFrame() used by Emscripten for the main
// loop, so that we can swap it out for XRSession.requestAnimationFrame()
@@ -283,6 +284,9 @@ const GodotWebXR = {
GodotRuntime.free(c_str);
});
+ // Store onsimpleevent so we can use it later.
+ GodotWebXR.onsimpleevent = onsimpleevent;
+
const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
const gl = GL.getContext(gl_context_handle).GLctx;
GodotWebXR.gl = gl;
@@ -368,6 +372,7 @@ const GodotWebXR = {
GodotWebXR.view_count = 1;
GodotWebXR.input_sources = new Array(16);
GodotWebXR.touches = new Array(5);
+ GodotWebXR.onsimpleevent = null;
// Disable the monkey-patched window.requestAnimationFrame() and
// pause/restart the main loop to activate it on all platforms.
@@ -594,6 +599,51 @@ const GodotWebXR = {
return point_count;
},
+
+ godot_webxr_get_frame_rate__proxy: 'sync',
+ godot_webxr_get_frame_rate__sig: 'i',
+ godot_webxr_get_frame_rate: function () {
+ if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) {
+ return 0;
+ }
+ return GodotWebXR.session.frameRate;
+ },
+
+ godot_webxr_update_target_frame_rate__proxy: 'sync',
+ godot_webxr_update_target_frame_rate__sig: 'vi',
+ godot_webxr_update_target_frame_rate: function (p_frame_rate) {
+ if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) {
+ return;
+ }
+
+ GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => {
+ const c_str = GodotRuntime.allocString('display_refresh_rate_changed');
+ GodotWebXR.onsimpleevent(c_str);
+ GodotRuntime.free(c_str);
+ });
+ },
+
+ godot_webxr_get_supported_frame_rates__proxy: 'sync',
+ godot_webxr_get_supported_frame_rates__sig: 'ii',
+ godot_webxr_get_supported_frame_rates: function (r_frame_rates) {
+ if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) {
+ return 0;
+ }
+
+ const frame_rate_count = GodotWebXR.session.supportedFrameRates.length;
+ if (frame_rate_count === 0) {
+ return 0;
+ }
+
+ const buf = GodotRuntime.malloc(frame_rate_count * 4);
+ for (let i = 0; i < frame_rate_count; i++) {
+ GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float');
+ }
+ GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32');
+
+ return frame_rate_count;
+ },
+
};
autoAddDeps(GodotWebXR, '$GodotWebXR');
diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js
index 4b88820b19..7f7c297acc 100644
--- a/modules/webxr/native/webxr.externs.js
+++ b/modules/webxr/native/webxr.externs.js
@@ -68,6 +68,16 @@ XRSession.prototype.inputSources;
XRSession.prototype.visibilityState;
/**
+ * @type {?number}
+ */
+XRSession.prototype.frameRate;
+
+/**
+ * @type {?Float32Array}
+ */
+XRSession.prototype.supportedFrameRates;
+
+/**
* @type {?function (Event)}
*/
XRSession.prototype.onend;
@@ -142,6 +152,12 @@ XRSession.prototype.end = function () {};
XRSession.prototype.requestReferenceSpace = function (referenceSpaceType) {};
/**
+ * @param {number} rate
+ * @return {Promise<undefined>}
+ */
+XRSession.prototype.updateTargetFrameRate = function (rate) {};
+
+/**
* @typedef {function(number, XRFrame): undefined}
*/
var XRFrameRequestCallback;
diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp
index d7f4247768..5e30d0c996 100644
--- a/modules/webxr/webxr_interface.cpp
+++ b/modules/webxr/webxr_interface.cpp
@@ -46,6 +46,9 @@ void WebXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker);
ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode);
ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state);
+ ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &WebXRInterface::get_display_refresh_rate);
+ ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &WebXRInterface::set_display_refresh_rate);
+ ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &WebXRInterface::get_available_display_refresh_rates);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features");
@@ -68,6 +71,7 @@ void WebXRInterface::_bind_methods() {
ADD_SIGNAL(MethodInfo("visibility_state_changed"));
ADD_SIGNAL(MethodInfo("reference_space_reset"));
+ ADD_SIGNAL(MethodInfo("display_refresh_rate_changed"));
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN);
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE);
diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h
index f18b4b40e6..abaa8c01f8 100644
--- a/modules/webxr/webxr_interface.h
+++ b/modules/webxr/webxr_interface.h
@@ -66,6 +66,9 @@ public:
virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0;
virtual String get_visibility_state() const = 0;
+ virtual float get_display_refresh_rate() const = 0;
+ virtual void set_display_refresh_rate(float p_refresh_rate) = 0;
+ virtual Array get_available_display_refresh_rates() const = 0;
};
VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode);
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index d3710bd0df..e1df2ea94e 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -202,6 +202,30 @@ PackedVector3Array WebXRInterfaceJS::get_play_area() const {
return ret;
}
+float WebXRInterfaceJS::get_display_refresh_rate() const {
+ return godot_webxr_get_frame_rate();
+}
+
+void WebXRInterfaceJS::set_display_refresh_rate(float p_refresh_rate) {
+ godot_webxr_update_target_frame_rate(p_refresh_rate);
+}
+
+Array WebXRInterfaceJS::get_available_display_refresh_rates() const {
+ Array ret;
+
+ float *rates;
+ int rate_count = godot_webxr_get_supported_frame_rates(&rates);
+ if (rate_count > 0) {
+ ret.resize(rate_count);
+ for (int i = 0; i < rate_count; i++) {
+ ret[i] = rates[i];
+ }
+ free(rates);
+ }
+
+ return ret;
+}
+
StringName WebXRInterfaceJS::get_name() const {
return "WebXR";
};
diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h
index 20516e89e2..d85eb8cad7 100644
--- a/modules/webxr/webxr_interface_js.h
+++ b/modules/webxr/webxr_interface_js.h
@@ -102,6 +102,10 @@ public:
virtual String get_visibility_state() const override;
virtual PackedVector3Array get_play_area() const override;
+ virtual float get_display_refresh_rate() const override;
+ virtual void set_display_refresh_rate(float p_refresh_rate) override;
+ virtual Array get_available_display_refresh_rates() const override;
+
virtual StringName get_name() const override;
virtual uint32_t get_capabilities() const override;