summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct6
-rw-r--r--core/config/project_settings.cpp18
-rw-r--r--core/core_bind.h1
-rw-r--r--core/extension/gdextension_interface.cpp1
-rw-r--r--core/input/input.cpp2
-rw-r--r--core/input/input_event.cpp3
-rw-r--r--core/input/input_event.h5
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/input/input_map.h5
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/io/resource_format_binary.cpp5
-rw-r--r--core/io/translation_loader_po.cpp3
-rw-r--r--core/os/os.h1
-rw-r--r--core/string/translation_server.cpp35
-rw-r--r--core/string/translation_server.h2
-rw-r--r--core/string/ustring.cpp4
-rw-r--r--core/variant/variant.h13
-rw-r--r--core/variant/variant_op.cpp2
-rw-r--r--core/variant/variant_parser.cpp2
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/AtlasTexture.xml2
-rw-r--r--doc/classes/AudioEffectFilter.xml5
-rw-r--r--doc/classes/AudioEffectStereoEnhance.xml4
-rw-r--r--doc/classes/AudioStreamPlayback.xml48
-rw-r--r--doc/classes/CanvasItem.xml8
-rw-r--r--doc/classes/CanvasModulate.xml1
-rw-r--r--doc/classes/Control.xml10
-rw-r--r--doc/classes/DisplayServer.xml9
-rw-r--r--doc/classes/Light3D.xml3
-rw-r--r--doc/classes/LineEdit.xml3
-rw-r--r--doc/classes/Material.xml2
-rw-r--r--doc/classes/Node.xml6
-rw-r--r--doc/classes/ProjectSettings.xml5
-rw-r--r--doc/classes/RenderingServer.xml8
-rw-r--r--doc/classes/Shader.xml6
-rw-r--r--doc/classes/String.xml1
-rw-r--r--doc/classes/Transform2D.xml4
-rw-r--r--doc/classes/Tree.xml15
-rw-r--r--doc/classes/Window.xml22
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.cpp2
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp4
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp1
-rw-r--r--drivers/gles3/storage/light_storage.cpp17
-rw-r--r--drivers/gles3/storage/light_storage.h3
-rw-r--r--drivers/gles3/storage/texture_storage.cpp63
-rw-r--r--drivers/gles3/storage/texture_storage.h6
-rw-r--r--drivers/metal/metal_objects.h53
-rw-r--r--drivers/metal/metal_objects.mm130
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm6
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp43
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.h9
-rw-r--r--editor/add_metadata_dialog.cpp2
-rw-r--r--editor/debugger/editor_profiler.cpp1
-rw-r--r--editor/debugger/editor_visual_profiler.cpp1
-rw-r--r--editor/editor_data.cpp2
-rw-r--r--editor/editor_file_system.cpp46
-rw-r--r--editor/editor_file_system.h2
-rw-r--r--editor/editor_node.cpp8
-rw-r--r--editor/editor_properties.cpp1
-rw-r--r--editor/editor_resource_picker.cpp23
-rw-r--r--editor/editor_resource_picker.h5
-rw-r--r--editor/event_listener_line_edit.cpp2
-rw-r--r--editor/gui/editor_file_dialog.cpp7
-rw-r--r--editor/import_dock.cpp17
-rw-r--r--editor/input_event_configuration_dialog.cpp15
-rw-r--r--editor/plugins/control_editor_plugin.cpp1
-rw-r--r--editor/plugins/editor_preview_plugins.cpp1
-rw-r--r--editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp5
-rw-r--r--editor/plugins/script_editor_plugin.cpp50
-rw-r--r--editor/plugins/script_editor_plugin.h18
-rw-r--r--editor/plugins/shader_editor_plugin.cpp8
-rw-r--r--editor/plugins/shader_editor_plugin.h1
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp9
-rw-r--r--editor/project_manager/project_list.cpp2
-rw-r--r--editor/scene_tree_dock.cpp121
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp56
-rw-r--r--editor/themes/editor_theme_manager.h1
-rw-r--r--editor/window_wrapper.cpp7
-rw-r--r--editor/window_wrapper.h1
-rw-r--r--main/main.cpp8
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp1
-rw-r--r--modules/betsy/image_compress_betsy.cpp10
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml4
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript.cpp19
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp14
-rw-r--r--modules/gdscript/gdscript_editor.cpp6
-rw-r--r--modules/gdscript/gdscript_parser.cpp18
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp4
-rw-r--r--modules/gdscript/gdscript_vm.cpp22
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs4
-rw-r--r--modules/mono/editor/bindings_generator.cpp4
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp23
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.cpp123
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.h32
-rw-r--r--modules/openxr/doc_classes/OpenXRIPBinding.xml18
-rw-r--r--modules/openxr/editor/openxr_interaction_profile_editor.cpp41
-rw-r--r--modules/openxr/openxr_interface.cpp5
-rw-r--r--modules/openxr/scene/openxr_composition_layer.cpp4
-rw-r--r--modules/svg/image_loader_svg.cpp26
-rw-r--r--modules/tga/image_loader_tga.cpp1
-rw-r--r--modules/theora/video_stream_theora.cpp1
-rw-r--r--modules/tinyexr/image_saver_tinyexr.cpp1
-rw-r--r--modules/tinyexr/image_saver_tinyexr.h2
-rw-r--r--platform/android/detect.py4
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml2
-rw-r--r--platform/android/export/export_plugin.h1
-rw-r--r--platform/ios/detect.py2
-rw-r--r--platform/macos/export/export_plugin.h1
-rw-r--r--platform/web/js/libs/library_godot_fetch.js16
-rw-r--r--platform/windows/detect.py45
-rw-r--r--platform/windows/display_server_windows.cpp36
-rw-r--r--platform/windows/display_server_windows.h2
-rw-r--r--platform/windows/native_menu_windows.h1
-rw-r--r--scene/2d/path_2d.cpp9
-rw-r--r--scene/3d/light_3d.cpp15
-rw-r--r--scene/3d/light_3d.h4
-rw-r--r--scene/3d/lightmap_gi.cpp15
-rw-r--r--scene/gui/button.cpp17
-rw-r--r--scene/gui/color_picker.cpp39
-rw-r--r--scene/gui/color_picker.h1
-rw-r--r--scene/gui/control.cpp20
-rw-r--r--scene/gui/control.h9
-rw-r--r--scene/gui/menu_bar.cpp1
-rw-r--r--scene/gui/tree.cpp397
-rw-r--r--scene/gui/tree.h18
-rw-r--r--scene/main/canvas_item.cpp2
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/main/window.cpp20
-rw-r--r--scene/main/window.h10
-rw-r--r--scene/property_utils.cpp10
-rw-r--r--scene/resources/external_texture.cpp23
-rw-r--r--scene/resources/external_texture.h5
-rw-r--r--scene/resources/resource_format_text.cpp4
-rw-r--r--scene/resources/shader.cpp12
-rw-r--r--scene/resources/shader.h2
-rw-r--r--scene/scene_string_names.cpp2
-rw-r--r--scene/scene_string_names.h2
-rw-r--r--scene/theme/default_theme.cpp6
-rw-r--r--servers/audio/audio_stream.cpp43
-rw-r--r--servers/audio/audio_stream.h6
-rw-r--r--servers/display_server.cpp5
-rw-r--r--servers/display_server.h4
-rw-r--r--servers/movie_writer/movie_writer.h1
-rw-r--r--servers/rendering/dummy/storage/light_storage.h2
-rw-r--r--servers/rendering/renderer_rd/environment/fog.cpp80
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.h1
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp17
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp71
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.h5
-rw-r--r--servers/rendering/renderer_scene_cull.cpp16
-rw-r--r--servers/rendering/renderer_scene_cull.h1
-rw-r--r--servers/rendering/rendering_device.cpp50
-rw-r--r--servers/rendering/rendering_device.h7
-rw-r--r--servers/rendering/rendering_device_binds.cpp5
-rw-r--r--servers/rendering/rendering_device_driver.cpp2
-rw-r--r--servers/rendering/rendering_device_driver.h2
-rw-r--r--servers/rendering/rendering_device_graph.cpp18
-rw-r--r--servers/rendering/rendering_device_graph.h5
-rw-r--r--servers/rendering/rendering_server_default.cpp2
-rw-r--r--servers/rendering/rendering_server_default.h1
-rw-r--r--servers/rendering/shader_language.cpp129
-rw-r--r--servers/rendering/shader_language.h4
-rw-r--r--servers/rendering/shader_preprocessor.cpp42
-rw-r--r--servers/rendering/shader_preprocessor.h1
-rw-r--r--servers/rendering/shader_types.cpp2
-rw-r--r--servers/rendering/storage/light_storage.h2
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h1
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/core/string/test_string.h18
-rw-r--r--tests/core/variant/test_variant.h8
189 files changed, 2021 insertions, 753 deletions
diff --git a/SConstruct b/SConstruct
index 63cff4fe16..785cc0b1a3 100644
--- a/SConstruct
+++ b/SConstruct
@@ -799,8 +799,8 @@ if env["lto"] != "none":
# This needs to come after `configure`, otherwise we don't have env.msvc.
if not env.msvc:
# Specifying GNU extensions support explicitly, which are supported by
- # both GCC and Clang. Both currently default to gnu11 and gnu++17.
- env.Prepend(CFLAGS=["-std=gnu11"])
+ # both GCC and Clang. Both currently default to gnu17 and gnu++17.
+ env.Prepend(CFLAGS=["-std=gnu17"])
env.Prepend(CXXFLAGS=["-std=gnu++17"])
else:
# MSVC started offering C standard support with Visual Studio 2019 16.8, which covers all
@@ -809,7 +809,7 @@ else:
if cc_version_major < 16:
print_warning("Visual Studio 2017 cannot specify a C-Standard.")
else:
- env.Prepend(CFLAGS=["/std:c11"])
+ env.Prepend(CFLAGS=["/std:c17"])
# MSVC is non-conforming with the C++ standard by default, so we enable more conformance.
# Note that this is still not complete conformance, as certain Windows-related headers
# don't compile under complete conformance.
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 01f15f9c8e..b389e5a58e 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -494,6 +494,7 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f
}
void ProjectSettings::_convert_to_last_version(int p_from_version) {
+#ifndef DISABLE_DEPRECATED
if (p_from_version <= 3) {
// Converts the actions from array to dictionary (array of events to dictionary with deadzone + events)
for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) {
@@ -507,6 +508,22 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
}
}
}
+ if (p_from_version <= 5) {
+ // Converts the device in events from -1 (emulated events) to -3 (all events).
+ for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) {
+ if (String(E.key).begins_with("input/")) {
+ Dictionary action = E.value.variant;
+ Array events = action["events"];
+ for (int i = 0; i < events.size(); i++) {
+ Ref<InputEvent> ev = events[i];
+ if (ev.is_valid() && ev->get_device() == -1) { // -1 was the previous value (GH-97707).
+ ev->set_device(InputEvent::DEVICE_ID_ALL_DEVICES);
+ }
+ }
+ }
+ }
+ }
+#endif // DISABLE_DEPRECATED
}
/*
@@ -1460,6 +1477,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/transparent", false);
GLOBAL_DEF("display/window/size/extend_to_title", false);
GLOBAL_DEF("display/window/size/no_focus", false);
+ GLOBAL_DEF("display/window/size/sharp_corners", false);
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
diff --git a/core/core_bind.h b/core/core_bind.h
index ce0bde3c05..d59a2c55f1 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -32,7 +32,6 @@
#define CORE_BIND_H
#include "core/debugger/engine_profiler.h"
-#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 66b0161160..91f0b4a2de 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -34,6 +34,7 @@
#include "core/extension/gdextension.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/xml_parser.h"
#include "core/object/class_db.h"
#include "core/object/script_language_extension.h"
diff --git a/core/input/input.cpp b/core/input/input.cpp
index eba7ded267..6261a435fa 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -690,6 +690,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
button_event->set_canceled(st->is_canceled());
button_event->set_button_index(MouseButton::LEFT);
button_event->set_double_click(st->is_double_tap());
+ button_event->set_window_id(st->get_window_id());
BitField<MouseButtonMask> ev_bm = mouse_button_mask;
if (st->is_pressed()) {
@@ -727,6 +728,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
motion_event->set_velocity(sd->get_velocity());
motion_event->set_screen_velocity(sd->get_screen_velocity());
motion_event->set_button_mask(mouse_button_mask);
+ motion_event->set_window_id(sd->get_window_id());
_parse_input_event_impl(motion_event, true);
}
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 905526bbbd..d125bad252 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -35,9 +35,6 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
-const int InputEvent::DEVICE_ID_EMULATION = -1;
-const int InputEvent::DEVICE_ID_INTERNAL = -2;
-
void InputEvent::set_device(int p_device) {
device = p_device;
emit_changed();
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 19176f748e..80bca28fbf 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -62,8 +62,9 @@ protected:
static void _bind_methods();
public:
- static const int DEVICE_ID_EMULATION;
- static const int DEVICE_ID_INTERNAL;
+ inline static constexpr int DEVICE_ID_EMULATION = -1;
+ inline static constexpr int DEVICE_ID_INTERNAL = -2;
+ inline static constexpr int DEVICE_ID_ALL_DEVICES = -3; // Signify that a given Action can be triggered by any device.
void set_device(int p_device);
int get_device() const;
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 27a50c79f6..5b9377fe59 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -39,8 +39,6 @@
InputMap *InputMap::singleton = nullptr;
-int InputMap::ALL_DEVICES = -1;
-
void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action);
ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions);
@@ -163,7 +161,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re
int i = 0;
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
int device = E->get()->get_device();
- if (device == ALL_DEVICES || device == p_event->get_device()) {
+ if (device == InputEvent::DEVICE_ID_ALL_DEVICES || device == p_event->get_device()) {
if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) {
if (r_event_index) {
*r_event_index = i;
diff --git a/core/input/input_map.h b/core/input/input_map.h
index b29687d144..45798490f7 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -43,11 +43,6 @@ class InputMap : public Object {
GDCLASS(InputMap, Object);
public:
- /**
- * A special value used to signify that a given Action can be triggered by any device
- */
- static int ALL_DEVICES;
-
struct Action {
int id;
float deadzone;
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 664ff7857b..22219fca29 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -121,7 +121,7 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_
d.get_key_list(&keys);
if (p_sort_keys) {
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
}
bool first_key = true;
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 109999d612..ecbb9c0104 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -1268,6 +1268,11 @@ void ResourceFormatLoaderBinary::get_recognized_extensions_for_type(const String
return;
}
+ // res files not supported for GDExtension.
+ if (p_type == "GDExtension") {
+ return;
+ }
+
List<String> extensions;
ClassDB::get_extensions_for_type(p_type, &extensions);
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 578cd91c52..812fbc774e 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -31,7 +31,6 @@
#include "translation_loader_po.h"
#include "core/io/file_access.h"
-#include "core/string/translation.h"
#include "core/string/translation_po.h"
Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_error) {
@@ -361,7 +360,7 @@ void TranslationLoaderPO::get_recognized_extensions(List<String> *p_extensions)
}
bool TranslationLoaderPO::handles_type(const String &p_type) const {
- return (p_type == "Translation");
+ return (p_type == "Translation") || (p_type == "TranslationPO");
}
String TranslationLoaderPO::get_resource_type(const String &p_path) const {
diff --git a/core/os/os.h b/core/os/os.h
index 30d2a4266f..c42a39e0a4 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -32,7 +32,6 @@
#define OS_H
#include "core/config/engine.h"
-#include "core/io/image.h"
#include "core/io/logger.h"
#include "core/io/remote_filesystem_client.h"
#include "core/os/time_enums.h"
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 89b37d0b8a..92b473b61f 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -228,32 +228,41 @@ int TranslationServer::compare_locales(const String &p_locale_a, const String &p
return 10;
}
+ const String cache_key = p_locale_a + "|" + p_locale_b;
+ const int *cached_result = locale_compare_cache.getptr(cache_key);
+ if (cached_result) {
+ return *cached_result;
+ }
+
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
if (locale_a == locale_b) {
// Exact match.
+ locale_compare_cache.insert(cache_key, 10);
return 10;
}
Vector<String> locale_a_elements = locale_a.split("_");
Vector<String> locale_b_elements = locale_b.split("_");
- if (locale_a_elements[0] == locale_b_elements[0]) {
- // Matching language, both locales have extra parts.
- // Return number of matching elements.
- int matching_elements = 1;
- for (int i = 1; i < locale_a_elements.size(); i++) {
- for (int j = 1; j < locale_b_elements.size(); j++) {
- if (locale_a_elements[i] == locale_b_elements[j]) {
- matching_elements++;
- }
- }
- }
- return matching_elements;
- } else {
+ if (locale_a_elements[0] != locale_b_elements[0]) {
// No match.
+ locale_compare_cache.insert(cache_key, 0);
return 0;
}
+
+ // Matching language, both locales have extra parts.
+ // Return number of matching elements.
+ int matching_elements = 1;
+ for (int i = 1; i < locale_a_elements.size(); i++) {
+ for (int j = 1; j < locale_b_elements.size(); j++) {
+ if (locale_a_elements[i] == locale_b_elements[j]) {
+ matching_elements++;
+ }
+ }
+ }
+ locale_compare_cache.insert(cache_key, matching_elements);
+ return matching_elements;
}
String TranslationServer::get_locale_name(const String &p_locale) const {
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index a09230c019..2438349a69 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -46,6 +46,8 @@ class TranslationServer : public Object {
Ref<TranslationDomain> doc_domain;
HashMap<StringName, Ref<TranslationDomain>> custom_domains;
+ mutable HashMap<String, int> locale_compare_cache;
+
bool enabled = true;
static TranslationServer *singleton;
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index e6f7492a18..4e9eb922f6 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1850,6 +1850,8 @@ String String::num(double p_num, int p_decimals) {
}
String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
bool sign = p_num < 0;
int64_t n = p_num;
@@ -1888,6 +1890,8 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
}
String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
uint64_t n = p_num;
int chars = 0;
diff --git a/core/variant/variant.h b/core/variant/variant.h
index c76b849abd..3b1924e8ea 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -854,6 +854,19 @@ struct StringLikeVariantComparator {
static bool compare(const Variant &p_lhs, const Variant &p_rhs);
};
+struct StringLikeVariantOrder {
+ static _ALWAYS_INLINE_ bool compare(const Variant &p_lhs, const Variant &p_rhs) {
+ if (p_lhs.is_string() && p_rhs.is_string()) {
+ return p_lhs.operator String() < p_rhs.operator String();
+ }
+ return p_lhs < p_rhs;
+ }
+
+ _ALWAYS_INLINE_ bool operator()(const Variant &p_lhs, const Variant &p_rhs) const {
+ return compare(p_lhs, p_rhs);
+ }
+};
+
Variant::ObjData &Variant::_get_obj() {
return *reinterpret_cast<ObjData *>(&_data._mem[0]);
}
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index d2c1cde970..ce27fbdf67 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -980,6 +980,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInDictionaryHas<Color>>(Variant::OP_IN, Variant::COLOR, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<StringName>>(Variant::OP_IN, Variant::STRING_NAME, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<NodePath>>(Variant::OP_IN, Variant::NODE_PATH, Variant::DICTIONARY);
+ register_op<OperatorEvaluatorInDictionaryHas<::RID>>(Variant::OP_IN, Variant::RID, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHasObject>(Variant::OP_IN, Variant::OBJECT, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Callable>>(Variant::OP_IN, Variant::CALLABLE, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Signal>>(Variant::OP_IN, Variant::SIGNAL, Variant::DICTIONARY);
@@ -1021,6 +1022,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInArrayFind<Color, Array>>(Variant::OP_IN, Variant::COLOR, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<StringName, Array>>(Variant::OP_IN, Variant::STRING_NAME, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<NodePath, Array>>(Variant::OP_IN, Variant::NODE_PATH, Variant::ARRAY);
+ register_op<OperatorEvaluatorInArrayFind<::RID, Array>>(Variant::OP_IN, Variant::RID, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFindObject>(Variant::OP_IN, Variant::OBJECT, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Callable, Array>>(Variant::OP_IN, Variant::CALLABLE, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Signal, Array>>(Variant::OP_IN, Variant::SIGNAL, Variant::ARRAY);
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index f5f96456d3..f05b9cd83a 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -2245,7 +2245,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
} else {
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
if (keys.is_empty()) {
// Avoid unnecessary line break.
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 55d00b6cf9..66f15f7494 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -6,7 +6,7 @@
<description>
A list of global scope enumerated constants and built-in functions. This is all that resides in the globals, constants regarding error codes, keycodes, property hints, etc.
Singletons are also documented here, since they can be accessed from anywhere.
- For the entries related to GDScript which can be accessed in any script see [@GDScript].
+ For the entries that can only be accessed from scripts written in GDScript, see [@GDScript].
</description>
<tutorials>
<link title="Random number generation">$DOCS_URL/tutorials/math/random_number_generation.html</link>
diff --git a/doc/classes/AtlasTexture.xml b/doc/classes/AtlasTexture.xml
index 45877f4003..287f2cc19a 100644
--- a/doc/classes/AtlasTexture.xml
+++ b/doc/classes/AtlasTexture.xml
@@ -6,7 +6,7 @@
<description>
[Texture2D] resource that draws only part of its [member atlas] texture, as defined by the [member region]. An additional [member margin] can also be set, which is useful for small adjustments.
Multiple [AtlasTexture] resources can be cropped from the same [member atlas]. Packing many smaller textures into a singular large texture helps to optimize video memory costs and render calls.
- [b]Note:[/b] [AtlasTexture] cannot be used in an [AnimatedTexture], and may not tile properly in nodes such as [TextureRect], when inside other [AtlasTexture] resources.
+ [b]Note:[/b] [AtlasTexture] cannot be used in an [AnimatedTexture], and will not tile properly in nodes such as [TextureRect] or [Sprite2D]. To tile an [AtlasTexture], modify its [member region] instead.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/AudioEffectFilter.xml b/doc/classes/AudioEffectFilter.xml
index e5c1f4ccf4..18540de736 100644
--- a/doc/classes/AudioEffectFilter.xml
+++ b/doc/classes/AudioEffectFilter.xml
@@ -14,6 +14,7 @@
Threshold frequency for the filter, in Hz.
</member>
<member name="db" type="int" setter="set_db" getter="get_db" enum="AudioEffectFilter.FilterDB" default="0">
+ Steepness of the cutoff curve in dB per octave, also known as the order of the filter. Higher orders have a more aggressive cutoff.
</member>
<member name="gain" type="float" setter="set_gain" getter="get_gain" default="1.0">
Gain amount of the frequencies after the filter.
@@ -24,12 +25,16 @@
</members>
<constants>
<constant name="FILTER_6DB" value="0" enum="FilterDB">
+ Cutting off at 6dB per octave.
</constant>
<constant name="FILTER_12DB" value="1" enum="FilterDB">
+ Cutting off at 12dB per octave.
</constant>
<constant name="FILTER_18DB" value="2" enum="FilterDB">
+ Cutting off at 18dB per octave.
</constant>
<constant name="FILTER_24DB" value="3" enum="FilterDB">
+ Cutting off at 24dB per octave.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioEffectStereoEnhance.xml b/doc/classes/AudioEffectStereoEnhance.xml
index f009bec5bb..459ae3ebc6 100644
--- a/doc/classes/AudioEffectStereoEnhance.xml
+++ b/doc/classes/AudioEffectStereoEnhance.xml
@@ -11,11 +11,13 @@
</tutorials>
<members>
<member name="pan_pullout" type="float" setter="set_pan_pullout" getter="get_pan_pullout" default="1.0">
- Values greater than 1.0 increase intensity of any panning on audio passing through this effect, whereas values less than 1.0 will decrease the panning intensity. A value of 0.0 will downmix audio to mono.
+ Amplifies the difference between stereo channels, increasing or decreasing existing panning. A value of 0.0 will downmix stereo to mono. Does not affect a mono signal.
</member>
<member name="surround" type="float" setter="set_surround" getter="get_surround" default="0.0">
+ Widens sound stage through phase shifting in conjunction with [member time_pullout_ms]. Just pans sound to the left channel if [member time_pullout_ms] is 0.
</member>
<member name="time_pullout_ms" type="float" setter="set_time_pullout" getter="get_time_pullout" default="0.0">
+ Widens sound stage through phase shifting in conjunction with [member surround]. Just delays the right channel if [member surround] is 0.
</member>
</members>
</class>
diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml
index 02f3407f79..f01406d0f1 100644
--- a/doc/classes/AudioStreamPlayback.xml
+++ b/doc/classes/AudioStreamPlayback.xml
@@ -79,12 +79,47 @@
Overridable method. Called whenever the audio stream is mixed if the playback is active and [method AudioServer.set_enable_tagging_used_audio_streams] has been set to [code]true[/code]. Editor plugins may use this method to "tag" the current position along the audio stream and display it in a preview.
</description>
</method>
+ <method name="get_loop_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the number of times the stream has looped.
+ </description>
+ </method>
+ <method name="get_playback_position" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the current position in the stream, in seconds.
+ </description>
+ </method>
<method name="get_sample_playback" qualifiers="const" experimental="">
<return type="AudioSamplePlayback" />
<description>
Returns the [AudioSamplePlayback] associated with this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
+ <method name="is_playing" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the stream is playing.
+ </description>
+ </method>
+ <method name="mix_audio">
+ <return type="PackedVector2Array" />
+ <param index="0" name="rate_scale" type="float" />
+ <param index="1" name="frames" type="int" />
+ <description>
+ Mixes up to [param frames] of audio from the stream from the current position, at a rate of [param rate_scale], advancing the stream.
+ Returns a [PackedVector2Array] where each element holds the left and right channel volume levels of each frame.
+ [b]Note:[/b] Can return fewer frames than requested, make sure to use the size of the return value.
+ </description>
+ </method>
+ <method name="seek">
+ <return type="void" />
+ <param index="0" name="time" type="float" default="0.0" />
+ <description>
+ Seeks the stream at the given [param time], in seconds.
+ </description>
+ </method>
<method name="set_sample_playback" experimental="">
<return type="void" />
<param index="0" name="playback_sample" type="AudioSamplePlayback" />
@@ -92,5 +127,18 @@
Associates [AudioSamplePlayback] to this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
+ <method name="start">
+ <return type="void" />
+ <param index="0" name="from_pos" type="float" default="0.0" />
+ <description>
+ Starts the stream from the given [param from_pos], in seconds.
+ </description>
+ </method>
+ <method name="stop">
+ <return type="void" />
+ <description>
+ Stops the stream.
+ </description>
+ </method>
</methods>
</class>
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index bc17c8b008..78e9c392db 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -533,9 +533,13 @@
</method>
<method name="make_canvas_position_local" qualifiers="const">
<return type="Vector2" />
- <param index="0" name="screen_point" type="Vector2" />
+ <param index="0" name="viewport_point" type="Vector2" />
<description>
- Assigns [param screen_point] as this node's new local transform.
+ Transforms [param viewport_point] from the viewport's coordinates to this node's local coordinates.
+ For the opposite operation, use [method get_global_transform_with_canvas].
+ [codeblock]
+ var viewport_point = get_global_transform_with_canvas() * local_point
+ [/codeblock]
</description>
</method>
<method name="make_input_local" qualifiers="const">
diff --git a/doc/classes/CanvasModulate.xml b/doc/classes/CanvasModulate.xml
index 7db0361020..43505498b3 100644
--- a/doc/classes/CanvasModulate.xml
+++ b/doc/classes/CanvasModulate.xml
@@ -7,6 +7,7 @@
[CanvasModulate] applies a color tint to all nodes on a canvas. Only one can be used to tint a canvas, but [CanvasLayer]s can be used to render things independently.
</description>
<tutorials>
+ <link title="2D lights and shadows">$DOCS_URL/tutorials/2d/2d_lights_and_shadows.html</link>
</tutorials>
<members>
<member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)" keywords="colour">
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 9d36bc657b..516b01bd7d 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1365,7 +1365,7 @@
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
Automatic layout direction, determined from the parent control layout direction.
</constant>
- <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">
+ <constant name="LAYOUT_DIRECTION_APPLICATION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale.
</constant>
<constant name="LAYOUT_DIRECTION_LTR" value="2" enum="LayoutDirection">
@@ -1374,6 +1374,14 @@
<constant name="LAYOUT_DIRECTION_RTL" value="3" enum="LayoutDirection">
Right-to-left layout direction.
</constant>
+ <constant name="LAYOUT_DIRECTION_SYSTEM_LOCALE" value="4" enum="LayoutDirection">
+ Automatic layout direction, determined from the system locale.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_MAX" value="5" enum="LayoutDirection">
+ Represents the size of the [enum LayoutDirection] enum.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection" deprecated="Use [constant LAYOUT_DIRECTION_APPLICATION_LOCALE] instead.">
+ </constant>
<constant name="TEXT_DIRECTION_INHERITED" value="3" enum="TextDirection">
Text writing direction is the same as layout direction.
</constant>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 37bf265d20..674aa148a3 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1683,6 +1683,7 @@
<param index="1" name="window_id" type="int" default="0" />
<description>
Sets window mode for the given window to [param mode]. See [enum WindowMode] for possible values and how each mode behaves.
+ [b]Note:[/b] On Android, setting it to [constant WINDOW_MODE_FULLSCREEN] or [constant WINDOW_MODE_EXCLUSIVE_FULLSCREEN] will enable immersive mode.
[b]Note:[/b] Setting the window to full screen forcibly sets the borderless flag to [code]true[/code], so make sure to set it back to [code]false[/code] when not wanted.
</description>
</method>
@@ -2058,6 +2059,7 @@
<constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode">
Full screen mode with full multi-window support.
Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Android:[/b] This enables immersive mode.
[b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
[b]On macOS:[/b] A new desktop is used to display the running project.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
@@ -2065,6 +2067,7 @@
<constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode">
A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
+ [b]On Android:[/b] This enables immersive mode.
[b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
[b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
[b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
@@ -2099,7 +2102,11 @@
<constant name="WINDOW_FLAG_MOUSE_PASSTHROUGH" value="7" enum="WindowFlags">
All mouse events are passed to the underlying window of the same application.
</constant>
- <constant name="WINDOW_FLAG_MAX" value="8" enum="WindowFlags">
+ <constant name="WINDOW_FLAG_SHARP_CORNERS" value="8" enum="WindowFlags">
+ Window style is overridden, forcing sharp corners.
+ [b]Note:[/b] This flag is implemented only on Windows (11).
+ </constant>
+ <constant name="WINDOW_FLAG_MAX" value="9" enum="WindowFlags">
Max value of the [enum WindowFlags].
</constant>
<constant name="WINDOW_EVENT_MOUSE_ENTER" value="0" enum="WindowEvent">
diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml
index bda5fb69de..966d0fdcb4 100644
--- a/doc/classes/Light3D.xml
+++ b/doc/classes/Light3D.xml
@@ -115,6 +115,9 @@
<member name="shadow_blur" type="float" setter="set_param" getter="get_param" default="1.0">
Blurs the edges of the shadow. Can be used to hide pixel artifacts in low-resolution shadow maps. A high value can impact performance, make shadows appear grainy and can cause other unwanted artifacts. Try to keep as near default as possible.
</member>
+ <member name="shadow_caster_mask" type="int" setter="set_shadow_caster_mask" getter="get_shadow_caster_mask" default="4294967295">
+ The light will only cast shadows using objects in the selected layers.
+ </member>
<member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false">
If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D].
</member>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index 3e0c328dcb..91c9072f73 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -33,6 +33,7 @@
- [kbd]Cmd + E[/kbd]: Same as [kbd]End[/kbd], move the caret to the end of the line
- [kbd]Cmd + Left Arrow[/kbd]: Same as [kbd]Home[/kbd], move the caret to the beginning of the line
- [kbd]Cmd + Right Arrow[/kbd]: Same as [kbd]End[/kbd], move the caret to the end of the line
+ [b]Note:[/b] Caret movement shortcuts listed above are not affected by [member shortcut_keys_enabled].
</description>
<tutorials>
</tutorials>
@@ -334,7 +335,7 @@
If [code]false[/code], it's impossible to select the text using mouse nor keyboard.
</member>
<member name="shortcut_keys_enabled" type="bool" setter="set_shortcut_keys_enabled" getter="is_shortcut_keys_enabled" default="true">
- If [code]false[/code], using shortcuts will be disabled.
+ If [code]true[/code], shortcut keys for context menu items are enabled, even if the context menu is disabled.
</member>
<member name="structured_text_bidi_override" type="int" setter="set_structured_text_bidi_override" getter="get_structured_text_bidi_override" enum="TextServer.StructuredTextParser" default="0">
Set BiDi algorithm override for the structured text.
diff --git a/doc/classes/Material.xml b/doc/classes/Material.xml
index 760773d5d9..94d12018ca 100644
--- a/doc/classes/Material.xml
+++ b/doc/classes/Material.xml
@@ -45,7 +45,7 @@
<method name="inspect_native_shader_code">
<return type="void" />
<description>
- Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code.
+ Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code. See also [method Shader.inspect_native_shader_code].
</description>
</method>
</methods>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 42753f7071..c07948b546 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -73,6 +73,7 @@
<description>
Called during the physics processing step of the main loop. Physics processing means that the frame rate is synced to the physics, i.e. the [param delta] variable should be constant. [param delta] is in seconds.
It is only called if physics processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_physics_process].
+ Processing happens in order of [member process_physics_priority], lower priority values are called first. Nodes with the same priority are processed in tree order, or top to bottom as seen in the editor (also known as pre-order traversal).
Corresponds to the [constant NOTIFICATION_PHYSICS_PROCESS] notification in [method Object._notification].
[b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not an orphan).
</description>
@@ -83,6 +84,7 @@
<description>
Called during the processing step of the main loop. Processing happens at every frame and as fast as possible, so the [param delta] time since the previous frame is not constant. [param delta] is in seconds.
It is only called if processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process].
+ Processing happens in order of [member process_priority], lower priority values are called first. Nodes with the same priority are processed in tree order, or top to bottom as seen in the editor (also known as pre-order traversal).
Corresponds to the [constant NOTIFICATION_PROCESS] notification in [method Object._notification].
[b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not an orphan).
</description>
@@ -1015,10 +1017,10 @@
The node's processing behavior (see [enum ProcessMode]). To check if the node can process in its current mode, use [method can_process].
</member>
<member name="process_physics_priority" type="int" setter="set_physics_process_priority" getter="get_physics_process_priority" default="0">
- Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process] or the internal version.
+ Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process], or [constant NOTIFICATION_INTERNAL_PHYSICS_PROCESS].
</member>
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0">
- The node's execution order of the process callbacks ([method _process], [method _physics_process], and internal processing). Nodes whose priority value is [i]lower[/i] call their process callbacks first, regardless of tree order.
+ The node's execution order of the process callbacks ([method _process], [constant NOTIFICATION_PROCESS], and [constant NOTIFICATION_INTERNAL_PROCESS]). Nodes whose priority value is [i]lower[/i] call their process callbacks first, regardless of tree order.
</member>
<member name="process_thread_group" type="int" setter="set_process_thread_group" getter="get_process_thread_group" enum="Node.ProcessThreadGroup" default="0">
Set the process thread group for this node (basically, whether it receives [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS], [method _process] or [method _physics_process] (and the internal versions) on the main thread or in a sub-thread.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1684edb9b8..4f7ca2a62b 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -10,6 +10,7 @@
[b]Overriding:[/b] Any project setting can be overridden by creating a file named [code]override.cfg[/code] in the project's root directory. This can also be used in exported projects by placing this file in the same directory as the project binary. Overriding will still take the base project settings' [url=$DOCS_URL/tutorials/export/feature_tags.html]feature tags[/url] in account. Therefore, make sure to [i]also[/i] override the setting with the desired feature tags if you want them to override base project settings on all platforms and configurations.
</description>
<tutorials>
+ <link title="Project Settings">$DOCS_URL/tutorials/editor/project_settings.html</link>
<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/2747</link>
<link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/2748</link>
<link title="Operating System Testing Demo">https://godotengine.org/asset-library/asset/2789</link>
@@ -875,6 +876,10 @@
[b]Note:[/b] Certain window managers can be configured to ignore the non-resizable status of a window. Do not rely on this setting as a guarantee that the window will [i]never[/i] be resizable.
[b]Note:[/b] This setting is ignored on iOS.
</member>
+ <member name="display/window/size/sharp_corners" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], the main window uses sharp corners by default.
+ [b]Note:[/b] This property is implemented only on Windows (11).
+ </member>
<member name="display/window/size/transparent" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables a window manager hint that the main window background [i]can[/i] be transparent. This does not make the background actually transparent. For the background to be transparent, the root viewport must also be made transparent by enabling [member rendering/viewport/transparent_background].
[b]Note:[/b] To use a transparent splash screen, set [member application/boot_splash/bg_color] to [code]Color(0, 0, 0, 0)[/code].
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 66a69b7902..16a554eded 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2137,6 +2137,14 @@
If [code]true[/code], light will cast shadows. Equivalent to [member Light3D.shadow_enabled].
</description>
</method>
+ <method name="light_set_shadow_caster_mask">
+ <return type="void" />
+ <param index="0" name="light" type="RID" />
+ <param index="1" name="mask" type="int" />
+ <description>
+ Sets the shadow caster mask for this 3D light. Shadows will only be cast using objects in the selected layers. Equivalent to [member Light3D.shadow_caster_mask].
+ </description>
+ </method>
<method name="lightmap_create">
<return type="RID" />
<description>
diff --git a/doc/classes/Shader.xml b/doc/classes/Shader.xml
index 68176dea14..1e7f9e5ee5 100644
--- a/doc/classes/Shader.xml
+++ b/doc/classes/Shader.xml
@@ -35,6 +35,12 @@
If argument [param get_groups] is true, parameter grouping hints will be provided.
</description>
</method>
+ <method name="inspect_native_shader_code">
+ <return type="void" />
+ <description>
+ Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code. See also [method Material.inspect_native_shader_code].
+ </description>
+ </method>
<method name="set_default_texture_parameter">
<return type="void" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 40f08dafe6..588d0c73f9 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -6,6 +6,7 @@
<description>
This is the built-in string Variant type (and the one used by GDScript). Strings may contain any number of Unicode characters, and expose methods useful for manipulating and generating strings. Strings are reference-counted and use a copy-on-write approach (every modification to a string returns a new [String]), so passing them around is cheap in resources.
Some string methods have corresponding variations. Variations suffixed with [code]n[/code] ([method countn], [method findn], [method replacen], etc.) are [b]case-insensitive[/b] (they make no distinction between uppercase and lowercase letters). Method variations prefixed with [code]r[/code] ([method rfind], [method rsplit], etc.) are reversed, and start from the end of the string, instead of the beginning.
+ To convert any Variant to or from a string, see [method @GlobalScope.str], [method @GlobalScope.str_to_var], and [method @GlobalScope.var_to_str].
[b]Note:[/b] In a boolean context, a string will evaluate to [code]false[/code] if it is empty ([code]""[/code]). Otherwise, a string will always evaluate to [code]true[/code].
</description>
<tutorials>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index 4158fbe710..756716433e 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -251,14 +251,14 @@
</member>
<member name="y" type="Vector2" setter="" getter="" default="Vector2(0, 1)">
The transform basis's Y axis, and the column [code]1[/code] of the matrix. Combined with [member x], this represents the transform's rotation, scale, and skew.
- On the identity transform, this vector points up ([constant Vector2.UP]).
+ On the identity transform, this vector points down ([constant Vector2.DOWN]).
</member>
</members>
<constants>
<constant name="IDENTITY" value="Transform2D(1, 0, 0, 1, 0, 0)">
The identity [Transform2D]. A transform with no translation, no rotation, and its scale being [code]1[/code]. When multiplied by another [Variant] such as [Rect2] or another [Transform2D], no transformation occurs. This means that:
- The [member x] points right ([constant Vector2.RIGHT]);
- - The [member y] points up ([constant Vector2.UP]).
+ - The [member y] points down ([constant Vector2.DOWN]).
[codeblock]
var transform = Transform2D.IDENTITY
print("| X | Y | Origin")
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index b0cb25fafd..9510d237da 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -509,6 +509,12 @@
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]).
</theme_item>
+ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Text [Color] used when the item is hovered.
+ </theme_item>
+ <theme_item name="font_hovered_dimmed_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
+ Text [Color] used when the item is hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)">
The tint of text outline of the item.
</theme_item>
@@ -645,6 +651,9 @@
<theme_item name="updown" data_type="icon" type="Texture2D">
The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell.
</theme_item>
+ <theme_item name="button_hover" data_type="style" type="StyleBox">
+ [StyleBox] used when a button in the tree is hovered.
+ </theme_item>
<theme_item name="button_pressed" data_type="style" type="StyleBox">
[StyleBox] used when a button in the tree is pressed.
</theme_item>
@@ -666,6 +675,12 @@
<theme_item name="focus" data_type="style" type="StyleBox">
The focused style for the [Tree], drawn on top of everything.
</theme_item>
+ <theme_item name="hovered" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered.
+ </theme_item>
+ <theme_item name="hovered_dimmed" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="panel" data_type="style" type="StyleBox">
The background style for the [Tree].
</theme_item>
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index ca155881c8..02110f0162 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -659,6 +659,11 @@
If [member ProjectSettings.display/window/subwindows/embed_subwindows] is [code]false[/code], the position is in absolute screen coordinates. This typically applies to editor plugins. If the setting is [code]true[/code], the window's position is in the coordinates of its parent [Viewport].
[b]Note:[/b] This property only works if [member initial_position] is set to [constant WINDOW_INITIAL_POSITION_ABSOLUTE].
</member>
+ <member name="sharp_corners" type="bool" setter="set_flag" getter="get_flag" default="false">
+ If [code]true[/code], the [Window] will override the OS window style to display sharp corners.
+ [b]Note:[/b] This property is implemented only on Windows (11).
+ [b]Note:[/b] This property only works with native windows.
+ </member>
<member name="size" type="Vector2i" setter="set_size" getter="get_size" default="Vector2i(100, 100)">
The window's size in pixels.
</member>
@@ -842,7 +847,12 @@
All mouse events are passed to the underlying window of the same application.
[b]Note:[/b] This flag has no effect in embedded windows.
</constant>
- <constant name="FLAG_MAX" value="8" enum="Flags">
+ <constant name="FLAG_SHARP_CORNERS" value="8" enum="Flags">
+ Window style is overridden, forcing sharp corners.
+ [b]Note:[/b] This flag has no effect in embedded windows.
+ [b]Note:[/b] This flag is implemented only on Windows (11).
+ </constant>
+ <constant name="FLAG_MAX" value="9" enum="Flags">
Max value of the [enum Flags].
</constant>
<constant name="CONTENT_SCALE_MODE_DISABLED" value="0" enum="ContentScaleMode">
@@ -878,7 +888,7 @@
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
Automatic layout direction, determined from the parent window layout direction.
</constant>
- <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">
+ <constant name="LAYOUT_DIRECTION_APPLICATION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale.
</constant>
<constant name="LAYOUT_DIRECTION_LTR" value="2" enum="LayoutDirection">
@@ -887,6 +897,14 @@
<constant name="LAYOUT_DIRECTION_RTL" value="3" enum="LayoutDirection">
Right-to-left layout direction.
</constant>
+ <constant name="LAYOUT_DIRECTION_SYSTEM_LOCALE" value="4" enum="LayoutDirection">
+ Automatic layout direction, determined from the system locale.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_MAX" value="5" enum="LayoutDirection">
+ Represents the size of the [enum LayoutDirection] enum.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection" deprecated="Use [constant LAYOUT_DIRECTION_APPLICATION_LOCALE] instead.">
+ </constant>
<constant name="WINDOW_INITIAL_POSITION_ABSOLUTE" value="0" enum="WindowInitialPosition">
Initial window position is determined by [member position].
</constant>
diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp
index fd0adb1fd1..433bbfb3f5 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.cpp
+++ b/drivers/coreaudio/audio_driver_coreaudio.cpp
@@ -250,7 +250,7 @@ OSStatus AudioDriverCoreAudio::input_callback(void *inRefCon,
}
void AudioDriverCoreAudio::start() {
- if (!active) {
+ if (!active && audio_unit != nullptr) {
OSStatus result = AudioOutputUnitStart(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStart failed, code: " + itos(result));
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index 0ef88e7d52..479afbba93 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -2003,6 +2003,8 @@ static D3D12_BARRIER_LAYOUT _rd_texture_layout_to_d3d12_barrier_layout(RDD::Text
switch (p_texture_layout) {
case RDD::TEXTURE_LAYOUT_UNDEFINED:
return D3D12_BARRIER_LAYOUT_UNDEFINED;
+ case RDD::TEXTURE_LAYOUT_GENERAL:
+ return D3D12_BARRIER_LAYOUT_COMMON;
case RDD::TEXTURE_LAYOUT_STORAGE_OPTIMAL:
return D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS;
case RDD::TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
@@ -6175,6 +6177,8 @@ uint64_t RenderingDeviceDriverD3D12::api_trait_get(ApiTrait p_trait) {
return false;
case API_TRAIT_CLEARS_WITH_COPY_ENGINE:
return false;
+ case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
+ return true;
default:
return RenderingDeviceDriver::api_trait_get(p_trait);
}
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index 6e508c6ebf..843b6eac05 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "storage/texture_storage.h"
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index 9b976c2206..9b81430d45 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -213,6 +213,23 @@ void LightStorage::light_set_cull_mask(RID p_light, uint32_t p_mask) {
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
}
+void LightStorage::light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(light);
+
+ light->shadow_caster_mask = p_caster_mask;
+
+ light->version++;
+ light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
+}
+
+uint32_t LightStorage::light_get_shadow_caster_mask(RID p_light) const {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL_V(light, 0);
+
+ return light->shadow_caster_mask;
+}
+
void LightStorage::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_NULL(light);
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index ed00dd235f..5391e607c3 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -59,6 +59,7 @@ struct Light {
RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -327,6 +328,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {}
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index d7b4d6911d..5f49a84fe8 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -230,6 +230,32 @@ TextureStorage::TextureStorage() {
sdf_shader.shader_version = sdf_shader.shader.version_create();
}
+ // Initialize texture placeholder data for the `texture_*_placeholder_initialize()` methods.
+
+ constexpr int placeholder_size = 4;
+ texture_2d_placeholder = Image::create_empty(placeholder_size, placeholder_size, false, Image::FORMAT_RGBA8);
+ // Draw a magenta/black checkerboard pattern.
+ for (int i = 0; i < placeholder_size * placeholder_size; i++) {
+ const int x = i % placeholder_size;
+ const int y = i / placeholder_size;
+ texture_2d_placeholder->set_pixel(x, y, (x + y) % 2 == 0 ? Color(1, 0, 1) : Color(0, 0, 0));
+ }
+
+ texture_2d_array_placeholder.push_back(texture_2d_placeholder);
+
+ for (int i = 0; i < 6; i++) {
+ cubemap_placeholder.push_back(texture_2d_placeholder);
+ }
+
+ Ref<Image> texture_2d_placeholder_rotated;
+ texture_2d_placeholder_rotated.instantiate();
+ texture_2d_placeholder_rotated->copy_from(texture_2d_placeholder);
+ texture_2d_placeholder_rotated->rotate_90(CLOCKWISE);
+ for (int i = 0; i < 4; i++) {
+ // Alternate checkerboard pattern on odd layers (by using a copy that is rotated 90 degrees).
+ texture_3d_placeholder.push_back(i % 2 == 0 ? texture_2d_placeholder : texture_2d_placeholder_rotated);
+ }
+
#ifdef GL_API_ENABLED
if (RasterizerGLES3::is_gles_over_gl()) {
glEnable(GL_PROGRAM_POINT_SIZE);
@@ -1014,46 +1040,19 @@ void TextureStorage::texture_proxy_update(RID p_texture, RID p_proxy_to) {
}
void TextureStorage::texture_2d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- texture_2d_initialize(p_texture, image);
+ texture_2d_initialize(p_texture, texture_2d_placeholder);
}
-void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
+void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RS::TextureLayeredType p_layered_type) {
if (p_layered_type == RS::TEXTURE_LAYERED_2D_ARRAY) {
- images.push_back(image);
+ texture_2d_layered_initialize(p_texture, texture_2d_array_placeholder, p_layered_type);
} else {
- //cube
- for (int i = 0; i < 6; i++) {
- images.push_back(image);
- }
+ texture_2d_layered_initialize(p_texture, cubemap_placeholder, p_layered_type);
}
-
- texture_2d_layered_initialize(p_texture, images, p_layered_type);
}
void TextureStorage::texture_3d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
- //cube
- for (int i = 0; i < 4; i++) {
- images.push_back(image);
- }
-
- texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, images);
+ texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, texture_3d_placeholder);
}
Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 3786c8c690..d85d10e235 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -36,6 +36,7 @@
#include "platform_gl.h"
#include "config.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/templates/rid_owner.h"
#include "servers/rendering/renderer_compositor.h"
@@ -521,6 +522,11 @@ public:
virtual void texture_external_update(RID p_texture, int p_width, int p_height, uint64_t p_external_buffer) override;
virtual void texture_proxy_update(RID p_proxy, RID p_base) override;
+ Ref<Image> texture_2d_placeholder;
+ Vector<Ref<Image>> texture_2d_array_placeholder;
+ Vector<Ref<Image>> cubemap_placeholder;
+ Vector<Ref<Image>> texture_3d_placeholder;
+
//these two APIs can be used together or in combination with the others.
virtual void texture_2d_placeholder_initialize(RID p_texture) override;
virtual void texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) override;
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index 030b353ee8..38d5b53ffa 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -96,6 +96,22 @@ _FORCE_INLINE_ ShaderStageUsage &operator|=(ShaderStageUsage &p_a, int p_b) {
return p_a;
}
+enum StageResourceUsage : uint32_t {
+ VertexRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_VERTEX * 2),
+ VertexWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_VERTEX * 2),
+ FragmentRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_FRAGMENT * 2),
+ FragmentWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_FRAGMENT * 2),
+ TesselationControlRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationControlWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationEvaluationRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ TesselationEvaluationWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ ComputeRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_COMPUTE * 2),
+ ComputeWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_COMPUTE * 2),
+};
+
+typedef LocalVector<__unsafe_unretained id<MTLResource>> ResourceVector;
+typedef HashMap<StageResourceUsage, ResourceVector> ResourceUsageMap;
+
enum class MDCommandBufferStateType {
None,
Render,
@@ -230,6 +246,7 @@ public:
uint32_t index_offset = 0;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> vertex_offsets;
+ ResourceUsageMap resource_usage;
// clang-format off
enum DirtyFlag: uint8_t {
DIRTY_NONE = 0b0000'0000,
@@ -271,8 +288,14 @@ public:
blend_constants.reset();
vertex_buffers.clear();
vertex_offsets.clear();
+ // Keep the keys, as they are likely to be used again.
+ for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
+ kv.value.clear();
+ }
}
+ void end_encoding();
+
_FORCE_INLINE_ void mark_viewport_dirty() {
if (viewports.is_empty()) {
return;
@@ -356,13 +379,20 @@ public:
} render;
// State specific for a compute pass.
- struct {
+ struct ComputeState {
MDComputePipeline *pipeline = nullptr;
id<MTLComputeCommandEncoder> encoder = nil;
+ ResourceUsageMap resource_usage;
_FORCE_INLINE_ void reset() {
pipeline = nil;
encoder = nil;
+ // Keep the keys, as they are likely to be used again.
+ for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
+ kv.value.clear();
+ }
}
+
+ void end_encoding();
} compute;
// State specific to a blit pass.
@@ -632,19 +662,6 @@ public:
MDRenderShader(CharString p_name, Vector<UniformSet> p_sets, MDLibrary *p_vert, MDLibrary *p_frag);
};
-enum StageResourceUsage : uint32_t {
- VertexRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_VERTEX * 2),
- VertexWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_VERTEX * 2),
- FragmentRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_FRAGMENT * 2),
- FragmentWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_FRAGMENT * 2),
- TesselationControlRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
- TesselationControlWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
- TesselationEvaluationRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
- TesselationEvaluationWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
- ComputeRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_COMPUTE * 2),
- ComputeWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_COMPUTE * 2),
-};
-
_FORCE_INLINE_ StageResourceUsage &operator|=(StageResourceUsage &p_a, uint32_t p_b) {
p_a = StageResourceUsage(uint32_t(p_a) | p_b);
return p_a;
@@ -667,7 +684,13 @@ struct HashMapComparatorDefault<RDD::ShaderID> {
struct BoundUniformSet {
id<MTLBuffer> buffer;
- HashMap<id<MTLResource>, StageResourceUsage> bound_resources;
+ ResourceUsageMap usage_to_resources;
+
+ /// Perform a 2-way merge each key of `ResourceVector` resources from this set into the
+ /// destination set.
+ ///
+ /// Assumes the vectors of resources are sorted.
+ void merge_into(ResourceUsageMap &p_dst) const;
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDUniformSet {
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index 596728212a..c3906af159 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -58,7 +58,7 @@
void MDCommandBuffer::begin() {
DEV_ASSERT(commandBuffer == nil);
- commandBuffer = queue.commandBuffer;
+ commandBuffer = queue.commandBufferWithUnretainedReferences;
}
void MDCommandBuffer::end() {
@@ -390,6 +390,38 @@ void MDCommandBuffer::render_set_blend_constants(const Color &p_constants) {
}
}
+void BoundUniformSet::merge_into(ResourceUsageMap &p_dst) const {
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : usage_to_resources) {
+ ResourceVector *resources = p_dst.getptr(keyval.key);
+ if (resources == nullptr) {
+ resources = &p_dst.insert(keyval.key, ResourceVector())->value;
+ }
+ // Reserve space for the new resources, assuming they are all added.
+ resources->reserve(resources->size() + keyval.value.size());
+
+ uint32_t i = 0, j = 0;
+ __unsafe_unretained id<MTLResource> *resources_ptr = resources->ptr();
+ const __unsafe_unretained id<MTLResource> *keyval_ptr = keyval.value.ptr();
+ // 2-way merge.
+ while (i < resources->size() && j < keyval.value.size()) {
+ if (resources_ptr[i] < keyval_ptr[j]) {
+ i++;
+ } else if (resources_ptr[i] > keyval_ptr[j]) {
+ resources->insert(i, keyval_ptr[j]);
+ i++;
+ j++;
+ } else {
+ i++;
+ j++;
+ }
+ }
+ // Append the remaining resources.
+ for (; j < keyval.value.size(); j++) {
+ resources->push_back(keyval_ptr[j]);
+ }
+ }
+}
+
void MDCommandBuffer::_render_bind_uniform_sets() {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
if (!render.dirty.has_flag(RenderState::DIRTY_UNIFORMS)) {
@@ -408,7 +440,7 @@ void MDCommandBuffer::_render_bind_uniform_sets() {
// Find the index of the next set bit.
int index = __builtin_ctzll(set_uniforms);
// Clear the set bit.
- set_uniforms &= ~(1ULL << index);
+ set_uniforms &= (set_uniforms - 1);
MDUniformSet *set = render.uniform_sets[index];
if (set == nullptr || set->index >= (uint32_t)shader->sets.size()) {
continue;
@@ -416,17 +448,7 @@ void MDCommandBuffer::_render_bind_uniform_sets() {
UniformSet const &set_info = shader->sets[set->index];
BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
-
- for (KeyValue<id<MTLResource>, StageResourceUsage> const &keyval : bus.bound_resources) {
- MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_VERTEX);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage stages:MTLRenderStageVertex];
- }
- usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_FRAGMENT);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage stages:MTLRenderStageFragment];
- }
- }
+ bus.merge_into(render.resource_usage);
// Set the buffer for the vertex stage.
{
@@ -545,8 +567,7 @@ void MDCommandBuffer::_end_render_pass() {
// see: https://github.com/KhronosGroup/MoltenVK/blob/d20d13fe2735adb845636a81522df1b9d89c0fba/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm#L407
}
- [render.encoder endEncoding];
- render.encoder = nil;
+ render.end_encoding();
}
void MDCommandBuffer::_render_clear_render_area() {
@@ -792,10 +813,59 @@ void MDCommandBuffer::render_draw_indirect_count(RDD::BufferID p_indirect_buffer
ERR_FAIL_MSG("not implemented");
}
+void MDCommandBuffer::RenderState::end_encoding() {
+ if (encoder == nil) {
+ return;
+ }
+
+ // Bind all resources.
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : resource_usage) {
+ if (keyval.value.is_empty()) {
+ continue;
+ }
+
+ MTLResourceUsage vert_usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_VERTEX);
+ MTLResourceUsage frag_usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_FRAGMENT);
+ if (vert_usage == frag_usage) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:vert_usage stages:MTLRenderStageVertex | MTLRenderStageFragment];
+ } else {
+ if (vert_usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:vert_usage stages:MTLRenderStageVertex];
+ }
+ if (frag_usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:frag_usage stages:MTLRenderStageFragment];
+ }
+ }
+ }
+
+ [encoder endEncoding];
+ encoder = nil;
+}
+
+void MDCommandBuffer::ComputeState::end_encoding() {
+ if (encoder == nil) {
+ return;
+ }
+
+ // Bind all resources.
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : resource_usage) {
+ if (keyval.value.is_empty()) {
+ continue;
+ }
+ MTLResourceUsage usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_COMPUTE);
+ if (usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:usage];
+ }
+ }
+
+ [encoder endEncoding];
+ encoder = nil;
+}
+
void MDCommandBuffer::render_end_pass() {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
- [render.encoder endEncoding];
+ render.end_encoding();
render.reset();
type = MDCommandBufferStateType::None;
}
@@ -813,13 +883,7 @@ void MDCommandBuffer::compute_bind_uniform_set(RDD::UniformSetID p_uniform_set,
MDUniformSet *set = (MDUniformSet *)(p_uniform_set.id);
BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
-
- for (KeyValue<id<MTLResource>, StageResourceUsage> &keyval : bus.bound_resources) {
- MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_COMPUTE);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage];
- }
- }
+ bus.merge_into(compute.resource_usage);
uint32_t const *offset = set_info.offsets.getptr(RDD::SHADER_STAGE_COMPUTE);
if (offset) {
@@ -848,7 +912,7 @@ void MDCommandBuffer::compute_dispatch_indirect(RDD::BufferID p_indirect_buffer,
void MDCommandBuffer::_end_compute_dispatch() {
DEV_ASSERT(type == MDCommandBufferStateType::Compute);
- [compute.encoder endEncoding];
+ compute.end_encoding();
compute.reset();
type = MDCommandBufferStateType::None;
}
@@ -1052,7 +1116,20 @@ BoundUniformSet &MDUniformSet::boundUniformSetForShader(MDShader *p_shader, id<M
}
}
- BoundUniformSet bs = { .buffer = enc_buffer, .bound_resources = bound_resources };
+ SearchArray<__unsafe_unretained id<MTLResource>> search;
+ ResourceUsageMap usage_to_resources;
+ for (KeyValue<id<MTLResource>, StageResourceUsage> const &keyval : bound_resources) {
+ ResourceVector *resources = usage_to_resources.getptr(keyval.value);
+ if (resources == nullptr) {
+ resources = &usage_to_resources.insert(keyval.value, ResourceVector())->value;
+ }
+ int64_t pos = search.bisect(resources->ptr(), resources->size(), keyval.key, true);
+ if (pos == resources->size() || (*resources)[pos] != keyval.key) {
+ resources->insert(pos, keyval.key);
+ }
+ }
+
+ BoundUniformSet bs = { .buffer = enc_buffer, .usage_to_resources = usage_to_resources };
bound_uniforms.insert(p_shader, bs);
return bound_uniforms.get(p_shader);
}
@@ -1211,8 +1288,7 @@ vertex VaryingsPos vertClear(AttributesPos attributes [[stage_in]], constant Cle
varyings.layer = uint(attributes.a_position.w);
return varyings;
}
-)",
- ClearAttKey::DEPTH_INDEX];
+)", ClearAttKey::DEPTH_INDEX];
return new_func(msl, @"vertClear", nil);
}
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index a4a408356a..4da11ecd21 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -2060,6 +2060,10 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
case BT::Sampler: {
primary.dataType = MTLDataTypeSampler;
+ primary.arrayLength = 1;
+ for (uint32_t const &a : a_type.array) {
+ primary.arrayLength *= a;
+ }
} break;
default: {
@@ -2067,7 +2071,7 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
} break;
}
- // Find array length.
+ // Find array length of image.
if (basetype == BT::Image || basetype == BT::SampledImage) {
primary.arrayLength = 1;
for (uint32_t const &a : a_type.array) {
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index d20f396281..32086515da 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -266,6 +266,7 @@ static const VkFormat RD_TO_VK_FORMAT[RDD::DATA_FORMAT_MAX] = {
static VkImageLayout RD_TO_VK_LAYOUT[RDD::TEXTURE_LAYOUT_MAX] = {
VK_IMAGE_LAYOUT_UNDEFINED, // TEXTURE_LAYOUT_UNDEFINED
+ VK_IMAGE_LAYOUT_GENERAL, // TEXTURE_LAYOUT_GENERAL
VK_IMAGE_LAYOUT_GENERAL, // TEXTURE_LAYOUT_STORAGE_OPTIMAL
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, // TEXTURE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
@@ -2636,11 +2637,13 @@ bool RenderingDeviceDriverVulkan::command_buffer_begin(CommandBufferID p_cmd_buf
bool RenderingDeviceDriverVulkan::command_buffer_begin_secondary(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, uint32_t p_subpass, FramebufferID p_framebuffer) {
// Reset is implicit (VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT).
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+
VkCommandBufferInheritanceInfo inheritance_info = {};
inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
inheritance_info.renderPass = (VkRenderPass)p_render_pass.id;
inheritance_info.subpass = p_subpass;
- inheritance_info.framebuffer = (VkFramebuffer)p_framebuffer.id;
+ inheritance_info.framebuffer = framebuffer->vk_framebuffer;
VkCommandBufferBeginInfo cmd_buf_begin_info = {};
cmd_buf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@@ -2950,12 +2953,16 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
fb_create_info.height = surface->height;
fb_create_info.layers = 1;
- VkFramebuffer framebuffer;
+ VkFramebuffer vk_framebuffer;
for (uint32_t i = 0; i < image_count; i++) {
fb_create_info.pAttachments = &swap_chain->image_views[i];
- err = vkCreateFramebuffer(vk_device, &fb_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER), &framebuffer);
+ err = vkCreateFramebuffer(vk_device, &fb_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER), &vk_framebuffer);
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
+ Framebuffer *framebuffer = memnew(Framebuffer);
+ framebuffer->vk_framebuffer = vk_framebuffer;
+ framebuffer->swap_chain_image = swap_chain->images[i];
+ framebuffer->swap_chain_image_subresource_range = view_create_info.subresourceRange;
swap_chain->framebuffers.push_back(RDD::FramebufferID(framebuffer));
}
@@ -3024,7 +3031,10 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::swap_chain_acquire_framebuffer(C
command_queue->pending_semaphores_for_fence.push_back(semaphore_index);
// Return the corresponding framebuffer to the new current image.
- return swap_chain->framebuffers[swap_chain->image_index];
+ FramebufferID framebuffer_id = swap_chain->framebuffers[swap_chain->image_index];
+ Framebuffer *framebuffer = (Framebuffer *)(framebuffer_id.id);
+ framebuffer->swap_chain_acquired = true;
+ return framebuffer_id;
}
RDD::RenderPassID RenderingDeviceDriverVulkan::swap_chain_get_render_pass(SwapChainID p_swap_chain) {
@@ -3093,11 +3103,15 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::framebuffer_create(RenderPassID
}
#endif
- return FramebufferID(vk_framebuffer);
+ Framebuffer *framebuffer = memnew(Framebuffer);
+ framebuffer->vk_framebuffer = vk_framebuffer;
+ return FramebufferID(framebuffer);
}
void RenderingDeviceDriverVulkan::framebuffer_free(FramebufferID p_framebuffer) {
- vkDestroyFramebuffer(vk_device, (VkFramebuffer)p_framebuffer.id, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER));
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+ vkDestroyFramebuffer(vk_device, framebuffer->vk_framebuffer, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER));
+ memdelete(framebuffer);
}
/****************/
@@ -4315,10 +4329,25 @@ void RenderingDeviceDriverVulkan::render_pass_free(RenderPassID p_render_pass) {
static_assert(ARRAYS_COMPATIBLE_FIELDWISE(RDD::RenderPassClearValue, VkClearValue));
void RenderingDeviceDriverVulkan::command_begin_render_pass(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, FramebufferID p_framebuffer, CommandBufferType p_cmd_buffer_type, const Rect2i &p_rect, VectorView<RenderPassClearValue> p_clear_values) {
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+ if (framebuffer->swap_chain_acquired) {
+ // Insert a barrier to wait for the acquisition of the framebuffer before the render pass begins.
+ VkImageMemoryBarrier image_barrier = {};
+ image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ image_barrier.image = framebuffer->swap_chain_image;
+ image_barrier.subresourceRange = framebuffer->swap_chain_image_subresource_range;
+ vkCmdPipelineBarrier((VkCommandBuffer)p_cmd_buffer.id, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier);
+ framebuffer->swap_chain_acquired = false;
+ }
+
VkRenderPassBeginInfo render_pass_begin = {};
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin.renderPass = (VkRenderPass)p_render_pass.id;
- render_pass_begin.framebuffer = (VkFramebuffer)p_framebuffer.id;
+ render_pass_begin.framebuffer = framebuffer->vk_framebuffer;
render_pass_begin.renderArea.offset.x = p_rect.position.x;
render_pass_begin.renderArea.offset.y = p_rect.position.y;
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index 58f7a97ec0..4d5de897cd 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -366,6 +366,15 @@ public:
/**** FRAMEBUFFER ****/
/*********************/
+ struct Framebuffer {
+ VkFramebuffer vk_framebuffer = VK_NULL_HANDLE;
+
+ // Only filled in by a framebuffer created by a swap chain. Unused otherwise.
+ VkImage swap_chain_image = VK_NULL_HANDLE;
+ VkImageSubresourceRange swap_chain_image_subresource_range = {};
+ bool swap_chain_acquired = false;
+ };
+
virtual FramebufferID framebuffer_create(RenderPassID p_render_pass, VectorView<TextureID> p_attachments, uint32_t p_width, uint32_t p_height) override final;
virtual void framebuffer_free(FramebufferID p_framebuffer) override final;
diff --git a/editor/add_metadata_dialog.cpp b/editor/add_metadata_dialog.cpp
index 0a070e37b6..66a7b820f5 100644
--- a/editor/add_metadata_dialog.cpp
+++ b/editor/add_metadata_dialog.cpp
@@ -64,7 +64,6 @@ AddMetadataDialog::AddMetadataDialog() {
}
void AddMetadataDialog::_complete_init(const StringName &p_title) {
- add_meta_name->grab_focus();
add_meta_name->set_text("");
validation_panel->update();
@@ -90,6 +89,7 @@ void AddMetadataDialog::open(const StringName p_title, List<StringName> &p_exist
this->_existing_metas = p_existing_metas;
_complete_init(p_title);
popup_centered();
+ add_meta_name->grab_focus();
}
StringName AddMetadataDialog::get_meta_name() {
diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp
index d244b6b4cd..8b253f36e4 100644
--- a/editor/debugger/editor_profiler.cpp
+++ b/editor/debugger/editor_profiler.cpp
@@ -30,6 +30,7 @@
#include "editor_profiler.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp
index 7b831a1c8b..b949df4518 100644
--- a/editor/debugger/editor_visual_profiler.cpp
+++ b/editor/debugger/editor_visual_profiler.cpp
@@ -30,6 +30,7 @@
#include "editor_visual_profiler.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index ee16c61c89..bb02172b1a 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -547,6 +547,7 @@ Variant EditorData::instantiate_custom_type(const String &p_type, const String &
if (n) {
n->set_name(p_type);
}
+ n->set_meta(SceneStringName(_custom_type_script), script);
((Object *)ob)->set_script(script);
return ob;
}
@@ -1008,6 +1009,7 @@ Variant EditorData::script_class_instance(const String &p_class) {
// Store in a variant to initialize the refcount if needed.
Variant obj = ClassDB::instantiate(script->get_instance_base_type());
if (obj) {
+ Object::cast_to<Object>(obj)->set_meta(SceneStringName(_custom_type_script), script);
obj.operator Object *()->set_script(script);
}
return obj;
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 2d1a914120..c6ed310a9a 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -248,11 +248,16 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Scanning file structure..."), 0, true);
nb_files_total = _scan_new_dir(first_scan_root_dir, d);
+ // Preloading GDExtensions file extensions to prevent looping on all the resource loaders
+ // for each files in _first_scan_process_scripts.
+ List<String> gdextension_extensions;
+ ResourceLoader::get_recognized_extensions_for_type("GDExtension", &gdextension_extensions);
+
// This loads the global class names from the scripts and ensures that even if the
// global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer.
// At the same time, to prevent looping multiple times in all files, it looks for extensions.
ep.step(TTR("Loading global class names..."), 1, true);
- _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions);
+ _first_scan_process_scripts(first_scan_root_dir, gdextension_extensions, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
@@ -276,16 +281,16 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Starting file scan..."), 5, true);
}
-void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
- _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions);
+ _first_scan_process_scripts(scan_sub_dir, p_gdextension_extensions, p_existing_class_names, p_extensions);
}
for (const String &scan_file : p_scan_dir->files) {
// Optimization to skip the ResourceLoader::get_resource_type for files
// that are not scripts. Some loader get_resource_type methods read the file
// which can be very slow on large projects.
- String ext = scan_file.get_extension().to_lower();
+ const String ext = scan_file.get_extension().to_lower();
bool is_script = false;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (ScriptServer::get_language(i)->get_extension() == ext) {
@@ -293,24 +298,29 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
break;
}
}
- if (!is_script) {
- continue; // Not a script.
- }
+ if (is_script) {
+ const String path = p_scan_dir->full_path.path_join(scan_file);
+ const String type = ResourceLoader::get_resource_type(path);
- String path = p_scan_dir->full_path.path_join(scan_file);
- String type = ResourceLoader::get_resource_type(path);
+ if (ClassDB::is_parent_class(type, SNAME("Script"))) {
+ String script_class_extends;
+ String script_class_icon_path;
+ String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
+ _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
- if (ClassDB::is_parent_class(type, SNAME("Script"))) {
- String script_class_extends;
- String script_class_icon_path;
- String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
- _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
+ if (!script_class_name.is_empty()) {
+ p_existing_class_names.insert(script_class_name);
+ }
+ }
+ }
- if (!script_class_name.is_empty()) {
- p_existing_class_names.insert(script_class_name);
+ // Check for GDExtensions.
+ if (p_gdextension_extensions.find(ext)) {
+ const String path = p_scan_dir->full_path.path_join(scan_file);
+ const String type = ResourceLoader::get_resource_type(path);
+ if (type == SNAME("GDExtension")) {
+ p_extensions.insert(path);
}
- } else if (type == SNAME("GDExtension")) {
- p_extensions.insert(path);
}
}
}
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 7aa0137f4e..7120a68b39 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -191,7 +191,7 @@ class EditorFileSystem : public Node {
void _scan_filesystem();
void _first_scan_filesystem();
- void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
HashSet<String> late_update_files;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index dd6c88ef25..c69f443a22 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -35,6 +35,7 @@
#include "core/input/input.h"
#include "core/io/config_file.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/class_db.h"
@@ -3378,6 +3379,8 @@ void EditorNode::unload_editor_addons() {
remove_editor_plugin(E.value, false);
memdelete(E.value);
}
+
+ addon_name_to_plugin.clear();
}
void EditorNode::_discard_changes(const String &p_str) {
@@ -4672,6 +4675,11 @@ void EditorNode::stop_child_process(OS::ProcessID p_pid) {
Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const {
ERR_FAIL_NULL_V(p_object, nullptr);
+ const Node *node = Object::cast_to<const Node>(p_object);
+ if (node && node->has_meta(SceneStringName(_custom_type_script))) {
+ return node->get_meta(SceneStringName(_custom_type_script));
+ }
+
Ref<Script> scr = p_object->get_script();
if (scr.is_valid()) {
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index c5a35e466c..2e2a18b9d6 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3221,6 +3221,7 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const
}
resource_picker->set_base_type(p_base_type);
+ resource_picker->set_resource_owner(p_object);
resource_picker->set_editable(true);
resource_picker->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(resource_picker);
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 0f0287718c..025d019f45 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -224,7 +224,9 @@ void EditorResourcePicker::_update_menu_items() {
}
if (is_editable()) {
- edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
+ if (!_is_custom_type_script()) {
+ edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
+ }
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
// Check whether the resource has subresources.
@@ -694,6 +696,16 @@ bool EditorResourcePicker::_is_type_valid(const String &p_type_name, const HashS
return false;
}
+bool EditorResourcePicker::_is_custom_type_script() const {
+ Ref<Script> resource_as_script = edited_resource;
+
+ if (resource_as_script.is_valid() && resource_owner && resource_owner->has_meta(SceneStringName(_custom_type_script))) {
+ return true;
+ }
+
+ return false;
+}
+
Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (edited_resource.is_valid()) {
Dictionary drag_data = EditorNode::get_singleton()->drag_resource(edited_resource, p_from);
@@ -953,6 +965,10 @@ bool EditorResourcePicker::is_toggle_pressed() const {
return assign_button->is_pressed();
}
+void EditorResourcePicker::set_resource_owner(Object *p_object) {
+ resource_owner = p_object;
+}
+
void EditorResourcePicker::set_editable(bool p_editable) {
editable = p_editable;
assign_button->set_disabled(!editable && !edited_resource.is_valid());
@@ -1098,7 +1114,10 @@ void EditorScriptPicker::set_create_options(Object *p_menu_node) {
return;
}
- menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
+ if (!(script_owner && script_owner->has_meta(SceneStringName(_custom_type_script)))) {
+ menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
+ }
+
if (script_owner) {
Ref<Script> scr = script_owner->get_script();
if (scr.is_valid()) {
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 0a32dea3ed..8fb774a2cb 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -81,6 +81,8 @@ class EditorResourcePicker : public HBoxContainer {
CONVERT_BASE_ID = 1000,
};
+ Object *resource_owner = nullptr;
+
PopupMenu *edit_menu = nullptr;
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
@@ -102,6 +104,7 @@ class EditorResourcePicker : public HBoxContainer {
void _ensure_allowed_types() const;
bool _is_drop_valid(const Dictionary &p_drag_data) const;
bool _is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const;
+ bool _is_custom_type_script() const;
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
@@ -137,6 +140,8 @@ public:
void set_toggle_pressed(bool p_pressed);
bool is_toggle_pressed() const;
+ void set_resource_owner(Object *p_object);
+
void set_editable(bool p_editable);
bool is_editable() const;
diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp
index a6b30233fc..8fde728027 100644
--- a/editor/event_listener_line_edit.cpp
+++ b/editor/event_listener_line_edit.cpp
@@ -121,7 +121,7 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo
}
String EventListenerLineEdit::get_device_string(int p_device) {
- if (p_device == InputMap::ALL_DEVICES) {
+ if (p_device == InputEvent::DEVICE_ID_ALL_DEVICES) {
return TTR("All Devices");
}
return TTR("Device") + " " + itos(p_device);
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index 91b7810f77..7600748685 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -1355,6 +1355,13 @@ EditorFileDialog::Access EditorFileDialog::get_access() const {
void EditorFileDialog::_make_dir_confirm() {
const String stripped_dirname = makedirname->get_text().strip_edges();
+ if (stripped_dirname.is_empty()) {
+ error_dialog->set_text(TTR("The path specified is invalid."));
+ error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
+ makedirname->set_text(""); // Reset label.
+ return;
+ }
+
if (dir_access->dir_exists(stripped_dirname)) {
error_dialog->set_text(TTR("Could not create folder. File with that name already exists."));
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index 14065abf73..6c22a965ae 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -790,23 +790,14 @@ ImportDock::ImportDock() {
import->set_text(TTR("Reimport"));
import->set_disabled(true);
import->connect(SceneStringName(pressed), callable_mp(this, &ImportDock::_reimport_pressed));
- if (!DisplayServer::get_singleton()->get_swap_cancel_ok()) {
- advanced_spacer = hb->add_spacer();
- advanced = memnew(Button);
- advanced->set_text(TTR("Advanced..."));
- hb->add_child(advanced);
- }
+ advanced_spacer = hb->add_spacer();
+ advanced = memnew(Button);
+ advanced->set_text(TTR("Advanced..."));
+ hb->add_child(advanced);
hb->add_spacer();
hb->add_child(import);
hb->add_spacer();
- if (DisplayServer::get_singleton()->get_swap_cancel_ok()) {
- advanced = memnew(Button);
- advanced->set_text(TTR("Advanced..."));
- hb->add_child(advanced);
- advanced_spacer = hb->add_spacer();
- }
-
advanced->hide();
advanced_spacer->hide();
advanced->connect(SceneStringName(pressed), callable_mp(this, &ImportDock::_advanced_options));
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index c60197b96b..a2aeeb11bd 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -551,18 +551,18 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
}
void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) {
- // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1)
- // and option index 1 corresponds to device 0, etc...
- event->set_device(p_option_button_index - 1);
+ // Option index 0 corresponds to "All Devices" (value of -3).
+ // Otherwise subtract 1 as option index 1 corresponds to device 0, etc...
+ event->set_device(p_option_button_index == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : p_option_button_index - 1);
event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true));
}
void InputEventConfigurationDialog::_set_current_device(int p_device) {
- device_id_option->select(p_device + 1);
+ device_id_option->select(p_device == InputEvent::DEVICE_ID_ALL_DEVICES ? 0 : p_device + 1);
}
int InputEventConfigurationDialog::_get_current_device() const {
- return device_id_option->get_selected() - 1;
+ return device_id_option->get_selected() == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : device_id_option->get_selected() - 1;
}
void InputEventConfigurationDialog::_notification(int p_what) {
@@ -705,11 +705,12 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
device_id_option = memnew(OptionButton);
device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- for (int i = -1; i < 8; i++) {
+ device_id_option->add_item(EventListenerLineEdit::get_device_string(InputEvent::DEVICE_ID_ALL_DEVICES));
+ for (int i = 0; i < 8; i++) {
device_id_option->add_item(EventListenerLineEdit::get_device_string(i));
}
device_id_option->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed));
- _set_current_device(InputMap::ALL_DEVICES);
+ _set_current_device(InputEvent::DEVICE_ID_ALL_DEVICES);
device_container->add_child(device_id_option);
device_container->hide();
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index cd13deb3e9..24943af60f 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -536,7 +536,6 @@ ControlEditorPopupButton::ControlEditorPopupButton() {
set_focus_mode(FOCUS_NONE);
popup_panel = memnew(PopupPanel);
- popup_panel->set_theme_type_variation("ControlEditorPopupPanel");
add_child(popup_panel);
popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true));
popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false));
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 9a53f07a3f..3618c0e6d3 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/file_access_memory.h"
+#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
diff --git a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
index 748f770d4d..007cc0636a 100644
--- a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
@@ -44,7 +44,10 @@ LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+ // Fade out probes when camera gets too close to them.
+ mat->set_distance_fade(StandardMaterial3D::DISTANCE_FADE_PIXEL_DITHER);
+ mat->set_distance_fade_min_distance(0.5);
+ mat->set_distance_fade_max_distance(1.5);
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 7e0331d15c..96a9dd7a80 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -261,6 +261,52 @@ Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const {
return syntax_highlighter;
}
+////
+
+void EditorMarkdownSyntaxHighlighter::_update_cache() {
+ highlighter->set_text_edit(text_edit);
+ highlighter->clear_keyword_colors();
+ highlighter->clear_member_keyword_colors();
+ highlighter->clear_color_regions();
+
+ // Disable automatic symbolic highlights, as these don't make sense for prose.
+ highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+
+ // Headings (any level).
+ const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
+ highlighter->add_color_region("#", "", function_color);
+
+ // Bold.
+ highlighter->add_color_region("**", "**", function_color);
+ // `__bold__` syntax is not supported as color regions must begin with a symbol,
+ // not a character that is valid in an identifier.
+
+ // Code (both inline code and triple-backticks code blocks).
+ const Color code_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
+ highlighter->add_color_region("`", "`", code_color);
+
+ // Link (both references and inline links with URLs). The URL is not highlighted.
+ const Color link_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
+ highlighter->add_color_region("[", "]", link_color);
+
+ // Quote.
+ const Color quote_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ highlighter->add_color_region(">", "", quote_color, true);
+
+ // HTML comment, which is also supported in Markdown.
+ const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
+ highlighter->add_color_region("<!--", "-->", comment_color);
+}
+
+Ref<EditorSyntaxHighlighter> EditorMarkdownSyntaxHighlighter::_create() const {
+ Ref<EditorMarkdownSyntaxHighlighter> syntax_highlighter;
+ syntax_highlighter.instantiate();
+ return syntax_highlighter;
+}
+
////////////////////////////////////////////////////////////////////////////////
/*** SCRIPT EDITOR ****/
@@ -4414,6 +4460,10 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
json_syntax_highlighter.instantiate();
register_syntax_highlighter(json_syntax_highlighter);
+ Ref<EditorMarkdownSyntaxHighlighter> markdown_syntax_highlighter;
+ markdown_syntax_highlighter.instantiate();
+ register_syntax_highlighter(markdown_syntax_highlighter);
+
_update_online_doc();
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 8e82d60605..5de0aaa1e9 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -120,6 +120,24 @@ public:
EditorJSONSyntaxHighlighter() { highlighter.instantiate(); }
};
+class EditorMarkdownSyntaxHighlighter : public EditorSyntaxHighlighter {
+ GDCLASS(EditorMarkdownSyntaxHighlighter, EditorSyntaxHighlighter)
+
+private:
+ Ref<CodeHighlighter> highlighter;
+
+public:
+ virtual void _update_cache() override;
+ virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); }
+
+ virtual PackedStringArray _get_supported_languages() const override { return PackedStringArray{ "md", "markdown" }; }
+ virtual String _get_name() const override { return TTR("Markdown"); }
+
+ virtual Ref<EditorSyntaxHighlighter> _create() const override;
+
+ EditorMarkdownSyntaxHighlighter() { highlighter.instantiate(); }
+};
+
///////////////////////////////////////////////////////////////////////////////
class ScriptEditorQuickOpen : public ConfirmationDialog {
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 6b00a2c9c5..5166619f90 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -396,6 +396,7 @@ void ShaderEditorPlugin::_setup_popup_menu(PopupMenuType p_type, PopupMenu *p_me
if (p_type == FILE) {
p_menu->add_separator();
p_menu->add_item(TTR("Open File in Inspector"), FILE_INSPECT);
+ p_menu->add_item(TTR("Inspect Native Shader Code..."), FILE_INSPECT_NATIVE_SHADER_CODE);
p_menu->add_separator();
p_menu->add_shortcut(ED_SHORTCUT("shader_editor/close_file", TTR("Close File"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE);
} else {
@@ -554,6 +555,12 @@ void ShaderEditorPlugin::_menu_item_pressed(int p_index) {
EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr());
}
} break;
+ case FILE_INSPECT_NATIVE_SHADER_CODE: {
+ int index = shader_tabs->get_current_tab();
+ if (edited_shaders[index].shader.is_valid()) {
+ edited_shaders[index].shader->inspect_native_shader_code();
+ }
+ } break;
case FILE_CLOSE: {
_close_shader(shader_tabs->get_current_tab());
} break;
@@ -754,6 +761,7 @@ void ShaderEditorPlugin::_set_file_specific_items_disabled(bool p_disabled) {
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE_AS), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_INSPECT), p_disabled);
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_INSPECT_NATIVE_SHADER_CODE), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_CLOSE), p_disabled);
}
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index 43e6af79fa..19e43921c3 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -69,6 +69,7 @@ class ShaderEditorPlugin : public EditorPlugin {
FILE_SAVE,
FILE_SAVE_AS,
FILE_INSPECT,
+ FILE_INSPECT_NATIVE_SHADER_CODE,
FILE_CLOSE,
CLOSE_ALL,
CLOSE_OTHER_TABS,
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index c7db243662..5333e10b56 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -593,12 +593,12 @@ Sprite2DEditor::Sprite2DEditor() {
add_child(err_dialog);
debug_uv_dialog = memnew(ConfirmationDialog);
+ debug_uv_dialog->set_size(Size2(960, 540) * EDSCALE);
VBoxContainer *vb = memnew(VBoxContainer);
debug_uv_dialog->add_child(vb);
debug_uv = memnew(Panel);
debug_uv->connect(SceneStringName(gui_input), callable_mp(this, &Sprite2DEditor::_debug_uv_input));
debug_uv->connect(SceneStringName(draw), callable_mp(this, &Sprite2DEditor::_debug_uv_draw));
- debug_uv->set_custom_minimum_size(Size2(800, 500) * EDSCALE);
debug_uv->set_clip_contents(true);
vb->add_margin_child(TTR("Preview:"), debug_uv, true);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index ede8351e41..a5df9edcf0 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -2128,12 +2128,11 @@ void VisualShaderEditor::_update_nodes() {
}
}
- Array keys = added.keys();
- keys.sort();
-
- for (int i = 0; i < keys.size(); i++) {
- const Variant &key = keys.get(i);
+ List<Variant> keys;
+ added.get_key_list(&keys);
+ keys.sort_custom<StringLikeVariantOrder>();
+ for (const Variant &key : keys) {
const Dictionary &value = (Dictionary)added[key];
add_custom_type(value["name"], value["type"], value["script"], value["description"], value["return_icon_type"], value["category"], value["highend"]);
diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp
index 541ab01e62..39c1a78c4a 100644
--- a/editor/project_manager/project_list.cpp
+++ b/editor/project_manager/project_list.cpp
@@ -88,7 +88,7 @@ void ProjectListItemControl::_notification(int p_what) {
draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
if (is_hovering) {
- draw_style_box(get_theme_stylebox(SNAME("hover"), SNAME("Tree")), Rect2(Point2(), get_size()));
+ draw_style_box(get_theme_stylebox(SNAME("hovered"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree")));
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index bcab0c2883..11aa282833 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1667,6 +1667,7 @@ void SceneTreeDock::_notification(int p_what) {
button_instance->set_icon(get_editor_theme_icon(SNAME("Instance")));
button_create_script->set_icon(get_editor_theme_icon(SNAME("ScriptCreate")));
button_detach_script->set_icon(get_editor_theme_icon(SNAME("ScriptRemove")));
+ button_extend_script->set_icon(get_editor_theme_icon(SNAME("ScriptExtend")));
button_tree_menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
@@ -2784,33 +2785,49 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
void SceneTreeDock::_update_script_button() {
- if (!profile_allow_script_editing) {
- button_create_script->hide();
- button_detach_script->hide();
- } else if (editor_selection->get_selection().size() == 0) {
- button_create_script->hide();
- button_detach_script->hide();
- } else if (editor_selection->get_selection().size() == 1) {
- Node *n = editor_selection->get_selected_node_list().front()->get();
- if (n->get_script().is_null()) {
- button_create_script->show();
- button_detach_script->hide();
- } else {
- button_create_script->hide();
- button_detach_script->show();
- }
- } else {
- button_create_script->hide();
+ bool can_create_script = false;
+ bool can_detach_script = false;
+ bool can_extend_script = false;
+
+ if (profile_allow_script_editing) {
Array selection = editor_selection->get_selected_nodes();
+
for (int i = 0; i < selection.size(); i++) {
Node *n = Object::cast_to<Node>(selection[i]);
- if (!n->get_script().is_null()) {
- button_detach_script->show();
- return;
+ Ref<Script> s = n->get_script();
+ Ref<Script> cts;
+
+ if (n->has_meta(SceneStringName(_custom_type_script))) {
+ cts = n->get_meta(SceneStringName(_custom_type_script));
+ }
+
+ if (selection.size() == 1) {
+ if (s.is_valid()) {
+ if (cts.is_valid() && s == cts) {
+ can_extend_script = true;
+ }
+ } else {
+ can_create_script = true;
+ }
+ }
+
+ if (s.is_valid()) {
+ if (cts.is_valid()) {
+ if (s != cts) {
+ can_detach_script = true;
+ break;
+ }
+ } else {
+ can_detach_script = true;
+ break;
+ }
}
}
- button_detach_script->hide();
}
+
+ button_create_script->set_visible(can_create_script);
+ button_detach_script->set_visible(can_detach_script);
+ button_extend_script->set_visible(can_extend_script);
}
void SceneTreeDock::_selection_changed() {
@@ -3057,7 +3074,28 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro
Node *newnode = p_by_node;
if (p_keep_properties) {
- Node *default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(oldnode->get_class()));
+ Node *default_oldnode = nullptr;
+
+ // If we're dealing with a custom node type, we need to create a default instance of the custom type instead of the native type for property comparison.
+ if (oldnode->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> cts = oldnode->get_meta(SceneStringName(_custom_type_script));
+ default_oldnode = Object::cast_to<Node>(get_editor_data()->script_class_instance(cts->get_global_name()));
+ if (default_oldnode) {
+ default_oldnode->set_name(cts->get_global_name());
+ get_editor_data()->instantiate_object_properties(default_oldnode);
+ } else {
+ // Legacy custom type, registered with "add_custom_type()".
+ // TODO: Should probably be deprecated in 4.x.
+ const EditorData::CustomType *custom_type = get_editor_data()->get_custom_type_by_path(cts->get_path());
+ if (custom_type) {
+ default_oldnode = Object::cast_to<Node>(get_editor_data()->instantiate_custom_type(custom_type->name, cts->get_instance_base_type()));
+ }
+ }
+ }
+
+ if (!default_oldnode) {
+ default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(oldnode->get_class()));
+ }
List<PropertyInfo> pinfo;
oldnode->get_property_list(&pinfo);
@@ -3542,6 +3580,27 @@ void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) {
undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(n)).path_join(new_node->get_name())));
undo_redo->commit_action();
} else {
+ // Check if dropped script is compatible.
+ if (n->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> ct_scr = n->get_meta(SceneStringName(_custom_type_script));
+ if (!scr->inherits_script(ct_scr)) {
+ String custom_type_name = ct_scr->get_global_name();
+
+ // Legacy custom type, registered with "add_custom_type()".
+ if (custom_type_name.is_empty()) {
+ const EditorData::CustomType *custom_type = get_editor_data()->get_custom_type_by_path(ct_scr->get_path());
+ if (custom_type) {
+ custom_type_name = custom_type->name;
+ } else {
+ custom_type_name = TTR("<unknown>");
+ }
+ }
+
+ WARN_PRINT_ED(vformat("Script does not extend type: '%s'.", custom_type_name));
+ return;
+ }
+ }
+
undo_redo->create_action(TTR("Attach Script"), UndoRedo::MERGE_DISABLE, n);
undo_redo->add_do_method(InspectorDock::get_singleton(), "store_script_properties", n);
undo_redo->add_undo_method(InspectorDock::get_singleton(), "store_script_properties", n);
@@ -3649,6 +3708,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
Ref<Script> existing_script;
bool existing_script_removable = true;
+ bool allow_attach_new_script = true;
if (selection.size() == 1) {
Node *selected = selection.front()->get();
@@ -3672,6 +3732,10 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script) {
existing_script_removable = false;
}
+
+ if (selected->has_meta(SceneStringName(_custom_type_script))) {
+ allow_attach_new_script = false;
+ }
}
if (profile_allow_editing) {
@@ -3692,7 +3756,10 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (full_selection.size() == 1) {
add_separator = true;
- menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptCreate")), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
+ if (allow_attach_new_script) {
+ menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptCreate")), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
+ }
+
if (existing_script.is_valid()) {
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptExtend")), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT);
}
@@ -4601,6 +4668,14 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
filter_hbc->add_child(button_detach_script);
button_detach_script->hide();
+ button_extend_script = memnew(Button);
+ button_extend_script->set_flat(true);
+ button_extend_script->connect(SceneStringName(pressed), callable_mp(this, &SceneTreeDock::_tool_selected).bind(TOOL_EXTEND_SCRIPT, false));
+ button_extend_script->set_tooltip_text(TTR("Extend the script of the selected node."));
+ button_extend_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/extend_script"));
+ filter_hbc->add_child(button_extend_script);
+ button_extend_script->hide();
+
button_tree_menu = memnew(MenuButton);
button_tree_menu->set_flat(false);
button_tree_menu->set_theme_type_variation("FlatMenuButton");
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 05ad0f36e4..8cee2870f6 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -115,6 +115,7 @@ class SceneTreeDock : public VBoxContainer {
Button *button_instance = nullptr;
Button *button_create_script = nullptr;
Button *button_detach_script = nullptr;
+ Button *button_extend_script = nullptr;
MenuButton *button_tree_menu = nullptr;
Button *node_shortcuts_toggle = nullptr;
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 17bcbacfc2..4db43f0703 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -633,6 +633,16 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme,
// in 4.0, and even if it was, it may not always work in practice (e.g. running with compositing disabled).
p_config.popup_style->set_corner_radius_all(0);
+ p_config.popup_border_style = p_config.popup_style->duplicate();
+ p_config.popup_border_style->set_content_margin_all(MAX(Math::round(EDSCALE), p_config.border_width) + 2 + (p_config.base_margin * 1.5) * EDSCALE);
+ // Always display a border for popups like PopupMenus so they can be distinguished from their background.
+ p_config.popup_border_style->set_border_width_all(MAX(Math::round(EDSCALE), p_config.border_width));
+ if (p_config.draw_extra_borders) {
+ p_config.popup_border_style->set_border_color(p_config.extra_border_color_2);
+ } else {
+ p_config.popup_border_style->set_border_color(p_config.dark_color_2);
+ }
+
p_config.window_style = p_config.popup_style->duplicate();
p_config.window_style->set_border_color(p_config.base_color);
p_config.window_style->set_border_width(SIDE_TOP, 24 * EDSCALE);
@@ -707,7 +717,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
}
// PopupPanel
- p_theme->set_stylebox(SceneStringName(panel), "PopupPanel", p_config.popup_style);
+ p_theme->set_stylebox(SceneStringName(panel), "PopupPanel", p_config.popup_border_style);
}
// Buttons.
@@ -945,6 +955,8 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_color("custom_button_font_highlight", "Tree", p_config.font_hover_color);
p_theme->set_color(SceneStringName(font_color), "Tree", p_config.font_color);
+ p_theme->set_color("font_hovered_color", "Tree", p_config.mono_color);
+ p_theme->set_color("font_hovered_dimmed_color", "Tree", p_config.font_color);
p_theme->set_color("font_selected_color", "Tree", p_config.mono_color);
p_theme->set_color("font_disabled_color", "Tree", p_config.font_disabled_color);
p_theme->set_color("font_outline_color", "Tree", p_config.font_outline_color);
@@ -997,7 +1009,13 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
Ref<StyleBoxFlat> style_tree_hover = p_config.base_style->duplicate();
style_tree_hover->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.4));
style_tree_hover->set_border_width_all(0);
- p_theme->set_stylebox("hover", "Tree", style_tree_hover);
+ p_theme->set_stylebox("hovered", "Tree", style_tree_hover);
+ p_theme->set_stylebox("button_hover", "Tree", style_tree_hover);
+
+ Ref<StyleBoxFlat> style_tree_hover_dimmed = p_config.base_style->duplicate();
+ style_tree_hover_dimmed->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.2));
+ style_tree_hover_dimmed->set_border_width_all(0);
+ p_theme->set_stylebox("hovered_dimmed", "Tree", style_tree_hover_dimmed);
p_theme->set_stylebox("selected_focus", "Tree", style_tree_focus);
p_theme->set_stylebox("selected", "Tree", style_tree_selected);
@@ -1306,18 +1324,11 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// PopupMenu.
{
- Ref<StyleBoxFlat> style_popup_menu = p_config.popup_style->duplicate();
+ Ref<StyleBoxFlat> style_popup_menu = p_config.popup_border_style->duplicate();
// Use 1 pixel for the sides, since if 0 is used, the highlight of hovered items is drawn
// on top of the popup border. This causes a 'gap' in the panel border when an item is highlighted,
// and it looks weird. 1px solves this.
- style_popup_menu->set_content_margin_individual(EDSCALE, 2 * EDSCALE, EDSCALE, 2 * EDSCALE);
- // Always display a border for PopupMenus so they can be distinguished from their background.
- style_popup_menu->set_border_width_all(EDSCALE);
- if (p_config.draw_extra_borders) {
- style_popup_menu->set_border_color(p_config.extra_border_color_2);
- } else {
- style_popup_menu->set_border_color(p_config.dark_color_2);
- }
+ style_popup_menu->set_content_margin_individual(Math::round(EDSCALE), 2 * EDSCALE, Math::round(EDSCALE), 2 * EDSCALE);
p_theme->set_stylebox(SceneStringName(panel), "PopupMenu", style_popup_menu);
Ref<StyleBoxFlat> style_menu_hover = p_config.button_style_hover->duplicate();
@@ -1327,17 +1338,17 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
Ref<StyleBoxLine> style_popup_separator(memnew(StyleBoxLine));
style_popup_separator->set_color(p_config.separator_color);
- style_popup_separator->set_grow_begin(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
- style_popup_separator->set_grow_end(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_separator->set_grow_begin(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_separator->set_grow_end(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_separator->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
Ref<StyleBoxLine> style_popup_labeled_separator_left(memnew(StyleBoxLine));
- style_popup_labeled_separator_left->set_grow_begin(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_labeled_separator_left->set_grow_begin(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_labeled_separator_left->set_color(p_config.separator_color);
style_popup_labeled_separator_left->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
Ref<StyleBoxLine> style_popup_labeled_separator_right(memnew(StyleBoxLine));
- style_popup_labeled_separator_right->set_grow_end(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_labeled_separator_right->set_grow_end(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_labeled_separator_right->set_color(p_config.separator_color);
style_popup_labeled_separator_right->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
@@ -2115,21 +2126,6 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
// EditorValidationPanel.
p_theme->set_stylebox(SceneStringName(panel), "EditorValidationPanel", p_config.tree_panel_style);
-
- // ControlEditor.
- {
- p_theme->set_type_variation("ControlEditorPopupPanel", "PopupPanel");
-
- Ref<StyleBoxFlat> control_editor_popup_style = p_config.popup_style->duplicate();
- control_editor_popup_style->set_shadow_size(0);
- control_editor_popup_style->set_content_margin(SIDE_LEFT, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_TOP, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_RIGHT, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_BOTTOM, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_border_width_all(0);
-
- p_theme->set_stylebox(SceneStringName(panel), "ControlEditorPopupPanel", control_editor_popup_style);
- }
}
// Editor inspector.
diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h
index 5e7bd00083..ca5e1a4e2d 100644
--- a/editor/themes/editor_theme_manager.h
+++ b/editor/themes/editor_theme_manager.h
@@ -135,6 +135,7 @@ class EditorThemeManager {
Ref<StyleBoxFlat> button_style_hover;
Ref<StyleBoxFlat> popup_style;
+ Ref<StyleBoxFlat> popup_border_style;
Ref<StyleBoxFlat> window_style;
Ref<StyleBoxFlat> dialog_style;
Ref<StyleBoxFlat> panel_container_style;
diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp
index 9496ba016c..7f61623980 100644
--- a/editor/window_wrapper.cpp
+++ b/editor/window_wrapper.cpp
@@ -391,7 +391,6 @@ void ScreenSelect::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
set_icon(get_editor_theme_icon("MakeFloating"));
- popup_background->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("PanelForeground", EditorStringName(EditorStyles)));
const real_t popup_height = real_t(get_theme_font_size(SceneStringName(font_size))) * 2.0;
popup->set_min_size(Size2(0, popup_height * 3));
@@ -454,14 +453,10 @@ ScreenSelect::ScreenSelect() {
// Create the popup.
const Size2 borders = Size2(4, 4) * EDSCALE;
- popup = memnew(Popup);
+ popup = memnew(PopupPanel);
popup->connect("popup_hide", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false));
add_child(popup);
- popup_background = memnew(Panel);
- popup_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
- popup->add_child(popup_background);
-
MarginContainer *popup_root = memnew(MarginContainer);
popup_root->add_theme_constant_override("margin_right", borders.width);
popup_root->add_theme_constant_override("margin_top", borders.height);
diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h
index a07e95f09e..3597276de9 100644
--- a/editor/window_wrapper.h
+++ b/editor/window_wrapper.h
@@ -88,7 +88,6 @@ class ScreenSelect : public Button {
GDCLASS(ScreenSelect, Button);
Popup *popup = nullptr;
- Panel *popup_background = nullptr;
HBoxContainer *screen_list = nullptr;
void _build_advanced_menu();
diff --git a/main/main.cpp b/main/main.cpp
index 5206e9b84c..743b67f89b 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -42,6 +42,7 @@
#include "core/io/dir_access.h"
#include "core/io/file_access_pack.h"
#include "core/io/file_access_zip.h"
+#include "core/io/image.h"
#include "core/io/image_loader.h"
#include "core/io/ip.h"
#include "core/io/resource_loader.h"
@@ -2412,6 +2413,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (bool(GLOBAL_GET("display/window/size/no_focus"))) {
window_flags |= DisplayServer::WINDOW_FLAG_NO_FOCUS_BIT;
}
+ if (bool(GLOBAL_GET("display/window/size/sharp_corners"))) {
+ window_flags |= DisplayServer::WINDOW_FLAG_SHARP_CORNERS_BIT;
+ }
window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int());
int initial_position_type = GLOBAL_GET("display/window/size/initial_position_type").operator int();
if (initial_position_type == 0) { // Absolute.
@@ -3197,6 +3201,10 @@ Error Main::setup2(bool p_show_boot_logo) {
}
id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true)));
+
+ if (editor) {
+ id->set_emulate_mouse_from_touch(true);
+ }
}
OS::get_singleton()->benchmark_end_measure("Startup", "Setup Window and Boot");
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index d48ea363a7..8ca5dba225 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -30,6 +30,7 @@
#include "image_compress_basisu.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "servers/rendering_server.h"
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 6bfe01f65c..1ad9bed721 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -37,11 +37,16 @@
#include "bc1.glsl.gen.h"
#include "bc4.glsl.gen.h"
#include "bc6h.glsl.gen.h"
+#include "servers/display_server.h"
static Mutex betsy_mutex;
static BetsyCompressor *betsy = nullptr;
void BetsyCompressor::_init() {
+ if (!DisplayServer::can_create_rendering_device()) {
+ return;
+ }
+
// Create local RD.
RenderingContextDriver *rcd = nullptr;
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
@@ -182,6 +187,11 @@ static String get_shader_name(BetsyFormat p_format) {
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+ // Return an error so that the compression can fall back to cpu compression
+ if (compress_rd == nullptr) {
+ return ERR_CANT_CREATE;
+ }
+
if (r_img->is_compressed()) {
return ERR_INVALID_DATA;
}
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 5fe47d69df..21365da7cc 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -4,8 +4,8 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
- A list of GDScript-specific utility functions and annotations accessible from any script.
- For the list of the global functions and constants see [@GlobalScope].
+ A list of utility functions and annotations accessible from any script written in GDScript.
+ For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 32ef429b0d..758887a723 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -217,7 +217,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index d765cfa1ea..0b12f2ff76 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -701,7 +701,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- class_names[E] = types_color;
+ if (ClassDB::is_class_exposed(E)) {
+ class_names[E] = types_color;
+ }
}
/* User types. */
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 18f2ccc455..64287cce99 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -76,9 +76,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
- } else {
- return false;
}
+
+ MethodBind *method = ClassDB::get_method(name, p_name);
+ if (method && method->is_static()) {
+ // Native static method.
+ r_ret = Callable(this, p_name);
+ return true;
+ }
+
+ return false;
}
void GDScriptNativeClass::_bind_methods() {
@@ -2546,11 +2553,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
-#endif
+#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@@ -2620,7 +2627,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
-#endif
+#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@@ -2688,7 +2695,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 4a3a3a4b61..93d4a512a9 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -940,8 +940,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
- if (member_type.has_container_element_type(0)) {
- ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
+ for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
+ ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@@ -3816,6 +3816,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
+ // Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
+ // Search dependencies for the parser that owns `p_class` and make a cache entry for it.
+ // Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
+ // Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
+ // Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
+
if (p_class == nullptr) {
return nullptr;
}
@@ -3832,8 +3838,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
- String script_path = p_class->get_datatype().script_path;
-
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@@ -5867,7 +5871,7 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
parent = ClassDB::get_parent_class(parent);
}
}
-#endif
+#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3de1decc18..b59c071ae2 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3534,13 +3534,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return OK;
}
-#else
+#else // !TOOLS_ENABLED
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
-#endif
+#endif // TOOLS_ENABLED
//////// END COMPLETION //////////
@@ -4125,4 +4125,4 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-#endif
+#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 111a39d730..ee8d53639c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -244,7 +244,7 @@ void GDScriptParser::apply_pending_warnings() {
pending_warnings.clear();
}
-#endif
+#endif // DEBUG_ENABLED
void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
if (!for_completion) {
@@ -1624,15 +1624,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- annotation->info = &valid_annotations[annotation->name];
+ if (valid) {
+ annotation->info = &valid_annotations[annotation->name];
- if (!annotation->applies_to(p_valid_targets)) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
- } else {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (!annotation->applies_to(p_valid_targets)) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
+ valid = false;
}
- valid = false;
}
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7f64ae902b..2ec33831a2 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -165,6 +165,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
+ _FORCE_INLINE_ int get_container_element_type_count() const {
+ return container_element_types.size();
+ }
+
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 59dd983ed2..7dc586186b 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -74,13 +74,13 @@
return; \
}
-#else
+#else // !DEBUG_ENABLED
#define VALIDATE_ARG_COUNT(m_count)
#define VALIDATE_ARG_INT(m_arg)
#define VALIDATE_ARG_NUM(m_arg)
-#endif
+#endif // DEBUG_ENABLED
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index d8139d913a..26c5cfe23c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -397,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
-#else
+#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#endif
+#endif // DEBUG_ENABLED
+
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
-#else
+#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
-#else
+#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
-#endif
+#endif // _MSC_VER
+
#define OPCODE_BREAK break
#define OPCODE_OUT break
-#endif
+#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@@ -663,7 +667,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#else
+#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@@ -676,7 +680,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#endif
+#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@@ -1965,7 +1969,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
-#endif
+#endif // DEBUG_ENABLED
ip += 3;
}
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
new file mode 100644
index 0000000000..cfacb6a010
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
@@ -0,0 +1,3 @@
+@export
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
new file mode 100644
index 0000000000..ed677cd39a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Annotation "@export" cannot be applied to a function.
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
new file mode 100644
index 0000000000..a6b171a428
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
@@ -0,0 +1,3 @@
+@hello_world
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
new file mode 100644
index 0000000000..540e66f15b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unrecognized annotation: "@hello_world".
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
new file mode 100644
index 0000000000..63d5935d1e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
@@ -0,0 +1,8 @@
+func get_parse_string(t: Variant):
+ return t.parse_string
+
+func test():
+ var a: Callable = JSON.parse_string
+ var b: Callable = get_parse_string(JSON)
+ prints(a.call("{\"test\": \"a\"}"), a.is_valid())
+ prints(b.call("{\"test\": \"b\"}"), b.is_valid())
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
new file mode 100644
index 0000000000..a2cb4b9a07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "test": "a" } false
+{ "test": "b" } false
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
index 188972e6fe..f54058b0d9 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
@@ -32,7 +32,7 @@ partial class EventSignals
add => backing_MySignal += value;
remove => backing_MySignal -= value;
}
- protected void OnMySignal(string str, int num)
+ protected void EmitSignalMySignal(string str, int num)
{
EmitSignal(SignalName.MySignal, str, num);
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
index c734dc7be1..ffde135930 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
index 0de840aa34..94d447f61a 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index 0f86b3b91c..fc67e4f592 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -791,7 +791,7 @@ namespace Godot.SourceGenerators
}
}
- hint = PropertyHint.DictionaryType;
+ hint = PropertyHint.TypeString;
hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
return hintString != null;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
index 0dda43ab4c..702c50d461 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -282,7 +282,7 @@ namespace Godot.SourceGenerators
.Append(" -= value;\n")
.Append("}\n");
- // Generate On{EventName} method to raise the event
+ // Generate EmitSignal{EventName} method to raise the event
var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
int paramCount = invokeMethodSymbol.Parameters.Length;
@@ -291,7 +291,7 @@ namespace Godot.SourceGenerators
"private" :
"protected";
- source.Append($" {raiseMethodModifiers} void On{signalName}(");
+ source.Append($" {raiseMethodModifiers} void EmitSignal{signalName}(");
for (int i = 0; i < paramCount; i++)
{
var paramSymbol = invokeMethodSymbol.Parameters[i];
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 946e997c1b..e97229c621 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3275,10 +3275,10 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(CLOSE_BLOCK_L1);
- // Generate On{EventName} method to raise the event.
+ // Generate EmitSignal{EventName} method to raise the event.
if (!p_itype.is_singleton) {
p_output.append(MEMBER_BEGIN "protected void ");
- p_output << "On" << p_isignal.proxy_name;
+ p_output << "EmitSignal" << p_isignal.proxy_name;
if (is_parameterless) {
p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 5430a41d6d..f924386ecf 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -576,20 +576,15 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref<OpenXRAction> p
const OpenXRInteractionProfileMetadata::InteractionProfile *profile = OpenXRInteractionProfileMetadata::get_singleton()->get_profile(ip->get_interaction_profile_path());
if (profile != nullptr) {
- for (int j = 0; j < ip->get_binding_count(); j++) {
- Ref<OpenXRIPBinding> binding = ip->get_binding(j);
- if (binding->get_action() == p_action) {
- PackedStringArray paths = binding->get_paths();
-
- for (int k = 0; k < paths.size(); k++) {
- const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(paths[k]);
- if (io_path != nullptr) {
- String top_path = io_path->top_level_path;
-
- if (!arr.has(top_path)) {
- arr.push_back(top_path);
- }
- }
+ Vector<Ref<OpenXRIPBinding>> bindings = ip->get_bindings_for_action(p_action);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ String binding_path = binding->get_binding_path();
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(binding_path);
+ if (io_path != nullptr) {
+ String top_path = io_path->top_level_path;
+
+ if (!arr.has(top_path)) {
+ arr.push_back(top_path);
}
}
}
diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp
index 1266457113..2aab55f6ec 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile.cpp
@@ -35,23 +35,30 @@ void OpenXRIPBinding::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action");
- ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
+ ClassDB::bind_method(D_METHOD("set_binding_path", "binding_path"), &OpenXRIPBinding::set_binding_path);
+ ClassDB::bind_method(D_METHOD("get_binding_path"), &OpenXRIPBinding::get_binding_path);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "binding_path"), "set_binding_path", "get_binding_path");
+
+ // Deprecated
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths);
ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"), "set_paths", "get_paths");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_paths", "get_paths");
+ ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path);
ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path);
ClassDB::bind_method(D_METHOD("remove_path", "path"), &OpenXRIPBinding::remove_path);
+#endif // DISABLE_DEPRECATED
}
-Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) {
// This is a helper function to help build our default action sets
Ref<OpenXRIPBinding> binding;
binding.instantiate();
binding->set_action(p_action);
- binding->parse_paths(String(p_paths));
+ binding->set_binding_path(p_binding_path);
return binding;
}
@@ -65,42 +72,68 @@ Ref<OpenXRAction> OpenXRIPBinding::get_action() const {
return action;
}
-int OpenXRIPBinding::get_path_count() const {
- return paths.size();
+void OpenXRIPBinding::set_binding_path(const String &path) {
+ binding_path = path;
+ emit_changed();
}
-void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) {
- paths = p_paths;
- emit_changed();
+String OpenXRIPBinding::get_binding_path() const {
+ return binding_path;
}
-PackedStringArray OpenXRIPBinding::get_paths() const {
+#ifndef DISABLE_DEPRECATED
+
+void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { // Deprecated, but needed for loading old action maps.
+ // Fallback logic, this should ONLY be called when loading older action maps.
+ // We'll parse this momentarily and extract individual bindings.
+ binding_path = "";
+ for (const String &path : p_paths) {
+ if (!binding_path.is_empty()) {
+ binding_path += ",";
+ }
+ binding_path += path;
+ }
+}
+
+PackedStringArray OpenXRIPBinding::get_paths() const { // Deprecated, but needed for converting old action maps.
+ // Fallback logic, return an array.
+ // If we just loaded an old action map from disc, this will be a comma separated list of actions.
+ // Once parsed there should be only one path in our array.
+ PackedStringArray paths = binding_path.split(",", false);
+
return paths;
}
-void OpenXRIPBinding::parse_paths(const String p_paths) {
- paths = p_paths.split(",", false);
- emit_changed();
+int OpenXRIPBinding::get_path_count() const { // Deprecated.
+ // Fallback logic, we only have one entry.
+ return binding_path.is_empty() ? 0 : 1;
}
-bool OpenXRIPBinding::has_path(const String p_path) const {
- return paths.has(p_path);
+bool OpenXRIPBinding::has_path(const String p_path) const { // Deprecated.
+ // Fallback logic, return true if this is our path.
+ return binding_path == p_path;
}
-void OpenXRIPBinding::add_path(const String p_path) {
- if (!paths.has(p_path)) {
- paths.push_back(p_path);
+void OpenXRIPBinding::add_path(const String p_path) { // Deprecated.
+ // Fallback logic, only assign first time this is called.
+ if (binding_path != p_path) {
+ ERR_FAIL_COND_MSG(!binding_path.is_empty(), "Method add_path has been deprecated. A binding path was already set, create separate binding resources for each path and use set_binding_path instead.");
+
+ binding_path = p_path;
emit_changed();
}
}
-void OpenXRIPBinding::remove_path(const String p_path) {
- if (paths.has(p_path)) {
- paths.erase(p_path);
- emit_changed();
- }
+void OpenXRIPBinding::remove_path(const String p_path) { // Deprecated.
+ ERR_FAIL_COND_MSG(binding_path != p_path, "Method remove_path has been deprecated. Attempt at removing a different binding path, remove the correct binding record from the interaction profile instead.");
+
+ // Fallback logic, clear if this is our path.
+ binding_path = p_path;
+ emit_changed();
}
+#endif // DISABLE_DEPRECATED
+
OpenXRIPBinding::~OpenXRIPBinding() {
action.unref();
}
@@ -151,9 +184,18 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding(int p_index) const {
}
void OpenXRInteractionProfile::set_bindings(Array p_bindings) {
- // TODO add check here that our bindings don't contain duplicate actions
+ bindings.clear();
+
+ for (Ref<OpenXRIPBinding> binding : p_bindings) {
+ String binding_path = binding->get_binding_path();
+ if (binding_path.find_char(',') >= 0) {
+ // Convert old binding approach to new...
+ add_new_binding(binding->get_action(), binding_path);
+ } else {
+ add_binding(binding);
+ }
+ }
- bindings = p_bindings;
emit_changed();
}
@@ -161,10 +203,9 @@ Array OpenXRInteractionProfile::get_bindings() const {
return bindings;
}
-Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<OpenXRAction> p_action) const {
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->get_action() == p_action) {
+Ref<OpenXRIPBinding> OpenXRInteractionProfile::find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action && binding->get_binding_path() == p_binding_path) {
return binding;
}
}
@@ -172,11 +213,23 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<
return Ref<OpenXRIPBinding>();
}
+Vector<Ref<OpenXRIPBinding>> OpenXRInteractionProfile::get_bindings_for_action(const Ref<OpenXRAction> p_action) const {
+ Vector<Ref<OpenXRIPBinding>> ret_bindings;
+
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action) {
+ ret_bindings.push_back(binding);
+ }
+ }
+
+ return ret_bindings;
+}
+
void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) {
ERR_FAIL_COND(p_binding.is_null());
if (!bindings.has(p_binding)) {
- ERR_FAIL_COND_MSG(get_binding_for_action(p_binding->get_action()).is_valid(), "There is already a binding for this action in this interaction profile");
+ ERR_FAIL_COND_MSG(find_binding(p_binding->get_action(), p_binding->get_binding_path()).is_valid(), "There is already a binding for this action and binding path in this interaction profile.");
bindings.push_back(p_binding);
emit_changed();
@@ -191,11 +244,15 @@ void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) {
}
}
-void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths) {
// This is a helper function to help build our default action sets
- Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths);
- add_binding(binding);
+ PackedStringArray paths = p_paths.split(",", false);
+
+ for (const String &path : paths) {
+ Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, path);
+ add_binding(binding);
+ }
}
void OpenXRInteractionProfile::remove_binding_for_action(const Ref<OpenXRAction> p_action) {
diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h
index 479cc3c527..952f87a09d 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.h
+++ b/modules/openxr/action_map/openxr_interaction_profile.h
@@ -41,26 +41,29 @@ class OpenXRIPBinding : public Resource {
private:
Ref<OpenXRAction> action;
- PackedStringArray paths;
+ String binding_path;
protected:
static void _bind_methods();
public:
- static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Helper function for adding a new binding
+ static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path); // Helper function for adding a new binding.
- void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding
- Ref<OpenXRAction> get_action() const; // Get the action for this binding
+ void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding.
+ Ref<OpenXRAction> get_action() const; // Get the action for this binding.
- int get_path_count() const; // Get the number of io paths
- void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource)
- PackedStringArray get_paths() const; // Get our paths (for saving to resource)
+ void set_binding_path(const String &path);
+ String get_binding_path() const;
- void parse_paths(const String p_paths); // Parse a comma separated string of io paths.
-
- bool has_path(const String p_path) const; // Has this io path
- void add_path(const String p_path); // Add an io path
- void remove_path(const String p_path); // Remove an io path
+ // Deprecated.
+#ifndef DISABLE_DEPRECATED
+ void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource), needed for loading old action maps.
+ PackedStringArray get_paths() const; // Get our paths (for saving to resource), needed for converted old action maps.
+ int get_path_count() const; // Get the number of io paths.
+ bool has_path(const String p_path) const; // Has this io path.
+ void add_path(const String p_path); // Add an io path.
+ void remove_path(const String p_path); // Remove an io path.
+#endif // DISABLE_DEPRECATED
// TODO add validation that we can display in the interface that checks if no two paths belong to the same top level path
@@ -88,11 +91,12 @@ public:
void set_bindings(Array p_bindings); // Set the bindings (for loading from a resource)
Array get_bindings() const; // Get the bindings (for saving to a resource)
- Ref<OpenXRIPBinding> get_binding_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
+ Ref<OpenXRIPBinding> find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const; // Get our binding record
+ Vector<Ref<OpenXRIPBinding>> get_bindings_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
void add_binding(Ref<OpenXRIPBinding> p_binding); // Add a binding object
void remove_binding(Ref<OpenXRIPBinding> p_binding); // Remove a binding object
- void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Create a new binding for this profile
+ void add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths); // Create a new binding for this profile
void remove_binding_for_action(const Ref<OpenXRAction> p_action); // Remove all bindings for this action
bool has_binding_for_action(const Ref<OpenXRAction> p_action); // Returns true if we have a binding for this action
diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml
index f274f0868e..ddd6fbe268 100644
--- a/modules/openxr/doc_classes/OpenXRIPBinding.xml
+++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml
@@ -4,32 +4,32 @@
Defines a binding between an [OpenXRAction] and an XR input or output.
</brief_description>
<description>
- This binding resource binds an [OpenXRAction] to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger".
+ This binding resource binds an [OpenXRAction] to an input or output. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". This would require two binding entries.
</description>
<tutorials>
</tutorials>
<methods>
- <method name="add_path">
+ <method name="add_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
Add an input/output path to this binding.
</description>
</method>
- <method name="get_path_count" qualifiers="const">
+ <method name="get_path_count" qualifiers="const" deprecated="Binding is for a single path.">
<return type="int" />
<description>
Get the number of input/output paths in this binding.
</description>
</method>
- <method name="has_path" qualifiers="const">
+ <method name="has_path" qualifiers="const" deprecated="Binding is for a single path.">
<return type="bool" />
<param index="0" name="path" type="String" />
<description>
Returns [code]true[/code] if this input/output path is part of this binding.
</description>
</method>
- <method name="remove_path">
+ <method name="remove_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
@@ -39,9 +39,13 @@
</methods>
<members>
<member name="action" type="OpenXRAction" setter="set_action" getter="get_action">
- [OpenXRAction] that is bound to these paths.
+ [OpenXRAction] that is bound to [member binding_path].
</member>
- <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()">
+ <member name="binding_path" type="String" setter="set_binding_path" getter="get_binding_path" default="&quot;&quot;">
+ Binding path that defines the input or output bound to [member action].
+ [b]Note:[/b] Binding paths are suggestions, an XR runtime may choose to bind the action to a different input or output emulating this input or output.
+ </member>
+ <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" deprecated="Use [member binding_path] instead.">
Paths that define the inputs or outputs bound on the device.
</member>
</members>
diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
index 651171358c..09a9a990ed 100644
--- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp
+++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
@@ -73,17 +73,19 @@ void OpenXRInteractionProfileEditorBase::_add_binding(const String p_action, con
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_null()) {
// create a new binding
binding.instantiate();
binding->set_action(action);
+ binding->set_binding_path(p_path);
+
+ // add it to our interaction profile
interaction_profile->add_binding(binding);
interaction_profile->set_edited(true);
- }
- binding->add_path(p_path);
- binding->set_edited(true);
+ binding->set_edited(true);
+ }
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -98,15 +100,10 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_valid()) {
- binding->remove_path(p_path);
- binding->set_edited(true);
-
- if (binding->get_path_count() == 0) {
- interaction_profile->remove_binding(binding);
- interaction_profile->set_edited(true);
- }
+ interaction_profile->remove_binding(binding);
+ interaction_profile->set_edited(true);
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -116,21 +113,22 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
}
void OpenXRInteractionProfileEditorBase::remove_all_bindings_for_action(Ref<OpenXRAction> p_action) {
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(p_action);
- if (binding.is_valid()) {
+ Vector<Ref<OpenXRIPBinding>> bindings = interaction_profile->get_bindings_for_action(p_action);
+ if (bindings.size() > 0) {
String action_name = p_action->get_name_with_set();
// for our undo/redo we process all paths
undo_redo->create_action(TTR("Remove action from interaction profile"));
- PackedStringArray paths = binding->get_paths();
- for (const String &path : paths) {
- undo_redo->add_do_method(this, "_remove_binding", action_name, path);
- undo_redo->add_undo_method(this, "_add_binding", action_name, path);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ undo_redo->add_do_method(this, "_remove_binding", action_name, binding->get_binding_path());
+ undo_redo->add_undo_method(this, "_add_binding", action_name, binding->get_binding_path());
}
undo_redo->commit_action(false);
// but we take a shortcut here :)
- interaction_profile->remove_binding(binding);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ interaction_profile->remove_binding(binding);
+ }
interaction_profile->set_edited(true);
// Update our toplevel paths
@@ -228,9 +226,8 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
if (interaction_profile.is_valid()) {
String io_path = String(p_io_path->openxr_path);
Array bindings = interaction_profile->get_bindings();
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->has_path(io_path)) {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_binding_path() == io_path) {
Ref<OpenXRAction> action = binding->get_action();
HBoxContainer *action_hb = memnew(HBoxContainer);
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 73ac529537..500a58acc3 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -300,10 +300,7 @@ void OpenXRInterface::_load_action_map() {
continue;
}
- PackedStringArray paths = xr_binding->get_paths();
- for (int k = 0; k < paths.size(); k++) {
- openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]);
- }
+ openxr_api->interaction_profile_add_binding(ip, action->action_rid, xr_binding->get_binding_path());
}
// Now submit our suggestions
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index 697369d516..bc429e4632 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -56,6 +56,10 @@ OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_c
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
+ if (openxr_api) {
+ openxr_session_running = openxr_api->is_running();
+ }
+
Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR");
if (openxr_interface.is_valid()) {
openxr_interface->connect("session_begun", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_begun));
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index d903137195..b9d493b844 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -104,51 +104,33 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
picture->size(width, height);
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- // Note: memalloc here, be sure to memfree before any return.
- uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
+ Vector<uint8_t> buffer;
+ buffer.resize(sizeof(uint32_t) * width * height);
- tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
+ tvg::Result res = sw_canvas->target((uint32_t *)buffer.ptrw(), width, width, height, tvg::SwCanvas::ABGR8888S);
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
}
res = sw_canvas->push(std::move(picture));
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't insert ThorVG picture on canvas.");
}
res = sw_canvas->draw();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas.");
}
res = sw_canvas->sync();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't sync ThorVG canvas.");
}
- Vector<uint8_t> image;
- image.resize(width * height * sizeof(uint32_t));
-
- for (uint32_t y = 0; y < height; y++) {
- for (uint32_t x = 0; x < width; x++) {
- uint32_t n = buffer[y * width + x];
- const size_t offset = sizeof(uint32_t) * width * y + sizeof(uint32_t) * x;
- image.write[offset + 0] = (n >> 16) & 0xff;
- image.write[offset + 1] = (n >> 8) & 0xff;
- image.write[offset + 2] = n & 0xff;
- image.write[offset + 3] = (n >> 24) & 0xff;
- }
- }
+ p_image->set_data(width, height, false, Image::FORMAT_RGBA8, buffer);
res = sw_canvas->clear(true);
- memfree(buffer);
- p_image->set_data(width, height, false, Image::FORMAT_RGBA8, image);
return OK;
}
diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp
index e2bb89811c..b205dbbbf2 100644
--- a/modules/tga/image_loader_tga.cpp
+++ b/modules/tga/image_loader_tga.cpp
@@ -32,6 +32,7 @@
#include "core/error/error_macros.h"
#include "core/io/file_access_memory.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index d964fd7627..2725a61a82 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -31,6 +31,7 @@
#include "video_stream_theora.h"
#include "core/config/project_settings.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "scene/resources/image_texture.h"
diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp
index 31c1e06dee..b0a977f647 100644
--- a/modules/tinyexr/image_saver_tinyexr.cpp
+++ b/modules/tinyexr/image_saver_tinyexr.cpp
@@ -31,6 +31,7 @@
#include "image_saver_tinyexr.h"
#include "core/math/math_funcs.h"
+#include "core/os/os.h"
#include <zlib.h> // Should come before including tinyexr.
diff --git a/modules/tinyexr/image_saver_tinyexr.h b/modules/tinyexr/image_saver_tinyexr.h
index 058eeae58e..014c2f2e19 100644
--- a/modules/tinyexr/image_saver_tinyexr.h
+++ b/modules/tinyexr/image_saver_tinyexr.h
@@ -31,7 +31,7 @@
#ifndef IMAGE_SAVER_TINYEXR_H
#define IMAGE_SAVER_TINYEXR_H
-#include "core/os/os.h"
+#include "core/io/image.h"
Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale);
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 0a10754e24..233e74364f 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -171,9 +171,7 @@ def configure(env: "SConsEnvironment"):
env["AS"] = compiler_path + "/clang"
env.Append(
- CCFLAGS=(
- "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
- )
+ CCFLAGS=("-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden".split())
)
if get_min_sdk_version(env["ndk_platform"]) >= 24:
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index 2fe5539e56..8c8bca2b7c 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -577,7 +577,7 @@
Allows an application to write to the user dictionary.
</member>
<member name="screen/immersive_mode" type="bool" setter="" getter="">
- If [code]true[/code], hides navigation and status bar.
+ If [code]true[/code], hides navigation and status bar. See [method DisplayServer.window_set_mode] to toggle it at runtime.
</member>
<member name="screen/support_large" type="bool" setter="" getter="">
Indicates whether the application supports larger screen form-factors.
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 7e1d626486..15e80f824d 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -35,6 +35,7 @@
#include "godot_plugin_config.h"
#endif // DISABLE_DEPRECATED
+#include "core/io/image.h"
#include "core/io/zip_io.h"
#include "core/os/os.h"
#include "editor/export/editor_export_platform.h"
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 20a3a996bc..317dbd3f4a 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -134,7 +134,7 @@ def configure(env: "SConsEnvironment"):
elif env["arch"] == "arm64":
env.Append(
CCFLAGS=(
- "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ "-fobjc-arc -arch arm64 -fmessage-length=0"
" -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
" -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies"
" -isysroot $IOS_SDK_PATH".split()
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index d88d347359..4ded2f3301 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -34,6 +34,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "core/os/os.h"
diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js
index 00616bc1a5..eeb3978256 100644
--- a/platform/web/js/libs/library_godot_fetch.js
+++ b/platform/web/js/libs/library_godot_fetch.js
@@ -59,7 +59,12 @@ const GodotFetch = {
});
obj.status = response.status;
obj.response = response;
- obj.reader = response.body.getReader();
+ // `body` can be null per spec (for example, in cases where the request method is HEAD).
+ // As of the time of writing, Chromium (127.0.6533.72) does not follow the spec but Firefox (131.0.3) does.
+ // See godotengine/godot#76825 for more information.
+ // See Chromium revert (of the change to follow the spec):
+ // https://chromium.googlesource.com/chromium/src/+/135354b7bdb554cd03c913af7c90aceead03c4d4
+ obj.reader = response.body?.getReader();
obj.chunked = chunked;
},
@@ -121,6 +126,10 @@ const GodotFetch = {
}
obj.reading = true;
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
+ } else if (obj.reader == null && obj.response.body == null) {
+ // Emulate a stream closure to maintain the request lifecycle.
+ obj.reading = true;
+ GodotFetch.onread(id, { value: undefined, done: true });
}
},
},
@@ -159,7 +168,10 @@ const GodotFetch = {
if (!obj.response) {
return 0;
}
- if (obj.reader) {
+ // If the reader is nullish, but there is no body, and the request is not marked as done,
+ // the same status should be returned as though the request is currently being read
+ // so that the proper lifecycle closure can be handled in `read()`.
+ if (obj.reader || (obj.response.body == null && !obj.done)) {
return 1;
}
if (obj.done) {
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 0ee52a09a7..4b1fde99cf 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -483,9 +483,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
else:
print_warning("Missing environment variable: WindowsSdkDir")
- if int(env["target_win_version"], 16) < 0x0601:
- print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
- sys.exit(255)
+ validate_win_version(env)
env.AppendUnique(
CPPDEFINES=[
@@ -549,15 +547,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
LIBS += ["vulkan"]
if env["d3d12"]:
- # Check whether we have d3d12 dependencies installed.
- if not os.path.exists(env["mesa_libs"]):
- print_error(
- "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
- "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
- "See the documentation for more information:\n\t"
- "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
- )
- sys.exit(255)
+ check_d3d12_installed(env)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
LIBS += ["dxgi", "dxguid"]
@@ -820,9 +810,7 @@ def configure_mingw(env: "SConsEnvironment"):
## Compile flags
- if int(env["target_win_version"], 16) < 0x0601:
- print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
- sys.exit(255)
+ validate_win_version(env)
if not env["use_llvm"]:
env.Append(CCFLAGS=["-mwindows"])
@@ -900,15 +888,7 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(LIBS=["vulkan"])
if env["d3d12"]:
- # Check whether we have d3d12 dependencies installed.
- if not os.path.exists(env["mesa_libs"]):
- print_error(
- "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
- "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
- "See the documentation for more information:\n\t"
- "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
- )
- sys.exit(255)
+ check_d3d12_installed(env)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
env.Append(LIBS=["dxgi", "dxguid"])
@@ -984,3 +964,20 @@ def configure(env: "SConsEnvironment"):
else: # MinGW
configure_mingw(env)
+
+
+def check_d3d12_installed(env):
+ if not os.path.exists(env["mesa_libs"]):
+ print_error(
+ "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
+ "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
+ "See the documentation for more information:\n\t"
+ "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
+ )
+ sys.exit(255)
+
+
+def validate_win_version(env):
+ if int(env["target_win_version"], 16) < 0x0601:
+ print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
+ sys.exit(255)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index ffa3840181..c427596829 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -67,6 +67,18 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#endif
+#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
+#define DWMWA_WINDOW_CORNER_PREFERENCE 33
+#endif
+
+#ifndef DWMWCP_DEFAULT
+#define DWMWCP_DEFAULT 0
+#endif
+
+#ifndef DWMWCP_DONOTROUND
+#define DWMWCP_DONOTROUND 1
+#endif
+
#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1)
#if defined(__GNUC__)
@@ -1483,6 +1495,9 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.always_on_top = true;
}
+ if (p_flags & WINDOW_FLAG_SHARP_CORNERS_BIT) {
+ wd.sharp_corners = true;
+ }
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
wd.no_focus = true;
}
@@ -2297,6 +2312,12 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.always_on_top = p_enabled;
_update_window_style(p_window);
} break;
+ case WINDOW_FLAG_SHARP_CORNERS: {
+ wd.sharp_corners = p_enabled;
+ DWORD value = wd.sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(wd.hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ _update_window_style(p_window);
+ } break;
case WINDOW_FLAG_TRANSPARENT: {
if (p_enabled) {
// Enable per-pixel alpha.
@@ -3994,6 +4015,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
native_menu->_menu_activate(HMENU(lParam), (int)wParam);
} break;
case WM_CREATE: {
+ {
+ DWORD value = windows[window_id].sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ }
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
@@ -5645,6 +5670,12 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
wd_transient_parent->transient_children.insert(id);
}
+ wd.sharp_corners = p_flags & WINDOW_FLAG_SHARP_CORNERS_BIT;
+ {
+ DWORD value = wd.sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(wd.hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ }
+
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
@@ -6369,7 +6400,10 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID);
- ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window.");
+ if (main_window == INVALID_WINDOW_ID) {
+ r_error = ERR_UNAVAILABLE;
+ ERR_FAIL_MSG("Failed to create main window.");
+ }
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 7d6a3e96a6..fc72e05b1d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -38,6 +38,7 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "drivers/unix/ip_unix.h"
#include "drivers/wasapi/audio_driver_wasapi.h"
@@ -473,6 +474,7 @@ class DisplayServerWindows : public DisplayServer {
bool exclusive = false;
bool context_created = false;
bool mpass = false;
+ bool sharp_corners = false;
// Used to transfer data between events using timer.
WPARAM saved_wparam;
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
index 235a4b332a..09e4640b40 100644
--- a/platform/windows/native_menu_windows.h
+++ b/platform/windows/native_menu_windows.h
@@ -31,6 +31,7 @@
#ifndef NATIVE_MENU_WINDOWS_H
#define NATIVE_MENU_WINDOWS_H
+#include "core/io/image.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
#include "servers/display/native_menu.h"
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 5813ab02e3..7d645ba448 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -167,17 +167,16 @@ void Path2D::_curve_changed() {
return;
}
- if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
- return;
- }
-
- queue_redraw();
for (int i = 0; i < get_child_count(); i++) {
PathFollow2D *follow = Object::cast_to<PathFollow2D>(get_child(i));
if (follow) {
follow->path_changed();
}
}
+
+ if (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_paths_hint()) {
+ queue_redraw();
+ }
}
void Path2D::set_curve(const Ref<Curve2D> &p_curve) {
diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp
index 7b70986adc..2d18e62b10 100644
--- a/scene/3d/light_3d.cpp
+++ b/scene/3d/light_3d.cpp
@@ -146,6 +146,15 @@ bool Light3D::get_shadow_reverse_cull_face() const {
return reverse_cull;
}
+void Light3D::set_shadow_caster_mask(uint32_t p_caster_mask) {
+ shadow_caster_mask = p_caster_mask;
+ RS::get_singleton()->light_set_shadow_caster_mask(light, shadow_caster_mask);
+}
+
+uint32_t Light3D::get_shadow_caster_mask() const {
+ return shadow_caster_mask;
+}
+
AABB Light3D::get_aabb() const {
if (type == RenderingServer::LIGHT_DIRECTIONAL) {
return AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2));
@@ -300,7 +309,7 @@ bool Light3D::is_editor_only() const {
}
void Light3D::_validate_property(PropertyInfo &p_property) const {
- if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow")) {
+ if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow" || p_property.name == "shadow_caster_mask")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
@@ -354,6 +363,9 @@ void Light3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_reverse_cull_face", "enable"), &Light3D::set_shadow_reverse_cull_face);
ClassDB::bind_method(D_METHOD("get_shadow_reverse_cull_face"), &Light3D::get_shadow_reverse_cull_face);
+ ClassDB::bind_method(D_METHOD("set_shadow_caster_mask", "caster_mask"), &Light3D::set_shadow_caster_mask);
+ ClassDB::bind_method(D_METHOD("get_shadow_caster_mask"), &Light3D::get_shadow_caster_mask);
+
ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &Light3D::set_bake_mode);
ClassDB::bind_method(D_METHOD("get_bake_mode"), &Light3D::get_bake_mode);
@@ -388,6 +400,7 @@ void Light3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_OPACITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BLUR);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_caster_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_shadow_caster_mask", "get_shadow_caster_mask");
ADD_GROUP("Distance Fade", "distance_fade_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h
index d6eca8d8b6..5f549469c6 100644
--- a/scene/3d/light_3d.h
+++ b/scene/3d/light_3d.h
@@ -75,6 +75,7 @@ private:
bool negative = false;
bool reverse_cull = false;
uint32_t cull_mask = 0;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade_enabled = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -136,6 +137,9 @@ public:
void set_shadow_reverse_cull_face(bool p_enable);
bool get_shadow_reverse_cull_face() const;
+ void set_shadow_caster_mask(uint32_t p_caster_mask);
+ uint32_t get_shadow_caster_mask() const;
+
void set_bake_mode(BakeMode p_mode);
BakeMode get_bake_mode() const;
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index a1f32fccbe..012ef7860d 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1602,19 +1602,16 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
PackedStringArray LightmapGI::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
-#ifndef MODULE_LIGHTMAPPER_RD_ENABLED
-#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
- warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
-#else
- warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
-#endif
- return warnings;
-#endif
-
+#ifdef MODULE_LIGHTMAPPER_RD_ENABLED
if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
return warnings;
}
+#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+ warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
+#else
+ warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
+#endif
return warnings;
}
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 0a5f2ec6c7..9df4bfde92 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -296,19 +296,12 @@ void Button::_notification(int p_what) {
}
} break;
case DRAW_HOVER_PRESSED: {
- // Edge case for CheckButton and CheckBox.
- if (has_theme_stylebox("hover_pressed")) {
- if (has_theme_color(SNAME("font_hover_pressed_color"))) {
- font_color = theme_cache.font_hover_pressed_color;
- }
- if (has_theme_color(SNAME("icon_hover_pressed_color"))) {
- icon_modulate_color = theme_cache.icon_hover_pressed_color;
- }
-
- break;
+ font_color = theme_cache.font_hover_pressed_color;
+ if (has_theme_color(SNAME("icon_hover_pressed_color"))) {
+ icon_modulate_color = theme_cache.icon_hover_pressed_color;
}
- }
- [[fallthrough]];
+
+ } break;
case DRAW_PRESSED: {
if (has_theme_color(SNAME("font_pressed_color"))) {
font_color = theme_cache.font_pressed_color;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index fe4c91cb56..5532176b2d 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -245,21 +245,7 @@ void ColorPicker::finish_shaders() {
}
void ColorPicker::set_focus_on_line_edit() {
- bool has_hardware_keyboard = true;
-#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
- has_hardware_keyboard = DisplayServer::get_singleton()->has_hardware_keyboard();
-#endif // ANDROID_ENABLED || IOS_ENABLED
- if (has_hardware_keyboard) {
- callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
- } else {
- // A hack to avoid showing the virtual keyboard when the ColorPicker window popups and
- // no hardware keyboard is detected on Android and IOS.
- // This will only focus the LineEdit without editing, the virtual keyboard will only be visible when
- // we touch the LineEdit to enter edit mode.
- callable_mp(c_text, &LineEdit::set_editable).call_deferred(false);
- callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
- callable_mp(c_text, &LineEdit::set_editable).call_deferred(true);
- }
+ callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
}
void ColorPicker::_update_controls() {
@@ -1571,19 +1557,16 @@ void ColorPicker::_pick_button_pressed_legacy() {
picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND);
picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input));
- picker_preview = memnew(Panel);
- picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP);
- picker_preview->set_mouse_filter(MOUSE_FILTER_IGNORE);
- picker_window->add_child(picker_preview);
-
picker_preview_label = memnew(Label);
- picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP);
+ picker_preview_label->set_anchors_preset(Control::PRESET_CENTER_TOP);
picker_preview_label->set_text(ETR("Color Picking active"));
- picker_preview->add_child(picker_preview_label);
- picker_preview_style_box = (Ref<StyleBoxFlat>)memnew(StyleBoxFlat);
+ picker_preview_style_box.instantiate();
picker_preview_style_box->set_bg_color(Color(1.0, 1.0, 1.0));
- picker_preview->add_theme_style_override(SceneStringName(panel), picker_preview_style_box);
+ picker_preview_style_box->set_content_margin_all(4.0);
+ picker_preview_label->add_theme_style_override(CoreStringName(normal), picker_preview_style_box);
+
+ picker_window->add_child(picker_preview_label);
}
Rect2i screen_rect;
@@ -1625,7 +1608,7 @@ void ColorPicker::_pick_button_pressed_legacy() {
}
picker_window->set_size(screen_rect.size);
- picker_preview->set_size(screen_rect.size / 10.0); // 10% of size in each axis.
+ picker_preview_label->set_custom_minimum_size(screen_rect.size / 10); // 10% of size in each axis.
picker_window->popup();
}
@@ -1648,7 +1631,7 @@ void ColorPicker::_picker_texture_input(const Ref<InputEvent> &p_event) {
Vector2 ofs = mev->get_position();
picker_color = img->get_pixel(ofs.x, ofs.y);
picker_preview_style_box->set_bg_color(picker_color);
- picker_preview_label->set_self_modulate(picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f));
+ picker_preview_label->add_theme_color_override(SceneStringName(font_color), picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f));
}
}
}
@@ -2103,7 +2086,9 @@ void ColorPickerButton::pressed() {
float v_offset = show_above ? -minsize.y : get_size().y;
popup->set_position(get_screen_position() + Vector2(h_offset, v_offset));
popup->popup();
- picker->set_focus_on_line_edit();
+ if (DisplayServer::get_singleton()->has_hardware_keyboard()) {
+ picker->set_focus_on_line_edit();
+ }
}
void ColorPickerButton::_notification(int p_what) {
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index ad028584b1..59540d9ace 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -130,7 +130,6 @@ private:
Popup *picker_window = nullptr;
// Legacy color picking.
TextureRect *picker_texture_rect = nullptr;
- Panel *picker_preview = nullptr;
Label *picker_preview_label = nullptr;
Ref<StyleBoxFlat> picker_preview_style_box;
Color picker_color;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index c1d197ea9b..5052deb65a 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -3042,7 +3042,7 @@ void Control::set_layout_direction(Control::LayoutDirection p_direction) {
if (data.layout_dir == p_direction) {
return;
}
- ERR_FAIL_INDEX((int)p_direction, 4);
+ ERR_FAIL_INDEX(p_direction, LAYOUT_DIRECTION_MAX);
data.layout_dir = p_direction;
@@ -3115,13 +3115,20 @@ bool Control::is_layout_rtl() const {
String locale = TranslationServer::get_singleton()->get_tool_locale();
const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
}
- } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) {
+ } else if (data.layout_dir == LAYOUT_DIRECTION_APPLICATION_LOCALE) {
if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
const_cast<Control *>(this)->data.is_rtl = true;
} else {
String locale = TranslationServer::get_singleton()->get_tool_locale();
const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
}
+ } else if (data.layout_dir == LAYOUT_DIRECTION_SYSTEM_LOCALE) {
+ if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
+ const_cast<Control *>(this)->data.is_rtl = true;
+ } else {
+ String locale = OS::get_singleton()->get_locale();
+ const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
+ }
} else {
const_cast<Control *>(this)->data.is_rtl = (data.layout_dir == LAYOUT_DIRECTION_RTL);
}
@@ -3574,7 +3581,7 @@ void Control::_bind_methods() {
ADD_GROUP("Layout", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Based on Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), "set_layout_direction", "get_layout_direction");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode");
ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION);
@@ -3723,9 +3730,14 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(ANCHOR_END);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
- BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_APPLICATION_LOCALE);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_RTL);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_SYSTEM_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_MAX);
+#ifndef DISABLE_DEPRECATED
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+#endif // DISABLE_DEPRECATED
BIND_ENUM_CONSTANT(TEXT_DIRECTION_INHERITED);
BIND_ENUM_CONSTANT(TEXT_DIRECTION_AUTO);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 6e1621ce58..6cabf10971 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -140,9 +140,14 @@ public:
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
- LAYOUT_DIRECTION_LOCALE,
+ LAYOUT_DIRECTION_APPLICATION_LOCALE,
LAYOUT_DIRECTION_LTR,
- LAYOUT_DIRECTION_RTL
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_SYSTEM_LOCALE,
+ LAYOUT_DIRECTION_MAX,
+#ifndef DISABLE_DEPRECATED
+ LAYOUT_DIRECTION_LOCALE = LAYOUT_DIRECTION_APPLICATION_LOCALE,
+#endif // DISABLE_DEPRECATED
};
enum TextDirection {
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 46c9c7cccc..9853d699d4 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -510,6 +510,7 @@ void MenuBar::_refresh_menu_names() {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ queue_redraw();
if (is_global && menu_cache[i].submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid);
if (item_idx >= 0) {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 646cd9c70e..6159e26efa 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2165,12 +2165,18 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int skip2 = 0;
+
+ bool is_row_hovered = (!cache.hover_header_row && cache.hover_item == p_item);
+
for (int i = 0; i < columns.size(); i++) {
if (skip2) {
skip2--;
continue;
}
+ bool is_col_hovered = cache.hover_column == i;
+ bool is_cell_hovered = is_row_hovered && is_col_hovered;
+ bool is_cell_button_hovered = is_cell_hovered && cache.hover_button_index_in_column != -1;
int item_width = get_column_width(i);
if (i == 0) {
@@ -2203,6 +2209,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int total_ofs = ofs - theme_cache.offset.x;
+ // If part of the column is beyond the right side of the control due to scrolling, clamp the label width
+ // so that all buttons attached to the cell remain within view.
if (total_ofs + item_width > p_draw_size.width) {
item_width = MAX(buttons_width, p_draw_size.width - total_ofs);
}
@@ -2247,17 +2255,42 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, theme_cache.guide_color, 1);
}
- if (i == 0) {
- if (p_item->cells[0].selected && select_mode == SELECT_ROW) {
+ if (i == 0 && select_mode == SELECT_ROW) {
+ if (p_item->cells[0].selected || is_row_hovered) {
const Rect2 content_rect = _get_content_rect();
Rect2i row_rect = Rect2i(Point2i(content_rect.position.x, item_rect.position.y), Size2i(content_rect.size.x, item_rect.size.y));
if (rtl) {
row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x;
}
- if (has_focus()) {
- theme_cache.selected_focus->draw(ci, row_rect);
+
+ if (p_item->cells[0].selected) {
+ if (has_focus()) {
+ theme_cache.selected_focus->draw(ci, row_rect);
+ } else {
+ theme_cache.selected->draw(ci, row_rect);
+ }
+ } else if (!drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, row_rect);
+ } else {
+ theme_cache.hovered->draw(ci, row_rect);
+ }
+ }
+ }
+ }
+
+ if (select_mode != SELECT_ROW) {
+ Rect2i r = cell_rect;
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
+
+ // Cell hover.
+ if (is_cell_hovered && !p_item->cells[i].selected && !drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, r);
} else {
- theme_cache.selected->draw(ci, row_rect);
+ theme_cache.hovered->draw(ci, r);
}
}
}
@@ -2335,7 +2368,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].custom_color) {
cell_color = p_item->cells[i].color;
} else {
- cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : theme_cache.font_color;
+ bool draw_as_hover = !drop_mode_flags && (select_mode == SELECT_ROW ? is_row_hovered : is_cell_hovered);
+ bool draw_as_hover_dim = draw_as_hover && is_cell_button_hovered;
+ cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : (draw_as_hover_dim ? theme_cache.font_hovered_dimmed_color : (draw_as_hover ? theme_cache.font_hovered_color : theme_cache.font_color));
}
Color font_outline_color = theme_cache.font_outline_color;
@@ -2487,7 +2522,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.size.width -= downarrow->get_width();
if (p_item->cells[i].custom_button) {
- if (cache.hover_item == p_item && cache.hover_cell == i) {
+ if (cache.hover_item == p_item && cache.hover_column == i) {
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
draw_style_box(theme_cache.custom_button_pressed, ir);
} else {
@@ -2508,19 +2543,26 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} break;
}
+ // Draw the buttons inside the cell.
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> button_texture = p_item->cells[i].buttons[j].texture;
Size2 button_size = button_texture->get_size() + theme_cache.button_pressed->get_minimum_size();
Point2i button_ofs = Point2i(ofs + item_width_with_buttons - button_size.width, p_pos.y) - theme_cache.offset + p_draw_ofs;
- if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
- // Being pressed.
+ bool should_draw_pressed = cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled;
+ bool should_draw_hovered = !should_draw_pressed && !drop_mode_flags && cache.hover_item == p_item && cache.hover_column == i && cache.hover_button_index_in_column == j && !p_item->cells[i].buttons[j].disabled;
+
+ if (should_draw_pressed || should_draw_hovered) {
Point2 od = button_ofs;
if (rtl) {
od.x = get_size().width - od.x - button_size.x;
}
- theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ if (should_draw_pressed) {
+ theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ } else {
+ theme_cache.button_hover->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ }
}
button_ofs.y += (label_h - button_size.height) / 2;
@@ -2551,6 +2593,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
+ // Draw the folding arrow.
if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box
Ref<Texture2D> arrow;
@@ -2966,6 +3009,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width = MAX(button_w, MIN(limit_w, col_width));
}
+ // Cell button detection code.
for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = c.buttons[j].texture;
int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width;
@@ -2978,7 +3022,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
// Make sure the click is correct.
- Point2 click_pos = get_global_mouse_position() - get_global_position();
+ const Point2 click_pos = get_local_mouse_position();
if (!get_item_at_position(click_pos)) {
pressed_button = -1;
cache.click_type = Cache::CLICK_NONE;
@@ -3485,7 +3529,11 @@ bool Tree::_scroll(bool p_horizontal, float p_pages) {
double prev_value = scroll->get_value();
scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages);
- return scroll->get_value() != prev_value;
+ bool scroll_happened = scroll->get_value() != prev_value;
+ if (scroll_happened) {
+ _determine_hovered_item();
+ }
+ return scroll_happened;
}
Rect2 Tree::_get_scrollbar_layout_rect() const {
@@ -3710,92 +3758,10 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- Ref<StyleBox> bg = theme_cache.panel_style;
- bool rtl = is_layout_rtl();
-
- Point2 pos = mm->get_position();
- if (rtl) {
- pos.x = get_size().width - pos.x;
- }
- pos -= theme_cache.panel_style->get_offset();
-
- Cache::ClickType old_hover = cache.hover_type;
- int old_index = cache.hover_index;
-
- cache.hover_type = Cache::CLICK_NONE;
- cache.hover_index = 0;
- if (show_column_titles) {
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- pos.x += theme_cache.offset.x;
- int len = 0;
- for (int i = 0; i < columns.size(); i++) {
- len += get_column_width(i);
- if (pos.x < len) {
- cache.hover_type = Cache::CLICK_TITLE;
- cache.hover_index = i;
- break;
- }
- }
- }
- }
-
- if (root) {
- Point2 mpos = mm->get_position();
- if (rtl) {
- mpos.x = get_size().width - mpos.x;
- }
- mpos -= theme_cache.panel_style->get_offset();
- mpos.y -= _get_title_button_height();
- if (mpos.y >= 0) {
- if (h_scroll->is_visible_in_tree()) {
- mpos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- mpos.y += v_scroll->get_value();
- }
-
- TreeItem *old_it = cache.hover_item;
- int old_col = cache.hover_cell;
-
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
-
- if (drop_mode_flags) {
- if (it != drop_mode_over) {
- drop_mode_over = it;
- queue_redraw();
- }
- if (it && section != drop_mode_section) {
- drop_mode_section = section;
- queue_redraw();
- }
- }
-
- cache.hover_item = it;
- cache.hover_cell = col;
-
- if (it != old_it || col != old_col) {
- if (old_it && old_col >= old_it->cells.size()) {
- // Columns may have changed since last redraw().
- queue_redraw();
- } else {
- // Only need to update if mouse enters/exits a button
- bool was_over_button = old_it && old_it->cells[old_col].custom_button;
- bool is_over_button = it && it->cells[col].custom_button;
- if (was_over_button || is_over_button) {
- queue_redraw();
- }
- }
- }
- }
- }
-
- // Update if mouse enters/exits columns
- if (cache.hover_type != old_hover || cache.hover_index != old_index) {
- queue_redraw();
- }
+ hovered_pos = mm->get_position();
+ _determine_hovered_item();
+ bool rtl = is_layout_rtl();
if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
/* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
popup_edited_item = popup_pressing_edited_item;
@@ -4080,6 +4046,174 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
+void Tree::_determine_hovered_item() {
+ Ref<StyleBox> bg = theme_cache.panel_style;
+ bool rtl = is_layout_rtl();
+
+ Point2 pos = hovered_pos;
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= theme_cache.panel_style->get_offset();
+
+ bool old_header_row = cache.hover_header_row;
+ int old_header_column = cache.hover_header_column;
+ TreeItem *old_item = cache.hover_item;
+ int old_column = cache.hover_column;
+ int old_button_index_in_column = cache.hover_button_index_in_column;
+
+ // Determine hover on column headers.
+ cache.hover_header_row = false;
+ cache.hover_header_column = 0;
+ if (show_column_titles && is_mouse_hovering) {
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ pos.x += theme_cache.offset.x;
+ int len = 0;
+ for (int i = 0; i < columns.size(); i++) {
+ len += get_column_width(i);
+ if (pos.x < len) {
+ cache.hover_header_row = true;
+ cache.hover_header_column = i;
+ cache.hover_button_index_in_column = -1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Determine hover on rows and items.
+ if (root && is_mouse_hovering) {
+ Point2 mpos = hovered_pos;
+ if (rtl) {
+ mpos.x = get_size().width - mpos.x;
+ }
+ mpos -= theme_cache.panel_style->get_offset();
+ mpos.y -= _get_title_button_height();
+ if (mpos.y >= 0) {
+ if (h_scroll->is_visible_in_tree()) {
+ mpos.x += h_scroll->get_value();
+ }
+ if (v_scroll->is_visible_in_tree()) {
+ mpos.y += v_scroll->get_value();
+ }
+
+ int col, h, section;
+ TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
+
+ // Find possible hovered button in cell.
+ int col_button_index = -1;
+
+ Point2 cpos = mpos;
+
+ if (it) {
+ const TreeItem::Cell &c = it->cells[col];
+ int col_width = get_column_width(col);
+
+ // In the first column, tree nesting indent impacts the leftmost possible buttons position
+ // and the clickable area of the folding arrow.
+ int col_indent = 0;
+ if (col == 0) {
+ col_indent = _get_item_h_offset(it);
+ }
+
+ // Compute total width of buttons block including spacings.
+ int buttons_width = 0;
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ buttons_width += size.width + theme_cache.button_margin;
+ }
+
+ // Adjust when buttons are shifted left into view so that they remain visible even
+ // if part of the cell is beyond the right border due to horizontal scrolling and
+ // a long string in one of the items. This matches the drawing & click handling algorithms
+ // that are based on recursion.
+ int clamped_column_offset = 0;
+ int col_left = 0;
+
+ for (int i = 0; i < col; i++) {
+ int i_col_w = get_column_width(i);
+ cpos.x -= i_col_w;
+ col_left += i_col_w;
+ }
+ col_left -= theme_cache.offset.x;
+
+ // Compute buttons offset that makes them visible, in comparison to what would be their
+ // natural position that would cut them off.
+ if (!rtl) {
+ const Rect2 content_rect = _get_content_rect();
+ int cw = content_rect.size.width;
+ int col_right = col_left + col_width;
+ if (col_right > cw) {
+ clamped_column_offset = col_right - cw - theme_cache.scrollbar_h_separation;
+ int max_clamp_offset = col_width - col_indent - buttons_width;
+ if (clamped_column_offset > max_clamp_offset) {
+ clamped_column_offset = max_clamp_offset;
+ }
+ }
+ }
+ col_width -= clamped_column_offset;
+
+ // Find the actual button under coordinates.
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (cpos.x > col_width - size.width && col_button_index == -1) {
+ col_button_index = j;
+ }
+ col_width -= size.width + theme_cache.button_margin;
+ }
+ }
+
+ if (drop_mode_flags) {
+ if (it != drop_mode_over) {
+ drop_mode_over = it;
+ queue_redraw();
+ }
+ if (it && section != drop_mode_section) {
+ drop_mode_section = section;
+ queue_redraw();
+ }
+ }
+
+ cache.hover_item = it;
+ cache.hover_column = col;
+ cache.hover_button_index_in_column = col_button_index;
+
+ if (it != old_item || col != old_column) {
+ if (old_item && old_column >= old_item->cells.size()) {
+ // Columns may have changed since last redraw().
+ queue_redraw();
+ } else {
+ // Only need to update if mouse enters/exits a button.
+ bool was_over_button = old_item && old_item->cells[old_column].custom_button;
+ bool is_over_button = it && it->cells[col].custom_button;
+ if (was_over_button || is_over_button) {
+ queue_redraw();
+ }
+ }
+ }
+ }
+ }
+
+ // Reduce useless redraw calls.
+
+ bool hovered_cell_button_changed = (cache.hover_button_index_in_column != old_button_index_in_column);
+ bool hovered_column_changed = (cache.hover_column != old_column);
+
+ // Mouse has moved from row to row, or from cell to cell within same row unless selection mode is full row which saves a useless redraw.
+ bool item_hover_needs_redraw = !cache.hover_header_row && (cache.hover_item != old_item || hovered_cell_button_changed || (select_mode != SELECT_ROW && hovered_column_changed));
+ // Mouse has moved between two different column header sections.
+ bool header_hover_needs_redraw = cache.hover_header_row && cache.hover_header_column != old_header_column;
+ // Mouse has moved between header and "main" areas.
+ bool whole_needs_redraw = cache.hover_header_row != old_header_row;
+
+ if (whole_needs_redraw || header_hover_needs_redraw || item_hover_needs_redraw) {
+ queue_redraw();
+ }
+}
+
bool Tree::edit_selected(bool p_force_edit) {
TreeItem *s = get_selected();
ERR_FAIL_NULL_V_MSG(s, false, "No item selected.");
@@ -4304,9 +4438,20 @@ void Tree::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ is_mouse_hovering = true;
+ _determine_hovered_item();
+ } break;
+
case NOTIFICATION_MOUSE_EXIT: {
- if (cache.hover_type != Cache::CLICK_NONE) {
- cache.hover_type = Cache::CLICK_NONE;
+ is_mouse_hovering = false;
+ // Clear hovered item cache.
+ if (cache.hover_header_row || cache.hover_item != nullptr) {
+ cache.hover_header_row = false;
+ cache.hover_header_column = -1;
+ cache.hover_item = nullptr;
+ cache.hover_column = -1;
+ cache.hover_button_index_in_column = -1;
queue_redraw();
}
} break;
@@ -4420,7 +4565,7 @@ void Tree::_notification(int p_what) {
//title buttons
int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT);
for (int i = 0; i < columns.size(); i++) {
- Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button);
+ Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_header_row && cache.hover_header_column == i) ? theme_cache.title_button_hover : theme_cache.title_button);
Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh);
if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
@@ -4536,6 +4681,8 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) {
}
}
+ _determine_hovered_item();
+
return ti;
}
@@ -4679,6 +4826,8 @@ void Tree::clear() {
popup_edited_item = nullptr;
popup_pressing_edited_item = nullptr;
+ _determine_hovered_item();
+
queue_redraw();
};
@@ -4931,6 +5080,7 @@ int Tree::get_columns() const {
}
void Tree::_scroll_moved(float) {
+ _determine_hovered_item();
queue_redraw();
}
@@ -4972,7 +5122,47 @@ int Tree::get_item_offset(TreeItem *p_item) const {
}
}
- return -1; //not found
+ return -1; // Not found.
+}
+
+int Tree::_get_item_h_offset(TreeItem *p_item) const {
+ TreeItem *it = root;
+ int nesting_level = 0;
+ if (!it) {
+ return 0;
+ }
+
+ while (true) {
+ if (it == p_item) {
+ if (!hide_root) {
+ nesting_level += 1;
+ }
+ if (hide_folding) {
+ nesting_level -= 1;
+ }
+ return nesting_level * theme_cache.item_margin;
+ }
+
+ if (it->first_child && !it->collapsed) {
+ it = it->first_child;
+ nesting_level += 1;
+
+ } else if (it->next) {
+ it = it->next;
+ } else {
+ while (!it->next) {
+ it = it->parent;
+ nesting_level -= 1;
+ if (it == nullptr) {
+ return 0;
+ }
+ }
+
+ it = it->next;
+ }
+ }
+
+ return -1; // Not found.
}
void Tree::ensure_cursor_is_visible() {
@@ -5803,10 +5993,13 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT, Tree, tb_font, "title_button_font");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT_SIZE, Tree, tb_font_size, "title_button_font_size");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered_dimmed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor_unfocus, "cursor_unfocused");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_hover);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked);
@@ -5827,6 +6020,8 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, custom_button_font_highlight);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_dimmed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_selected_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 86efdfec52..b417b7ee31 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -450,6 +450,9 @@ private:
Vector2 pressing_pos;
Rect2 pressing_item_rect;
+ Vector2 hovered_pos;
+ bool is_mouse_hovering = false;
+
float range_drag_base = 0.0;
bool range_drag_enabled = false;
Vector2 range_drag_capture_pos;
@@ -545,10 +548,13 @@ private:
int font_size = 0;
int tb_font_size = 0;
+ Ref<StyleBox> hovered;
+ Ref<StyleBox> hovered_dimmed;
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor;
Ref<StyleBox> cursor_unfocus;
+ Ref<StyleBox> button_hover;
Ref<StyleBox> button_pressed;
Ref<StyleBox> title_button;
Ref<StyleBox> title_button_hover;
@@ -572,6 +578,8 @@ private:
Ref<Texture2D> updown;
Color font_color;
+ Color font_hovered_color;
+ Color font_hovered_dimmed_color;
Color font_selected_color;
Color font_disabled_color;
Color guide_color;
@@ -623,16 +631,17 @@ private:
};
ClickType click_type = Cache::CLICK_NONE;
- ClickType hover_type = Cache::CLICK_NONE;
int click_index = -1;
int click_id = -1;
TreeItem *click_item = nullptr;
int click_column = 0;
- int hover_index = -1;
+ int hover_header_column = -1;
+ bool hover_header_row = false;
Point2 click_pos;
TreeItem *hover_item = nullptr;
- int hover_cell = -1;
+ int hover_column = -1;
+ int hover_button_index_in_column = -1;
bool rtl = false;
} cache;
@@ -660,6 +669,7 @@ private:
TreeItem *_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards = false);
TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const;
+ int _get_item_h_offset(TreeItem *p_item) const;
void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const;
@@ -689,6 +699,8 @@ private:
bool enable_recursive_folding = true;
+ void _determine_hovered_item();
+
int _count_selected_items(TreeItem *p_from) const;
bool _is_branch_selected(TreeItem *p_from) const;
bool _is_sibling_branch_selected(TreeItem *p_from) const;
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index c0386b056f..7c8bf9c809 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -1253,7 +1253,7 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_update_transform"), &CanvasItem::force_update_transform);
- ClassDB::bind_method(D_METHOD("make_canvas_position_local", "screen_point"), &CanvasItem::make_canvas_position_local);
+ ClassDB::bind_method(D_METHOD("make_canvas_position_local", "viewport_point"), &CanvasItem::make_canvas_position_local);
ClassDB::bind_method(D_METHOD("make_input_local", "event"), &CanvasItem::make_input_local);
ClassDB::bind_method(D_METHOD("set_visibility_layer", "layer"), &CanvasItem::set_visibility_layer);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 0cdb23618f..8755d5f51e 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1921,7 +1921,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(mpos));
tooltip = tooltip.strip_edges();
- if (tooltip.is_empty() || tooltip != gui.tooltip_text) {
+ if (tooltip != gui.tooltip_text) {
_gui_cancel_tooltip();
} else {
is_tooltip_shown = true;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 803ce89bc9..045c3ae02d 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -2635,7 +2635,7 @@ void Window::set_unparent_when_invisible(bool p_unparent) {
void Window::set_layout_direction(Window::LayoutDirection p_direction) {
ERR_MAIN_THREAD_GUARD;
- ERR_FAIL_INDEX((int)p_direction, 4);
+ ERR_FAIL_INDEX(p_direction, LAYOUT_DIRECTION_MAX);
layout_dir = p_direction;
propagate_notification(Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED);
@@ -2700,13 +2700,20 @@ bool Window::is_layout_rtl() const {
String locale = TranslationServer::get_singleton()->get_tool_locale();
return TS->is_locale_right_to_left(locale);
}
- } else if (layout_dir == LAYOUT_DIRECTION_LOCALE) {
+ } else if (layout_dir == LAYOUT_DIRECTION_APPLICATION_LOCALE) {
if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
return true;
} else {
String locale = TranslationServer::get_singleton()->get_tool_locale();
return TS->is_locale_right_to_left(locale);
}
+ } else if (layout_dir == LAYOUT_DIRECTION_SYSTEM_LOCALE) {
+ if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
+ return true;
+ } else {
+ String locale = OS::get_singleton()->get_locale();
+ return TS->is_locale_right_to_left(locale);
+ }
} else {
return (layout_dir == LAYOUT_DIRECTION_RTL);
}
@@ -3001,6 +3008,7 @@ void Window::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "popup_window"), "set_flag", "get_flag", FLAG_POPUP);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "extend_to_title"), "set_flag", "get_flag", FLAG_EXTEND_TO_TITLE);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "mouse_passthrough"), "set_flag", "get_flag", FLAG_MOUSE_PASSTHROUGH);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "sharp_corners"), "set_flag", "get_flag", FLAG_SHARP_CORNERS);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_native"), "set_force_native", "get_force_native");
ADD_GROUP("Limits", "");
@@ -3054,6 +3062,7 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_POPUP);
BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH);
+ BIND_ENUM_CONSTANT(FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED);
@@ -3070,9 +3079,14 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_INTEGER);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
- BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_APPLICATION_LOCALE);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_RTL);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_SYSTEM_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_MAX);
+#ifndef DISABLE_DEPRECATED
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+#endif // DISABLE_DEPRECATED
BIND_ENUM_CONSTANT(WINDOW_INITIAL_POSITION_ABSOLUTE);
BIND_ENUM_CONSTANT(WINDOW_INITIAL_POSITION_CENTER_PRIMARY_SCREEN);
diff --git a/scene/main/window.h b/scene/main/window.h
index 47aaf73728..6517350b78 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -62,6 +62,7 @@ public:
FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP,
FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE,
FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH,
+ FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS,
FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX,
};
@@ -86,9 +87,14 @@ public:
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
- LAYOUT_DIRECTION_LOCALE,
+ LAYOUT_DIRECTION_APPLICATION_LOCALE,
LAYOUT_DIRECTION_LTR,
- LAYOUT_DIRECTION_RTL
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_SYSTEM_LOCALE,
+ LAYOUT_DIRECTION_MAX,
+#ifndef DISABLE_DEPRECATED
+ LAYOUT_DIRECTION_LOCALE = LAYOUT_DIRECTION_APPLICATION_LOCALE,
+#endif // DISABLE_DEPRECATED
};
enum {
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
index 94a037bd9b..f068e34beb 100644
--- a/scene/property_utils.cpp
+++ b/scene/property_utils.cpp
@@ -89,6 +89,16 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const
*r_is_valid = false;
}
+ // Handle special case "script" property, where the default value is either null or the custom type script.
+ // Do this only if there's no states stack cache to trace for default values.
+ if (!p_states_stack_cache && p_property == CoreStringName(script) && p_object->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> ct_scr = p_object->get_meta(SceneStringName(_custom_type_script));
+ if (r_is_valid) {
+ *r_is_valid = true;
+ }
+ return ct_scr;
+ }
+
Ref<Script> topmost_script;
if (const Node *node = Object::cast_to<Node>(p_object)) {
diff --git a/scene/resources/external_texture.cpp b/scene/resources/external_texture.cpp
index 0552bbd081..c8b714372a 100644
--- a/scene/resources/external_texture.cpp
+++ b/scene/resources/external_texture.cpp
@@ -39,12 +39,14 @@ void ExternalTexture::_bind_methods() {
}
uint64_t ExternalTexture::get_external_texture_id() const {
+ _ensure_created();
return RenderingServer::get_singleton()->texture_get_native_handle(texture);
}
void ExternalTexture::set_size(const Size2 &p_size) {
if (p_size.width > 0 && p_size.height > 0 && p_size != size) {
size = p_size;
+ _ensure_created();
RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer);
emit_changed();
}
@@ -57,6 +59,7 @@ Size2 ExternalTexture::get_size() const {
void ExternalTexture::set_external_buffer_id(uint64_t p_external_buffer) {
if (p_external_buffer != external_buffer) {
external_buffer = p_external_buffer;
+ _ensure_created();
RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer);
}
}
@@ -74,11 +77,29 @@ bool ExternalTexture::has_alpha() const {
}
RID ExternalTexture::get_rid() const {
+ if (!texture.is_valid()) {
+ texture = RenderingServer::get_singleton()->texture_2d_placeholder_create();
+ using_placeholder = true;
+ }
return texture;
}
+void ExternalTexture::_ensure_created() const {
+ if (texture.is_valid() && !using_placeholder) {
+ return;
+ }
+
+ RID new_texture = RenderingServer::get_singleton()->texture_external_create(size.width, size.height);
+ if (using_placeholder) {
+ DEV_ASSERT(texture.is_valid());
+ RenderingServer::get_singleton()->texture_replace(texture, new_texture);
+ using_placeholder = false;
+ } else {
+ texture = new_texture;
+ }
+}
+
ExternalTexture::ExternalTexture() {
- texture = RenderingServer::get_singleton()->texture_external_create(size.width, size.height);
}
ExternalTexture::~ExternalTexture() {
diff --git a/scene/resources/external_texture.h b/scene/resources/external_texture.h
index 96bcd8d0fe..cd60bcc030 100644
--- a/scene/resources/external_texture.h
+++ b/scene/resources/external_texture.h
@@ -38,10 +38,13 @@ class ExternalTexture : public Texture2D {
GDCLASS(ExternalTexture, Texture2D);
private:
- RID texture;
+ mutable RID texture;
+ mutable bool using_placeholder = false;
Size2 size = Size2(256, 256);
uint64_t external_buffer = 0;
+ void _ensure_created() const;
+
protected:
static void _bind_methods();
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index e234a81c88..4a318a10f0 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1431,8 +1431,8 @@ void ResourceFormatLoaderText::get_recognized_extensions_for_type(const String &
p_extensions->push_back("tscn");
}
- // Don't allow .tres for PackedScenes.
- if (p_type != "PackedScene") {
+ // Don't allow .tres for PackedScenes or GDExtension.
+ if (p_type != "PackedScene" && p_type != "GDExtension") {
p_extensions->push_back("tres");
}
}
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 24d17108d5..d163a42fa9 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -32,6 +32,7 @@
#include "shader.compat.inc"
#include "core/io/file_access.h"
+#include "scene/main/scene_tree.h"
#include "servers/rendering/shader_language.h"
#include "servers/rendering/shader_preprocessor.h"
#include "servers/rendering_server.h"
@@ -138,6 +139,14 @@ String Shader::get_code() const {
return code;
}
+void Shader::inspect_native_shader_code() {
+ SceneTree *st = SceneTree::get_singleton();
+ RID _shader = get_rid();
+ if (st && _shader.is_valid()) {
+ st->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_native_shader_source_visualizer", "_inspect_shader", _shader);
+ }
+}
+
void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups) const {
_update_shader();
_check_shader_rid();
@@ -267,6 +276,9 @@ void Shader::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_shader_uniform_list", "get_groups"), &Shader::_get_shader_uniform_list, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("inspect_native_shader_code"), &Shader::inspect_native_shader_code);
+ ClassDB::set_method_flags(get_class_static(), _scs_create("inspect_native_shader_code"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_code", "get_code");
BIND_ENUM_CONSTANT(MODE_SPATIAL);
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 18197419f3..7234d37579 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -88,6 +88,8 @@ public:
void set_code(const String &p_code);
String get_code() const;
+ void inspect_native_shader_code();
+
void get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups = false) const;
void set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index = 0);
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 140e588291..31daeb3ae3 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -130,6 +130,8 @@ SceneStringNames::SceneStringNames() {
shader_overrides_group = StaticCString::create("_shader_overrides_group_");
shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_");
+ _custom_type_script = StaticCString::create("_custom_type_script");
+
pressed = StaticCString::create("pressed");
id_pressed = StaticCString::create("id_pressed");
toggled = StaticCString::create("toggled");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index fc22be33b2..0a2ebeda7a 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -143,6 +143,8 @@ public:
StringName shader_overrides_group;
StringName shader_overrides_group_active;
+ StringName _custom_type_script;
+
StringName pressed;
StringName id_pressed;
StringName toggled;
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index caf44ac392..f5065e8de1 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -30,6 +30,7 @@
#include "default_theme.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "default_font.gen.h"
#include "default_theme_icons.gen.h"
@@ -832,10 +833,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox(SceneStringName(panel), "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_stylebox("focus", "Tree", focus);
+ theme->set_stylebox("hovered", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
+ theme->set_stylebox("hovered_dimmed", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.03)));
theme->set_stylebox("selected", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("selected_focus", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("cursor", "Tree", focus);
theme->set_stylebox("cursor_unfocused", "Tree", focus);
+ theme->set_stylebox("button_hover", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
theme->set_stylebox("button_pressed", "Tree", button_pressed);
theme->set_stylebox("title_button_normal", "Tree", make_flat_stylebox(style_pressed_color, 4, 4, 4, 4));
theme->set_stylebox("title_button_pressed", "Tree", make_flat_stylebox(style_hover_color, 4, 4, 4, 4));
@@ -863,6 +867,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("title_button_color", "Tree", control_font_color);
theme->set_color(SceneStringName(font_color), "Tree", control_font_low_color);
+ theme->set_color("font_hovered_color", "Tree", control_font_hover_color);
+ theme->set_color("font_hovered_dimmed_color", "Tree", control_font_color);
theme->set_color("font_selected_color", "Tree", control_font_pressed_color);
theme->set_color("font_disabled_color", "Tree", control_font_disabled_color);
theme->set_color("font_outline_color", "Tree", Color(0, 0, 0));
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 1886ebe1ac..d400b5790f 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -76,6 +76,42 @@ int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_fra
return ret;
}
+PackedVector2Array AudioStreamPlayback::_mix_audio_bind(float p_rate_scale, int p_frames) {
+ Vector<AudioFrame> frames = mix_audio(p_rate_scale, p_frames);
+
+ PackedVector2Array res;
+ res.resize(frames.size());
+
+ Vector2 *res_ptrw = res.ptrw();
+ for (int i = 0; i < frames.size(); i++) {
+ res_ptrw[i] = Vector2(frames[i].left, frames[i].right);
+ }
+
+ return res;
+}
+
+Vector<AudioFrame> AudioStreamPlayback::mix_audio(float p_rate_scale, int p_frames) {
+ Vector<AudioFrame> res;
+ res.resize(p_frames);
+
+ int frames = mix(res.ptrw(), p_rate_scale, p_frames);
+ res.resize(frames);
+
+ return res;
+}
+
+void AudioStreamPlayback::start_playback(double p_from_pos) {
+ start(p_from_pos);
+}
+
+void AudioStreamPlayback::stop_playback() {
+ stop();
+}
+
+void AudioStreamPlayback::seek_playback(double p_time) {
+ seek(p_time);
+}
+
void AudioStreamPlayback::tag_used_streams() {
GDVIRTUAL_CALL(_tag_used_streams);
}
@@ -108,6 +144,13 @@ void AudioStreamPlayback::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sample_playback", "playback_sample"), &AudioStreamPlayback::set_sample_playback);
ClassDB::bind_method(D_METHOD("get_sample_playback"), &AudioStreamPlayback::get_sample_playback);
+ ClassDB::bind_method(D_METHOD("mix_audio", "rate_scale", "frames"), &AudioStreamPlayback::_mix_audio_bind);
+ ClassDB::bind_method(D_METHOD("start", "from_pos"), &AudioStreamPlayback::start_playback, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("seek", "time"), &AudioStreamPlayback::seek_playback, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayback::stop_playback);
+ ClassDB::bind_method(D_METHOD("get_loop_count"), &AudioStreamPlayback::get_loop_count);
+ ClassDB::bind_method(D_METHOD("get_playback_position"), &AudioStreamPlayback::get_playback_position);
+ ClassDB::bind_method(D_METHOD("is_playing"), &AudioStreamPlayback::is_playing);
}
AudioStreamPlayback::AudioStreamPlayback() {}
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 3feaa53630..65efccdc28 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -83,6 +83,7 @@ class AudioStreamPlayback : public RefCounted {
protected:
static void _bind_methods();
+ PackedVector2Array _mix_audio_bind(float p_rate_scale, int p_frames);
GDVIRTUAL1(_start, double)
GDVIRTUAL0(_stop)
GDVIRTUAL0RC(bool, _is_playing)
@@ -118,6 +119,11 @@ public:
AudioStreamPlayback();
~AudioStreamPlayback();
+
+ Vector<AudioFrame> mix_audio(float p_rate_scale, int p_frames);
+ void start_playback(double p_from_pos = 0.0);
+ void stop_playback();
+ void seek_playback(double p_time);
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index ce0d6cb996..428fb77c74 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1128,6 +1128,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_FLAG_POPUP);
BIND_ENUM_CONSTANT(WINDOW_FLAG_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MOUSE_PASSTHROUGH);
+ BIND_ENUM_CONSTANT(WINDOW_FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX);
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER);
@@ -1229,6 +1230,10 @@ void DisplayServer::_input_set_custom_mouse_cursor_func(const Ref<Resource> &p_i
}
bool DisplayServer::can_create_rendering_device() {
+ if (get_singleton()->get_name() == "headless") {
+ return false;
+ }
+
#if defined(RD_ENABLED)
RenderingDevice *device = RenderingDevice::get_singleton();
if (device) {
diff --git a/servers/display_server.h b/servers/display_server.h
index 36798bd011..f25bf334a4 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -32,6 +32,7 @@
#define DISPLAY_SERVER_H
#include "core/input/input.h"
+#include "core/io/image.h"
#include "core/io/resource.h"
#include "core/os/os.h"
#include "core/variant/callable.h"
@@ -39,7 +40,6 @@
#include "display/native_menu.h"
class Texture2D;
-class Image;
class DisplayServer : public Object {
GDCLASS(DisplayServer, Object)
@@ -381,6 +381,7 @@ public:
WINDOW_FLAG_POPUP,
WINDOW_FLAG_EXTEND_TO_TITLE,
WINDOW_FLAG_MOUSE_PASSTHROUGH,
+ WINDOW_FLAG_SHARP_CORNERS,
WINDOW_FLAG_MAX,
};
@@ -394,6 +395,7 @@ public:
WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP),
WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE),
WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH),
+ WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS),
};
virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID);
diff --git a/servers/movie_writer/movie_writer.h b/servers/movie_writer/movie_writer.h
index e1dc8ef8cf..69d6b1ba2b 100644
--- a/servers/movie_writer/movie_writer.h
+++ b/servers/movie_writer/movie_writer.h
@@ -31,6 +31,7 @@
#ifndef MOVIE_WRITER_H
#define MOVIE_WRITER_H
+#include "core/io/image.h"
#include "core/templates/local_vector.h"
#include "servers/audio/audio_driver_dummy.h"
#include "servers/audio_server.h"
diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h
index c3b63cdbf6..d25523753c 100644
--- a/servers/rendering/dummy/storage/light_storage.h
+++ b/servers/rendering/dummy/storage/light_storage.h
@@ -78,6 +78,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override {}
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override {}
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override {}
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override {}
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override { return 0xFFFFFFFF; }
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override {}
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {}
diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp
index 2dfcd67411..903d73ff2b 100644
--- a/servers/rendering/renderer_rd/environment/fog.cpp
+++ b/servers/rendering/renderer_rd/environment/fog.cpp
@@ -622,6 +622,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
bool any_uses_time = false;
+ Vector3 cam_position = p_cam_transform.get_origin();
for (int i = 0; i < (int)p_fog_volumes.size(); i++) {
FogVolumeInstance *fog_volume_instance = fog_volume_instance_owner.get_or_null(p_fog_volumes[i]);
@@ -652,41 +653,68 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
any_uses_time |= shader_data->uses_time;
- Vector3i min;
- Vector3i max;
+ Vector3i froxel_min;
+ Vector3i froxel_max;
Vector3i kernel_size;
- Vector3 position = fog_volume_instance->transform.get_origin();
+ Vector3 fog_position = fog_volume_instance->transform.get_origin();
RS::FogVolumeShape volume_type = RendererRD::Fog::get_singleton()->fog_volume_get_shape(fog_volume);
Vector3 extents = RendererRD::Fog::get_singleton()->fog_volume_get_size(fog_volume) / 2;
if (volume_type != RS::FOG_VOLUME_SHAPE_WORLD) {
// Local fog volume.
- Vector3i points[8];
Vector3 fog_size = Vector3(fog->width, fog->height, fog->depth);
float volumetric_fog_detail_spread = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_detail_spread(p_settings.env);
- points[0] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[1] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[2] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[3] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[4] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[5] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[6] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[7] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
-
- min = Vector3i(int32_t(fog->width) - 1, int32_t(fog->height) - 1, int32_t(fog->depth) - 1);
- max = Vector3i(1, 1, 1);
-
+ Vector3 corners[8]{
+ fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, -extents.z))
+ };
+ Vector3i froxels[8];
+ Vector3 corner_min = corners[0];
+ Vector3 corner_max = corners[0];
for (int j = 0; j < 8; j++) {
- min = min.min(points[j]);
- max = max.max(points[j]);
+ froxels[j] = _point_get_position_in_froxel_volume(corners[j], fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
+ corner_min = corner_min.min(corners[j]);
+ corner_max = corner_max.max(corners[j]);
+ }
+
+ froxel_min = Vector3i(int32_t(fog->width) - 1, int32_t(fog->height) - 1, int32_t(fog->depth) - 1);
+ froxel_max = Vector3i(1, 1, 1);
+
+ // Tracking just the corners of the fog volume can result in missing some fog:
+ // when the camera's near plane is inside the fog, we must always consider the entire screen
+ Vector3 near_plane_corner(frustum_near_size.x, frustum_near_size.y, z_near);
+ float expand = near_plane_corner.length();
+ if (cam_position.x > (corner_min.x - expand) && cam_position.x < (corner_max.x + expand) &&
+ cam_position.y > (corner_min.y - expand) && cam_position.y < (corner_max.y + expand) &&
+ cam_position.z > (corner_min.z - expand) && cam_position.z < (corner_max.z + expand)) {
+ froxel_min.x = 0;
+ froxel_min.y = 0;
+ froxel_min.z = 0;
+ froxel_max.x = int32_t(fog->width);
+ froxel_max.y = int32_t(fog->height);
+ for (int j = 0; j < 8; j++) {
+ froxel_max.z = MAX(froxel_max.z, froxels[j].z);
+ }
+ } else {
+ // Camera is guaranteed to be outside the fog volume
+ for (int j = 0; j < 8; j++) {
+ froxel_min = froxel_min.min(froxels[j]);
+ froxel_max = froxel_max.max(froxels[j]);
+ }
}
- kernel_size = max - min;
+ kernel_size = froxel_max - froxel_min;
} else {
// Volume type global runs on all cells
extents = Vector3(fog->width, fog->height, fog->depth);
- min = Vector3i(0, 0, 0);
+ froxel_min = Vector3i(0, 0, 0);
kernel_size = Vector3i(int32_t(fog->width), int32_t(fog->height), int32_t(fog->depth));
}
@@ -695,15 +723,15 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
}
VolumetricFogShader::FogPushConstant push_constant;
- push_constant.position[0] = position.x;
- push_constant.position[1] = position.y;
- push_constant.position[2] = position.z;
+ push_constant.position[0] = fog_position.x;
+ push_constant.position[1] = fog_position.y;
+ push_constant.position[2] = fog_position.z;
push_constant.size[0] = extents.x * 2;
push_constant.size[1] = extents.y * 2;
push_constant.size[2] = extents.z * 2;
- push_constant.corner[0] = min.x;
- push_constant.corner[1] = min.y;
- push_constant.corner[2] = min.z;
+ push_constant.corner[0] = froxel_min.x;
+ push_constant.corner[1] = froxel_min.y;
+ push_constant.corner[2] = froxel_min.z;
push_constant.shape = uint32_t(RendererRD::Fog::get_singleton()->fog_volume_get_shape(fog_volume));
RendererRD::MaterialStorage::store_transform(fog_volume_instance->transform.affine_inverse(), push_constant.transform);
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h
index dcd3e90e1b..2547f08715 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.h
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h
@@ -31,6 +31,7 @@
#ifndef RENDERER_COMPOSITOR_RD_H
#define RENDERER_COMPOSITOR_RD_H
+#include "core/io/image.h"
#include "core/os/os.h"
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/renderer_rd/environment/fog.h"
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 0c21fec282..dc2605b670 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -31,6 +31,7 @@
#include "renderer_scene_render_rd.h"
#include "core/config/project_settings.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/environment/fog.h"
diff --git a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
index a797891ab6..90bbdfe685 100644
--- a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
+++ b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
@@ -513,6 +513,7 @@ void main() {
shadow_sample.z = 1.0 + abs(shadow_sample.z);
vec3 pos = vec3(shadow_sample.xy / shadow_sample.z, shadow_len - omni_lights.data[light_index].shadow_bias);
pos.z *= omni_lights.data[light_index].inv_radius;
+ pos.z = 1.0 - pos.z;
pos.xy = pos.xy * 0.5 + 0.5;
pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index 8f71909154..3639b5739b 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -285,6 +285,23 @@ void LightStorage::light_set_reverse_cull_face_mode(RID p_light, bool p_enabled)
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
}
+void LightStorage::light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(light);
+
+ light->shadow_caster_mask = p_caster_mask;
+
+ light->version++;
+ light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
+}
+
+uint32_t LightStorage::light_get_shadow_caster_mask(RID p_light) const {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL_V(light, 0);
+
+ return light->shadow_caster_mask;
+}
+
void LightStorage::light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_NULL(light);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 59303e8a73..80c62a7bc4 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -72,6 +72,7 @@ private:
RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -480,6 +481,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index 9dc606620b..42fce65b2d 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -518,6 +518,32 @@ TextureStorage::TextureStorage() {
rt_sdf.pipelines[i] = RD::get_singleton()->compute_pipeline_create(rt_sdf.shader.version_get_shader(rt_sdf.shader_version, i));
}
}
+
+ // Initialize texture placeholder data for the `texture_*_placeholder_initialize()` methods.
+
+ constexpr int placeholder_size = 4;
+ texture_2d_placeholder = Image::create_empty(placeholder_size, placeholder_size, false, Image::FORMAT_RGBA8);
+ // Draw a magenta/black checkerboard pattern.
+ for (int i = 0; i < placeholder_size * placeholder_size; i++) {
+ const int x = i % placeholder_size;
+ const int y = i / placeholder_size;
+ texture_2d_placeholder->set_pixel(x, y, (x + y) % 2 == 0 ? Color(1, 0, 1) : Color(0, 0, 0));
+ }
+
+ texture_2d_array_placeholder.push_back(texture_2d_placeholder);
+
+ for (int i = 0; i < 6; i++) {
+ cubemap_placeholder.push_back(texture_2d_placeholder);
+ }
+
+ Ref<Image> texture_2d_placeholder_rotated;
+ texture_2d_placeholder_rotated.instantiate();
+ texture_2d_placeholder_rotated->copy_from(texture_2d_placeholder);
+ texture_2d_placeholder_rotated->rotate_90(CLOCKWISE);
+ for (int i = 0; i < 4; i++) {
+ // Alternate checkerboard pattern on odd layers (by using a copy that is rotated 90 degrees).
+ texture_3d_placeholder.push_back(i % 2 == 0 ? texture_2d_placeholder : texture_2d_placeholder_rotated);
+ }
}
TextureStorage::~TextureStorage() {
@@ -1365,46 +1391,19 @@ void TextureStorage::texture_proxy_update(RID p_texture, RID p_proxy_to) {
//these two APIs can be used together or in combination with the others.
void TextureStorage::texture_2d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- texture_2d_initialize(p_texture, image);
+ texture_2d_initialize(p_texture, texture_2d_placeholder);
}
void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RS::TextureLayeredType p_layered_type) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
if (p_layered_type == RS::TEXTURE_LAYERED_2D_ARRAY) {
- images.push_back(image);
+ texture_2d_layered_initialize(p_texture, texture_2d_array_placeholder, p_layered_type);
} else {
- //cube
- for (int i = 0; i < 6; i++) {
- images.push_back(image);
- }
+ texture_2d_layered_initialize(p_texture, cubemap_placeholder, p_layered_type);
}
-
- texture_2d_layered_initialize(p_texture, images, p_layered_type);
}
void TextureStorage::texture_3d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
- //cube
- for (int i = 0; i < 4; i++) {
- images.push_back(image);
- }
-
- texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, images);
+ texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, texture_3d_placeholder);
}
Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
@@ -2269,6 +2268,16 @@ void TextureStorage::_texture_format_from_rd(RD::DataFormat p_rd_format, Texture
r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
} break;
+ case RD::DATA_FORMAT_B8G8R8A8_UNORM:
+ case RD::DATA_FORMAT_B8G8R8A8_SRGB: {
+ r_format.image_format = Image::FORMAT_RGBA8;
+ r_format.rd_format = RD::DATA_FORMAT_B8G8R8A8_UNORM;
+ r_format.rd_format_srgb = RD::DATA_FORMAT_B8G8R8A8_SRGB;
+ r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
+ r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
+ r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
+ r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
+ } break;
case RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16: {
r_format.image_format = Image::FORMAT_RGBA4444;
r_format.rd_format = RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index 2135ee3e3b..866fdd50ac 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -520,6 +520,11 @@ public:
virtual void texture_external_update(RID p_texture, int p_width, int p_height, uint64_t p_external_buffer) override;
virtual void texture_proxy_update(RID p_proxy, RID p_base) override;
+ Ref<Image> texture_2d_placeholder;
+ Vector<Ref<Image>> texture_2d_array_placeholder;
+ Vector<Ref<Image>> cubemap_placeholder;
+ Vector<Ref<Image>> texture_3d_placeholder;
+
//these two APIs can be used together or in combination with the others.
virtual void texture_2d_placeholder_initialize(RID p_texture) override;
virtual void texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) override;
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 8b7ec08868..45f980ab41 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1998,6 +1998,9 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
pair.bvh2 = &p_instance->scenario->indexers[Scenario::INDEXER_VOLUMES];
}
pair.cull_mask = RSG::light_storage->light_get_cull_mask(p_instance->base);
+ } else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
+ pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
+ pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
} else if (geometry_instance_pair_mask & (1 << RS::INSTANCE_REFLECTION_PROBE) && (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE)) {
pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
@@ -2296,6 +2299,7 @@ void RendererSceneCull::_light_instance_setup_directional_shadow(int p_shadow_in
cull.shadow_count = p_shadow_index + 1;
cull.shadows[p_shadow_index].cascade_count = splits;
cull.shadows[p_shadow_index].light_instance = light->instance;
+ cull.shadows[p_shadow_index].caster_mask = RSG::light_storage->light_get_shadow_caster_mask(p_instance->base);
for (int i = 0; i < splits; i++) {
RENDER_TIMESTAMP("Cull DirectionalLight3D, Split " + itos(i));
@@ -2526,7 +2530,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -2608,7 +2612,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -2675,7 +2679,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -3033,6 +3037,10 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
for (const Instance *E : geom->lights) {
InstanceLightData *light = static_cast<InstanceLightData *>(E->base_data);
+ if (!(RSG::light_storage->light_get_cull_mask(E->base) & idata.layer_mask)) {
+ continue;
+ }
+
instance_pair_buffer[idx++] = light->instance;
if (idx == MAX_INSTANCE_PAIRS) {
break;
@@ -3137,7 +3145,7 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
if (IN_FRUSTUM(cull_data.cull->shadows[j].cascades[k].frustum) && VIS_CHECK) {
uint32_t base_type = idata.flags & InstanceData::FLAG_BASE_TYPE_MASK;
- if (((1 << base_type) & RS::INSTANCE_GEOMETRY_MASK) && idata.flags & InstanceData::FLAG_CAST_SHADOWS && LAYER_CHECK) {
+ if (((1 << base_type) & RS::INSTANCE_GEOMETRY_MASK) && idata.flags & InstanceData::FLAG_CAST_SHADOWS && (LAYER_CHECK & cull_data.cull->shadows[j].caster_mask)) {
cull_result.directional_shadows[j].cascade_geometry_instances[k].push_back(idata.instance_geometry);
mesh_visible = true;
}
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 5aae59eb51..d3f03c4789 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1121,6 +1121,7 @@ public:
struct Cull {
struct Shadow {
RID light_instance;
+ uint32_t caster_mask;
struct Cascade {
Frustum frustum;
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index aee23ab476..6eb1386749 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -1243,6 +1243,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
TransferWorker *transfer_worker = nullptr;
const uint8_t *read_ptr = p_data.ptr();
uint8_t *write_ptr = nullptr;
+ const RDD::TextureLayout copy_dst_layout = driver->api_trait_get(RDD::API_TRAIT_USE_GENERAL_IN_COPY_QUEUES) ? RDD::TEXTURE_LAYOUT_GENERAL : RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
for (uint32_t pass = 0; pass < 2; pass++) {
const bool copy_pass = (pass == 1);
if (copy_pass) {
@@ -1267,7 +1268,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
tb.texture = texture->driver_id;
tb.dst_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
tb.prev_layout = RDD::TEXTURE_LAYOUT_UNDEFINED;
- tb.next_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.next_layout = copy_dst_layout;
tb.subresources.aspect = texture->barrier_aspect_flags;
tb.subresources.mipmap_count = texture->mipmaps;
tb.subresources.base_layer = p_layer;
@@ -1313,7 +1314,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
copy_region.texture_subresources.layer_count = 1;
copy_region.texture_offset = Vector3i(0, 0, z);
copy_region.texture_region_size = Vector3i(logic_width, logic_height, 1);
- driver->command_copy_buffer_to_texture(transfer_worker->command_buffer, transfer_worker->staging_buffer, texture->driver_id, RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL, copy_region);
+ driver->command_copy_buffer_to_texture(transfer_worker->command_buffer, transfer_worker->staging_buffer, texture->driver_id, copy_dst_layout, copy_region);
}
staging_local_offset += to_allocate;
@@ -1332,14 +1333,13 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
RDD::TextureBarrier tb;
tb.texture = texture->driver_id;
tb.src_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
- tb.prev_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.prev_layout = copy_dst_layout;
tb.next_layout = RDD::TEXTURE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
tb.subresources.aspect = texture->barrier_aspect_flags;
tb.subresources.mipmap_count = texture->mipmaps;
tb.subresources.base_layer = p_layer;
tb.subresources.layer_count = 1;
-
- driver->command_pipeline_barrier(transfer_worker->command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {}, {}, tb);
+ transfer_worker->texture_barriers.push_back(tb);
}
_release_transfer_worker(transfer_worker);
@@ -5152,6 +5152,21 @@ void RenderingDevice::_wait_for_transfer_worker(TransferWorker *p_transfer_worke
MutexLock lock(p_transfer_worker->operations_mutex);
p_transfer_worker->operations_processed = p_transfer_worker->operations_submitted;
}
+
+ if (!p_transfer_worker->texture_barriers.is_empty()) {
+ MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
+ _flush_barriers_for_transfer_worker(p_transfer_worker);
+ }
+}
+
+void RenderingDevice::_flush_barriers_for_transfer_worker(TransferWorker *p_transfer_worker) {
+ if (!p_transfer_worker->texture_barriers.is_empty()) {
+ for (uint32_t i = 0; i < p_transfer_worker->texture_barriers.size(); i++) {
+ transfer_worker_pool_texture_barriers.push_back(p_transfer_worker->texture_barriers[i]);
+ }
+
+ p_transfer_worker->texture_barriers.clear();
+ }
}
void RenderingDevice::_check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation) {
@@ -5193,11 +5208,11 @@ void RenderingDevice::_check_transfer_worker_index_array(IndexArray *p_index_arr
}
}
-void RenderingDevice::_submit_transfer_workers(bool p_operations_used_by_draw) {
+void RenderingDevice::_submit_transfer_workers(RDD::CommandBufferID p_draw_command_buffer) {
MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
for (uint32_t i = 0; i < transfer_worker_pool.size(); i++) {
TransferWorker *worker = transfer_worker_pool[i];
- if (p_operations_used_by_draw) {
+ if (p_draw_command_buffer) {
MutexLock lock(worker->operations_mutex);
if (worker->operations_processed >= transfer_worker_operation_used_by_draw[worker->index]) {
// The operation used by the draw has already been processed, we don't need to wait on the worker.
@@ -5208,12 +5223,21 @@ void RenderingDevice::_submit_transfer_workers(bool p_operations_used_by_draw) {
{
MutexLock lock(worker->thread_mutex);
if (worker->recording) {
- VectorView<RDD::SemaphoreID> semaphores = p_operations_used_by_draw ? frames[frame].transfer_worker_semaphores[i] : VectorView<RDD::SemaphoreID>();
+ VectorView<RDD::SemaphoreID> semaphores = p_draw_command_buffer ? frames[frame].transfer_worker_semaphores[i] : VectorView<RDD::SemaphoreID>();
_end_transfer_worker(worker);
_submit_transfer_worker(worker, semaphores);
}
+
+ if (p_draw_command_buffer) {
+ _flush_barriers_for_transfer_worker(worker);
+ }
}
}
+
+ if (p_draw_command_buffer && !transfer_worker_pool_texture_barriers.is_empty()) {
+ driver->command_pipeline_barrier(p_draw_command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_ALL_COMMANDS_BIT, {}, {}, transfer_worker_pool_texture_barriers);
+ transfer_worker_pool_texture_barriers.clear();
+ }
}
void RenderingDevice::_wait_for_transfer_workers() {
@@ -5807,10 +5831,10 @@ void RenderingDevice::_end_frame() {
ERR_PRINT("Found open compute list at the end of the frame, this should never happen (further compute will likely not work).");
}
- _submit_transfer_workers(true);
-
// The command buffer must be copied into a stack variable as the driver workarounds can change the command buffer in use.
RDD::CommandBufferID command_buffer = frames[frame].command_buffer;
+ _submit_transfer_workers(command_buffer);
+
draw_graph.end(RENDER_GRAPH_REORDER, RENDER_GRAPH_FULL_BARRIERS, command_buffer, frames[frame].command_buffer_pool);
driver->command_buffer_end(command_buffer);
driver->end_segment();
@@ -6387,7 +6411,7 @@ void RenderingDevice::finalize() {
}
// Wait for transfer workers to finish.
- _submit_transfer_workers(false);
+ _submit_transfer_workers();
_wait_for_transfer_workers();
// Delete everything the graph has created.
@@ -7236,6 +7260,10 @@ void RenderingDevice::_bind_methods() {
BIND_ENUM_CONSTANT(DEBUG_PASS);
}
+void RenderingDevice::make_current() {
+ render_thread_id = Thread::get_caller_id();
+}
+
RenderingDevice::~RenderingDevice() {
finalize();
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index b61418b1fc..c440e11cd4 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1267,6 +1267,7 @@ private:
RDD::CommandBufferID command_buffer;
RDD::CommandPoolID command_pool;
RDD::FenceID command_fence;
+ LocalVector<RDD::TextureBarrier> texture_barriers;
bool recording = false;
bool submitted = false;
BinaryMutex thread_mutex;
@@ -1280,6 +1281,7 @@ private:
uint32_t transfer_worker_pool_max_size = 1;
LocalVector<uint64_t> transfer_worker_operation_used_by_draw;
LocalVector<uint32_t> transfer_worker_pool_available_list;
+ LocalVector<RDD::TextureBarrier> transfer_worker_pool_texture_barriers;
BinaryMutex transfer_worker_pool_mutex;
ConditionVariable transfer_worker_pool_condition;
@@ -1288,12 +1290,13 @@ private:
void _end_transfer_worker(TransferWorker *p_transfer_worker);
void _submit_transfer_worker(TransferWorker *p_transfer_worker, VectorView<RDD::SemaphoreID> p_signal_semaphores = VectorView<RDD::SemaphoreID>());
void _wait_for_transfer_worker(TransferWorker *p_transfer_worker);
+ void _flush_barriers_for_transfer_worker(TransferWorker *p_transfer_worker);
void _check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation);
void _check_transfer_worker_buffer(Buffer *p_buffer);
void _check_transfer_worker_texture(Texture *p_texture);
void _check_transfer_worker_vertex_array(VertexArray *p_vertex_array);
void _check_transfer_worker_index_array(IndexArray *p_index_array);
- void _submit_transfer_workers(bool p_operations_used_by_draw);
+ void _submit_transfer_workers(RDD::CommandBufferID p_draw_command_buffer = RDD::CommandBufferID());
void _wait_for_transfer_workers();
void _free_transfer_workers();
@@ -1493,6 +1496,8 @@ public:
static RenderingDevice *get_singleton();
+ void make_current();
+
RenderingDevice();
~RenderingDevice();
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index d9ca286b15..e41a56b0a3 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -31,7 +31,10 @@
#include "rendering_device_binds.h"
Error RDShaderFile::parse_versions_from_text(const String &p_text, const String p_defines, OpenIncludeFunction p_include_func, void *p_include_func_userdata) {
- ERR_FAIL_NULL_V(RenderingDevice::get_singleton(), ERR_UNAVAILABLE);
+ ERR_FAIL_NULL_V_MSG(
+ RenderingDevice::get_singleton(),
+ ERR_UNAVAILABLE,
+ "Cannot import custom .glsl shaders when running without a RenderingDevice. This can happen if you are using the headless more or the Compatibility backend.");
Vector<String> lines = p_text.split("\n");
diff --git a/servers/rendering/rendering_device_driver.cpp b/servers/rendering/rendering_device_driver.cpp
index 3b8e3efeb8..9ff7b83215 100644
--- a/servers/rendering/rendering_device_driver.cpp
+++ b/servers/rendering/rendering_device_driver.cpp
@@ -374,6 +374,8 @@ uint64_t RenderingDeviceDriver::api_trait_get(ApiTrait p_trait) {
return 1;
case API_TRAIT_CLEARS_WITH_COPY_ENGINE:
return true;
+ case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
+ return false;
default:
ERR_FAIL_V(0);
}
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 91da67c8d7..637d52c060 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -220,6 +220,7 @@ public:
enum TextureLayout {
TEXTURE_LAYOUT_UNDEFINED,
+ TEXTURE_LAYOUT_GENERAL,
TEXTURE_LAYOUT_STORAGE_OPTIMAL,
TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
TEXTURE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
@@ -750,6 +751,7 @@ public:
API_TRAIT_TEXTURE_DATA_ROW_PITCH_STEP,
API_TRAIT_SECONDARY_VIEWPORT_SCISSOR,
API_TRAIT_CLEARS_WITH_COPY_ENGINE,
+ API_TRAIT_USE_GENERAL_IN_COPY_QUEUES,
};
enum ShaderChangeInvalidation {
diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp
index abcb76cd43..0ecd818805 100644
--- a/servers/rendering/rendering_device_graph.cpp
+++ b/servers/rendering/rendering_device_graph.cpp
@@ -34,6 +34,7 @@
#define FORCE_FULL_ACCESS_BITS 0
#define PRINT_RESOURCE_TRACKER_TOTAL 0
#define PRINT_COMMAND_RECORDING 0
+#define INSERT_BREADCRUMBS 1
RenderingDeviceGraph::RenderingDeviceGraph() {
driver_honors_barriers = false;
@@ -438,6 +439,15 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
// Always update the access of the tracker according to the latest usage.
resource_tracker->usage_access = new_usage_access;
+ // Always accumulate the stages of the tracker with the commands that use it.
+ search_tracker->current_frame_stages = search_tracker->current_frame_stages | r_command->self_stages;
+
+ if (!search_tracker->previous_frame_stages.is_empty()) {
+ // Add to the command the stages the tracker was used on in the previous frame.
+ r_command->previous_stages = r_command->previous_stages | search_tracker->previous_frame_stages;
+ search_tracker->previous_frame_stages.clear();
+ }
+
if (different_usage) {
// Even if the usage of the resource isn't a write usage explicitly, a different usage implies a transition and it should therefore be considered a write.
write_usage = true;
@@ -823,7 +833,7 @@ void RenderingDeviceGraph::_run_render_commands(int32_t p_level, const RecordedC
const RecordedDrawListCommand *draw_list_command = reinterpret_cast<const RecordedDrawListCommand *>(command);
const VectorView clear_values(draw_list_command->clear_values(), draw_list_command->clear_values_count);
-#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+#if INSERT_BREADCRUMBS
driver->command_insert_breadcrumb(r_command_buffer, draw_list_command->breadcrumb);
#endif
driver->command_begin_render_pass(r_command_buffer, draw_list_command->render_pass, draw_list_command->framebuffer, draw_list_command->command_buffer_type, draw_list_command->region, clear_values);
@@ -874,7 +884,7 @@ void RenderingDeviceGraph::_run_label_command_change(RDD::CommandBufferID p_comm
}
if (p_ignore_previous_value || p_new_label_index != r_current_label_index || p_new_level != r_current_label_level) {
- if (!p_ignore_previous_value && (p_use_label_for_empty || r_current_label_index >= 0)) {
+ if (!p_ignore_previous_value && (p_use_label_for_empty || r_current_label_index >= 0 || r_current_label_level >= 0)) {
// End the current label.
driver->command_end_label(p_command_buffer);
}
@@ -888,6 +898,8 @@ void RenderingDeviceGraph::_run_label_command_change(RDD::CommandBufferID p_comm
} else if (p_use_label_for_empty) {
label_name = "Command graph";
label_color = Color(1, 1, 1, 1);
+ } else {
+ return;
}
// Add the level to the name.
@@ -2064,7 +2076,7 @@ void RenderingDeviceGraph::end(bool p_reorder_commands, bool p_full_barriers, RD
}
}
- _run_label_command_change(r_command_buffer, -1, -1, true, false, nullptr, 0, current_label_index, current_label_level);
+ _run_label_command_change(r_command_buffer, -1, -1, false, false, nullptr, 0, current_label_index, current_label_level);
#if PRINT_COMMAND_RECORDING
print_line(vformat("Recorded %d commands", command_count));
diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h
index e13e3a0429..0534d4ee1e 100644
--- a/servers/rendering/rendering_device_graph.h
+++ b/servers/rendering/rendering_device_graph.h
@@ -151,6 +151,8 @@ public:
struct ResourceTracker {
uint32_t reference_count = 0;
int64_t command_frame = -1;
+ BitField<RDD::PipelineStageBits> previous_frame_stages;
+ BitField<RDD::PipelineStageBits> current_frame_stages;
int32_t read_full_command_list_index = -1;
int32_t read_slice_command_list_index = -1;
int32_t write_command_or_list_index = -1;
@@ -174,8 +176,9 @@ public:
_FORCE_INLINE_ void reset_if_outdated(int64_t new_command_frame) {
if (new_command_frame != command_frame) {
- usage_access.clear();
command_frame = new_command_frame;
+ previous_frame_stages = current_frame_stages;
+ current_frame_stages.clear();
read_full_command_list_index = -1;
read_slice_command_list_index = -1;
write_command_or_list_index = -1;
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index 20f1f9ad6f..2ec693cbbf 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -370,6 +370,8 @@ Size2i RenderingServerDefault::get_maximum_viewport_size() const {
void RenderingServerDefault::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
server_thread = Thread::get_caller_id();
server_task_id = p_pump_task_id;
+ // This is needed because the main RD is created on the main thread.
+ RenderingDevice::get_singleton()->make_current();
}
void RenderingServerDefault::_thread_exit() {
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 225a67fb52..766ec8fa96 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -443,6 +443,7 @@ public:
FUNC2(light_set_cull_mask, RID, uint32_t)
FUNC5(light_set_distance_fade, RID, bool, float, float, float)
FUNC2(light_set_reverse_cull_face_mode, RID, bool)
+ FUNC2(light_set_shadow_caster_mask, RID, uint32_t)
FUNC2(light_set_bake_mode, RID, LightBakeMode)
FUNC2(light_set_max_sdfgi_cascade, RID, uint32_t)
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index b6770c773c..7c4128b0e3 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -356,7 +356,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_CF_BREAK, "break", CF_BLOCK, {}, {} },
{ TK_CF_CONTINUE, "continue", CF_BLOCK, {}, {} },
{ TK_CF_RETURN, "return", CF_BLOCK, {}, {} },
- { TK_CF_DISCARD, "discard", CF_BLOCK, { "particles", "sky", "fog" }, { "fragment" } },
+ { TK_CF_DISCARD, "discard", CF_BLOCK, { "particles", "sky", "fog" }, { "vertex" } },
// function specifier keywords
@@ -3565,28 +3565,33 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
int argcount = args.size();
- if (p_function_info.stage_functions.has(name)) {
- //stage based function
- const StageFunctionInfo &sf = p_function_info.stage_functions[name];
- if (argcount != sf.arguments.size()) {
- _set_error(vformat(RTR("Invalid number of arguments when calling stage function '%s', which expects %d arguments."), String(name), sf.arguments.size()));
- return false;
- }
- //validate arguments
- for (int i = 0; i < argcount; i++) {
- if (args[i] != sf.arguments[i].type) {
- _set_error(vformat(RTR("Invalid argument type when calling stage function '%s', type expected is '%s'."), String(name), get_datatype_name(sf.arguments[i].type)));
- return false;
- }
- }
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ if (E.value.stage_functions.has(name)) {
+ // Stage-based function.
+ const StageFunctionInfo &sf = E.value.stage_functions[name];
+ if (argcount != sf.arguments.size()) {
+ _set_error(vformat(RTR("Invalid number of arguments when calling stage function '%s', which expects %d arguments."), String(name), sf.arguments.size()));
+ return false;
+ }
+ // Validate arguments.
+ for (int i = 0; i < argcount; i++) {
+ if (args[i] != sf.arguments[i].type) {
+ _set_error(vformat(RTR("Invalid argument type when calling stage function '%s', type expected is '%s'."), String(name), get_datatype_name(sf.arguments[i].type)));
+ return false;
+ }
+ }
- if (r_ret_type) {
- *r_ret_type = sf.return_type;
- }
- if (r_ret_type_str) {
- *r_ret_type_str = "";
+ if (r_ret_type) {
+ *r_ret_type = sf.return_type;
+ }
+ if (r_ret_type_str) {
+ *r_ret_type_str = "";
+ }
+ return true;
+ }
}
- return true;
}
bool failed_builtin = false;
@@ -5937,22 +5942,35 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
calls_info[current_function].calls.push_back(&calls_info[name]);
}
- int idx = 0;
bool is_builtin = false;
- while (frag_only_func_defs[idx].name) {
- if (frag_only_func_defs[idx].name == name) {
- // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
- if (!is_supported_frag_only_funcs) {
- _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
- return nullptr;
+ if (is_supported_frag_only_funcs && stages) {
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ if (E.value.stage_functions.has(name)) {
+ // Register usage of the restricted stage function.
+ calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
+ is_builtin = true;
+ break;
}
- // Register usage of the restricted function.
- calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
- is_builtin = true;
- break;
}
- idx++;
+ }
+
+ if (!is_builtin) {
+ int idx = 0;
+ while (frag_only_func_defs[idx].name) {
+ if (frag_only_func_defs[idx].name == name) {
+ // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
+ if (!is_supported_frag_only_funcs) {
+ _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
+ return nullptr;
+ }
+ // Register usage of the restricted function.
+ calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
+ is_builtin = true;
+ break;
+ }
+ idx++;
+ }
}
// Recursively checks for the restricted function call.
@@ -8581,6 +8599,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
block = block->parent_block;
}
} else if (tk.type == TK_CF_DISCARD) {
+ if (!is_discard_supported) {
+ _set_error(vformat(RTR("Use of '%s' is not supported for the '%s' shader type."), "discard", shader_type_identifier));
+ return ERR_PARSE_ERROR;
+ }
+
//check return type
BlockNode *b = p_block;
while (b && !b->parent_function) {
@@ -8592,7 +8615,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
}
if (!b->parent_function->can_discard) {
- _set_error(vformat(RTR("Use of '%s' is not allowed here."), "discard"));
+ _set_error(vformat(RTR("'%s' cannot be used within the '%s' processor function."), "discard", b->parent_function->name));
return ERR_PARSE_ERROR;
}
@@ -8601,6 +8624,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
pos = _get_tkpos();
tk = _get_token();
+
+ calls_info[b->parent_function->name].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>("discard", CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, pos)));
+
if (tk.type != TK_SEMICOLON) {
_set_expected_after_error(";", "discard");
return ERR_PARSE_ERROR;
@@ -8838,7 +8864,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
ShaderNode::Uniform::Scope uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL;
stages = &p_functions;
- is_supported_frag_only_funcs = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial" || shader_type_identifier == "sky";
+
+ is_discard_supported = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial";
+ is_supported_frag_only_funcs = is_discard_supported || shader_type_identifier == "sky";
const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo();
@@ -10332,6 +10360,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
if (p_functions.has(name)) {
func_node->can_discard = p_functions[name].can_discard;
+ } else {
+ func_node->can_discard = is_discard_supported; // Allow use it for custom functions (in supported shader types).
}
if (!function_overload_count.has(name)) {
@@ -10922,10 +10952,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
break; // Ignore hint keywords (parsed below).
}
if (keyword_list[i].flags & keyword_completion_context) {
- if (keyword_list[i].excluded_shader_types.has(shader_type_identifier)) {
- continue;
- }
- if (!keyword_list[i].functions.is_empty() && !keyword_list[i].functions.has(current_function)) {
+ if (keyword_list[i].excluded_shader_types.has(shader_type_identifier) || keyword_list[i].excluded_functions.has(current_function)) {
continue;
}
ScriptLanguage::CodeCompletionOption option(keyword_list[i].text, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
@@ -11160,9 +11187,15 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
int idx = 0;
bool low_end = RenderingServer::get_singleton()->is_low_end();
- if (stages && stages->has(skip_function)) {
- for (const KeyValue<StringName, StageFunctionInfo> &E : (*stages)[skip_function].stage_functions) {
- matches.insert(String(E.key), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ for (const KeyValue<StringName, StageFunctionInfo> &F : E.value.stage_functions) {
+ if (F.value.skip_function == skip_function && stages->has(skip_function)) {
+ continue;
+ }
+ matches.insert(String(F.key), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ }
}
}
@@ -11292,9 +11325,15 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
return OK;
}
- if (stages && stages->has(block_function)) {
- for (const KeyValue<StringName, StageFunctionInfo> &E : (*stages)[block_function].stage_functions) {
- if (completion_function == E.key) {
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &S : *stages) {
+ for (const KeyValue<StringName, StageFunctionInfo> &E : S.value.stage_functions) {
+ // No need to check for the skip function here.
+ if (completion_function != E.key) {
+ continue;
+ }
+
calltip += get_datatype_name(E.value.return_type);
calltip += " ";
calltip += E.key;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index fb0a526230..ddd4c41059 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -859,6 +859,7 @@ public:
Vector<Argument> arguments;
DataType return_type = TYPE_VOID;
+ String skip_function;
};
struct ModeInfo {
@@ -934,7 +935,7 @@ private:
const char *text;
uint32_t flags;
const Vector<String> excluded_shader_types;
- const Vector<String> functions;
+ const Vector<String> excluded_functions;
};
static const KeyWord keyword_list[];
@@ -1150,6 +1151,7 @@ private:
const HashMap<StringName, FunctionInfo> *stages = nullptr;
bool is_supported_frag_only_funcs = false;
+ bool is_discard_supported = false;
bool _get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier);
static const BuiltinFuncDef builtin_func_defs[];
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index 27e39551ba..0e41a178b5 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -393,6 +393,8 @@ void ShaderPreprocessor::process_directive(Tokenizer *p_tokenizer) {
process_else(p_tokenizer);
} else if (directive == "endif") {
process_endif(p_tokenizer);
+ } else if (directive == "error") {
+ process_error(p_tokenizer);
} else if (directive == "define") {
process_define(p_tokenizer);
} else if (directive == "undef") {
@@ -466,7 +468,7 @@ void ShaderPreprocessor::process_elif(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
if (state->current_branch == nullptr || state->current_branch->else_defined) {
- set_error(RTR("Unmatched elif."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "elif"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -523,7 +525,7 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
if (state->current_branch == nullptr || state->current_branch->else_defined) {
- set_error(RTR("Unmatched else."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "else"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -531,7 +533,7 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid else."), p_tokenizer->get_line());
+ set_error(vformat(RTR("Invalid '%s' directive."), "else"), line);
}
bool skip = false;
@@ -559,7 +561,7 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) {
state->condition_depth--;
if (state->condition_depth < 0) {
- set_error(RTR("Unmatched endif."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "endif"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -568,13 +570,28 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid endif."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "endif"), line);
}
state->current_branch = state->current_branch->parent;
state->branches.pop_back();
}
+void ShaderPreprocessor::process_error(Tokenizer *p_tokenizer) {
+ const int line = p_tokenizer->get_line();
+
+ const String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges();
+ if (body.is_empty()) {
+ set_error(" ", line);
+ } else {
+ set_error(body, line);
+ }
+
+ if (!p_tokenizer->consume_empty_line()) {
+ set_error(vformat(RTR("Invalid '%s' directive."), "error"), line);
+ }
+}
+
void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
@@ -626,7 +643,7 @@ void ShaderPreprocessor::process_ifdef(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid ifdef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "ifdef"), line);
return;
}
@@ -648,7 +665,7 @@ void ShaderPreprocessor::process_ifndef(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid ifndef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "ifndef"), line);
return;
}
@@ -771,21 +788,21 @@ void ShaderPreprocessor::process_pragma(Tokenizer *p_tokenizer) {
}
if (label.is_empty()) {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
- // Rxplicitly handle pragma values here.
+ // Explicitly handle pragma values here.
// If more pragma options are created, then refactor into a more defined structure.
if (label == "disable_preprocessor") {
state->disabled = true;
} else {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
}
@@ -794,7 +811,7 @@ void ShaderPreprocessor::process_undef(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
const String label = p_tokenizer->get_identifier();
if (label.is_empty() || !p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid undef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "undef"), line);
return;
}
@@ -1383,6 +1400,7 @@ void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_inclu
r_keywords->push_back("else");
}
r_keywords->push_back("endif");
+ r_keywords->push_back("error");
if (p_include_shader_keywords) {
r_keywords->push_back("if");
}
diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h
index b29239105a..0a90aec958 100644
--- a/servers/rendering/shader_preprocessor.h
+++ b/servers/rendering/shader_preprocessor.h
@@ -191,6 +191,7 @@ private:
void process_elif(Tokenizer *p_tokenizer);
void process_else(Tokenizer *p_tokenizer);
void process_endif(Tokenizer *p_tokenizer);
+ void process_error(Tokenizer *p_tokenizer);
void process_if(Tokenizer *p_tokenizer);
void process_ifdef(Tokenizer *p_tokenizer);
void process_ifndef(Tokenizer *p_tokenizer);
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index f498c0bf93..9ccfe2f9d7 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -284,6 +284,7 @@ ShaderTypes::ShaderTypes() {
{
ShaderLanguage::StageFunctionInfo func;
+ func.skip_function = "vertex";
func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("sdf_pos", ShaderLanguage::TYPE_VEC2));
func.return_type = ShaderLanguage::TYPE_FLOAT; //whether it could emit
shader_modes[RS::SHADER_CANVAS_ITEM].functions["fragment"].stage_functions["texture_sdf"] = func;
@@ -297,6 +298,7 @@ ShaderTypes::ShaderTypes() {
{
ShaderLanguage::StageFunctionInfo func;
+ func.skip_function = "vertex";
func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("uv", ShaderLanguage::TYPE_VEC2));
func.return_type = ShaderLanguage::TYPE_VEC2; //whether it could emit
shader_modes[RS::SHADER_CANVAS_ITEM].functions["fragment"].stage_functions["screen_uv_to_sdf"] = func;
diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h
index 6a0adfa596..1e149e3a97 100644
--- a/servers/rendering/storage/light_storage.h
+++ b/servers/rendering/storage/light_storage.h
@@ -59,6 +59,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) = 0;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) = 0;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) = 0;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) = 0;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const = 0;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) = 0;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) = 0;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 32ef5261f3..53dda24dc3 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2479,6 +2479,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("light_set_cull_mask", "light", "mask"), &RenderingServer::light_set_cull_mask);
ClassDB::bind_method(D_METHOD("light_set_distance_fade", "decal", "enabled", "begin", "shadow", "length"), &RenderingServer::light_set_distance_fade);
ClassDB::bind_method(D_METHOD("light_set_reverse_cull_face_mode", "light", "enabled"), &RenderingServer::light_set_reverse_cull_face_mode);
+ ClassDB::bind_method(D_METHOD("light_set_shadow_caster_mask", "light", "mask"), &RenderingServer::light_set_shadow_caster_mask);
ClassDB::bind_method(D_METHOD("light_set_bake_mode", "light", "bake_mode"), &RenderingServer::light_set_bake_mode);
ClassDB::bind_method(D_METHOD("light_set_max_sdfgi_cascade", "light", "cascade"), &RenderingServer::light_set_max_sdfgi_cascade);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 0208a640a5..6c1e1274d4 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -542,6 +542,7 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) = 0;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) = 0;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) = 0;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) = 0;
enum LightBakeMode {
LIGHT_BAKE_DISABLED,
diff --git a/servers/text_server.h b/servers/text_server.h
index ba3fdaa35e..7dd9669818 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -31,6 +31,7 @@
#ifndef TEXT_SERVER_H
#define TEXT_SERVER_H
+#include "core/io/image.h"
#include "core/object/ref_counted.h"
#include "core/os/os.h"
#include "core/templates/rid.h"
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 8559737e74..8d6137cf62 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -460,11 +460,27 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
+ CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
CHECK(String::num_scientific(30000000) == "3e+07");
+
+ // String::num_int64 tests.
CHECK(String::num_int64(3141593) == "3141593");
+ CHECK(String::num_int64(-3141593) == "-3141593");
CHECK(String::num_int64(0xA141593, 16) == "a141593");
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
- CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
+ ERR_PRINT_OFF;
+ CHECK(String::num_int64(3141593, 1) == ""); // Invalid base < 2.
+ CHECK(String::num_int64(3141593, 37) == ""); // Invalid base > 36.
+ ERR_PRINT_ON;
+
+ // String::num_uint64 tests.
+ CHECK(String::num_uint64(4294967295) == "4294967295");
+ CHECK(String::num_uint64(0xF141593, 16) == "f141593");
+ CHECK(String::num_uint64(0xF141593, 16, true) == "F141593");
+ ERR_PRINT_OFF;
+ CHECK(String::num_uint64(4294967295, 1) == ""); // Invalid base < 2.
+ CHECK(String::num_uint64(4294967295, 37) == ""); // Invalid base > 36.
+ ERR_PRINT_ON;
// String::num_real tests.
CHECK(String::num_real(1.0) == "1.0");
diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h
index be615975f8..599a282b20 100644
--- a/tests/core/variant/test_variant.h
+++ b/tests/core/variant/test_variant.h
@@ -1806,6 +1806,14 @@ TEST_CASE("[Variant] Writer and parser dictionary") {
CHECK_MESSAGE(d_parsed == Variant(d), "Should parse back.");
}
+TEST_CASE("[Variant] Writer key sorting") {
+ Dictionary d = build_dictionary(StringName("C"), 3, "A", 1, StringName("B"), 2, "D", 4);
+ String d_str;
+ VariantWriter::write_to_string(d, d_str);
+
+ CHECK_EQ(d_str, "{\n\"A\": 1,\n&\"B\": 2,\n&\"C\": 3,\n\"D\": 4\n}");
+}
+
TEST_CASE("[Variant] Writer recursive dictionary") {
// There is no way to accurately represent a recursive dictionary,
// the only thing we can do is make sure the writer doesn't blow up