diff options
109 files changed, 1178 insertions, 430 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..52677e80c3 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> x = events[i]; + if (x->get_device() == -1) { // -1 was the previous value (GH-97707). + x->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_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/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/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_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/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..91151ae869 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -2099,7 +2099,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/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..1e571e58a1 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -875,6 +875,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/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/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/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/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/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_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..f328b9fc91 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" 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/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/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/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/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/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 17bcbacfc2..cdc4087142 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -945,6 +945,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 +999,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); 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/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/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 111a39d730..e30f03afad 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -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/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/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/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/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/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/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/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/color_picker.cpp b/scene/gui/color_picker.cpp index fe4c91cb56..e92f979c32 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() { @@ -2103,7 +2089,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/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 §ion) 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/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/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/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/display_server.cpp b/servers/display_server.cpp index ce0d6cb996..8bfe95aa76 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); 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/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/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..ca07444465 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]; 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_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/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/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/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 |
