diff options
72 files changed, 1380 insertions, 284 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 3b96fc20c6..aaabbabfd9 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -507,6 +507,10 @@ void register_global_constants() { BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, KPAD); BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, GROUP_SWITCH); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, UNSPECIFIED); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, LEFT); + BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, RIGHT); + BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, NONE); BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, LEFT); BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, RIGHT); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index ce01531b5c..2904e54b22 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -653,6 +653,8 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra if (!ext->is_reloading) { self->extension_classes.erase(class_name); } + + GDExtensionEditorHelp::remove_class(class_name); #else self->extension_classes.erase(class_name); #endif @@ -1196,4 +1198,17 @@ void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_ extension_classes.erase(p_class_name); } } + +GDExtensionEditorHelp::EditorHelpLoadXmlBufferFunc GDExtensionEditorHelp::editor_help_load_xml_buffer = nullptr; +GDExtensionEditorHelp::EditorHelpRemoveClassFunc GDExtensionEditorHelp::editor_help_remove_class = nullptr; + +void GDExtensionEditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + ERR_FAIL_NULL(editor_help_load_xml_buffer); + editor_help_load_xml_buffer(p_buffer, p_size); +} + +void GDExtensionEditorHelp::remove_class(const String &p_class) { + ERR_FAIL_NULL(editor_help_remove_class); + editor_help_remove_class(p_class); +} #endif // TOOLS_ENABLED diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 0b39581751..588efed017 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -197,6 +197,26 @@ public: return extension_classes; } }; + +class GDExtensionEditorHelp { +protected: + friend class EditorHelp; + + // Similarly to EditorNode above, we need to be able to ask EditorHelp to parse + // new documentation data. Note though that, differently from EditorHelp, this + // is initialized even _before_ it gets instantiated, as we need to rely on + // this method while initializing the engine. + typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size); + static EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer; + + typedef void (*EditorHelpRemoveClassFunc)(const String &p_class); + static EditorHelpRemoveClassFunc editor_help_remove_class; + +public: + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); +}; + #endif // TOOLS_ENABLED #endif // GDEXTENSION_H diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index e02e7aa701..88572f24f0 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -42,6 +42,8 @@ #include "core/variant/variant.h" #include "core/version.h" +#include <string.h> + class CallableCustomExtension : public CallableCustom { void *userdata; void *token; @@ -1373,6 +1375,19 @@ static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_cla #endif } +static void gdextension_editor_help_load_xml_from_utf8_chars_and_len(const char *p_data, GDExtensionInt p_size) { +#ifdef TOOLS_ENABLED + GDExtensionEditorHelp::load_xml_buffer((const uint8_t *)p_data, p_size); +#endif +} + +static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data) { +#ifdef TOOLS_ENABLED + size_t len = strlen(p_data); + gdextension_editor_help_load_xml_from_utf8_chars_and_len(p_data, len); +#endif +} + #define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name) void gdextension_setup_interface() { @@ -1516,6 +1531,8 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(classdb_get_class_tag); REGISTER_INTERFACE_FUNC(editor_add_plugin); REGISTER_INTERFACE_FUNC(editor_remove_plugin); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars); + REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len); } #undef REGISTER_INTERFACE_FUNCTION diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index d58f0226d8..8fda11c651 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2617,6 +2617,31 @@ typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePt */ typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name); +/** + * @name editor_help_load_xml_from_utf8_chars + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string (null terminated). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *p_data); + +/** + * @name editor_help_load_xml_from_utf8_chars_and_len + * @since 4.3 + * + * Loads new XML-formatted documentation data in the editor. + * + * The provided pointer can be immediately freed once the function returns. + * + * @param p_data A pointer to an UTF-8 encoded C string. + * @param p_size The number of bytes (not code units). + */ +typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size); + #ifdef __cplusplus } #endif diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index e99dd04599..89ffcecf50 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -364,6 +364,15 @@ char32_t InputEventKey::get_unicode() const { return unicode; } +void InputEventKey::set_location(KeyLocation p_key_location) { + location = p_key_location; + emit_changed(); +} + +KeyLocation InputEventKey::get_location() const { + return location; +} + void InputEventKey::set_echo(bool p_enable) { echo = p_enable; emit_changed(); @@ -436,6 +445,23 @@ String InputEventKey::as_text_key_label() const { return mods_text.is_empty() ? kc : mods_text + "+" + kc; } +String InputEventKey::as_text_location() const { + String loc; + + switch (location) { + case KeyLocation::LEFT: + loc = "left"; + break; + case KeyLocation::RIGHT: + loc = "right"; + break; + default: + break; + } + + return loc; +} + String InputEventKey::as_text() const { String kc; @@ -464,6 +490,11 @@ String InputEventKey::to_string() { String kc = ""; String physical = "false"; + String loc = as_text_location(); + if (loc.is_empty()) { + loc = "unspecified"; + } + if (keycode == Key::NONE && physical_keycode == Key::NONE && unicode != 0) { kc = "U+" + String::num_uint64(unicode, 16) + " (" + String::chr(unicode) + ")"; } else if (keycode != Key::NONE) { @@ -478,7 +509,7 @@ String InputEventKey::to_string() { String mods = InputEventWithModifiers::as_text(); mods = mods.is_empty() ? "none" : mods; - return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, pressed=%s, echo=%s", kc, mods, physical, p, e); + return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, location=%s, pressed=%s, echo=%s", kc, mods, physical, loc, p, e); } Ref<InputEventKey> InputEventKey::create_reference(Key p_keycode, bool p_physical) { @@ -531,6 +562,9 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool p_exact_ma match = keycode == key->keycode; } else if (physical_keycode != Key::NONE) { match = physical_keycode == key->physical_keycode; + if (location != KeyLocation::UNSPECIFIED) { + match &= location == key->location; + } } else { match = false; } @@ -572,6 +606,9 @@ bool InputEventKey::is_match(const Ref<InputEvent> &p_event, bool p_exact_match) return (keycode == key->keycode) && (!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask()); } else if (physical_keycode != Key::NONE) { + if (location != KeyLocation::UNSPECIFIED && location != key->location) { + return false; + } return (physical_keycode == key->physical_keycode) && (!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask()); } else { @@ -594,6 +631,9 @@ void InputEventKey::_bind_methods() { ClassDB::bind_method(D_METHOD("set_unicode", "unicode"), &InputEventKey::set_unicode); ClassDB::bind_method(D_METHOD("get_unicode"), &InputEventKey::get_unicode); + ClassDB::bind_method(D_METHOD("set_location", "location"), &InputEventKey::set_location); + ClassDB::bind_method(D_METHOD("get_location"), &InputEventKey::get_location); + ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventKey::set_echo); ClassDB::bind_method(D_METHOD("get_keycode_with_modifiers"), &InputEventKey::get_keycode_with_modifiers); @@ -603,12 +643,14 @@ void InputEventKey::_bind_methods() { ClassDB::bind_method(D_METHOD("as_text_keycode"), &InputEventKey::as_text_keycode); ClassDB::bind_method(D_METHOD("as_text_physical_keycode"), &InputEventKey::as_text_physical_keycode); ClassDB::bind_method(D_METHOD("as_text_key_label"), &InputEventKey::as_text_key_label); + ClassDB::bind_method(D_METHOD("as_text_location"), &InputEventKey::as_text_location); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "keycode"), "set_keycode", "get_keycode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_keycode"), "set_physical_keycode", "get_physical_keycode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "key_label"), "set_key_label", "get_key_label"); ADD_PROPERTY(PropertyInfo(Variant::INT, "unicode"), "set_unicode", "get_unicode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "location", PROPERTY_HINT_ENUM, "Unspecified,Left,Right"), "set_location", "get_location"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo"); } diff --git a/core/input/input_event.h b/core/input/input_event.h index ed7ccf0a9f..61a53116e9 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -157,6 +157,7 @@ class InputEventKey : public InputEventWithModifiers { Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; ///unicode + KeyLocation location = KeyLocation::UNSPECIFIED; bool echo = false; /// true if this is an echo key @@ -178,6 +179,9 @@ public: void set_unicode(char32_t p_unicode); char32_t get_unicode() const; + void set_location(KeyLocation p_key_location); + KeyLocation get_location() const; + void set_echo(bool p_enable); virtual bool is_echo() const override; @@ -193,6 +197,7 @@ public: virtual String as_text_physical_keycode() const; virtual String as_text_keycode() const; virtual String as_text_key_label() const; + virtual String as_text_location() const; virtual String as_text() const override; virtual String to_string() override; diff --git a/core/os/keyboard.h b/core/os/keyboard.h index 785972d31d..2051973336 100644 --- a/core/os/keyboard.h +++ b/core/os/keyboard.h @@ -260,6 +260,12 @@ enum class KeyModifierMask { GROUP_SWITCH = (1 << 30) }; +enum class KeyLocation { + UNSPECIFIED, + LEFT, + RIGHT +}; + // To avoid having unnecessary operators, only define the ones that are needed. constexpr Key operator-(uint32_t a, Key b) { diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index 34b54f1d00..c9f5ae7fc6 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -176,6 +176,7 @@ VARIANT_ENUM_CAST(Variant::Operator); VARIANT_ENUM_CAST(Key); VARIANT_BITFIELD_CAST(KeyModifierMask); +VARIANT_ENUM_CAST(KeyLocation); static inline Key &operator|=(Key &a, BitField<KeyModifierMask> b) { a = static_cast<Key>(static_cast<int>(a) | static_cast<int>(b.operator int64_t())); diff --git a/core/variant/variant.h b/core/variant/variant.h index 602d287f22..429b346896 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -502,6 +502,7 @@ public: VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyAxis) VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyButton) VARIANT_ENUM_CLASS_CONSTRUCTOR(Key) + VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation) VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage) VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton) diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 9a814f484d..f8d1c99393 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2374,6 +2374,16 @@ <constant name="KEY_MASK_GROUP_SWITCH" value="1073741824" enum="KeyModifierMask" is_bitfield="true"> Group Switch key mask. </constant> + <constant name="KEY_LOCATION_UNSPECIFIED" value="0" enum="KeyLocation"> + Used for keys which only appear once, or when a comparison doesn't need to differentiate the [code]LEFT[/code] and [code]RIGHT[/code] versions. + For example, when using [method InputEvent.is_match], an event which has [constant KEY_LOCATION_UNSPECIFIED] will match any [enum KeyLocation] on the passed event. + </constant> + <constant name="KEY_LOCATION_LEFT" value="1" enum="KeyLocation"> + A key which is to the left of its twin. + </constant> + <constant name="KEY_LOCATION_RIGHT" value="2" enum="KeyLocation"> + A key which is to the right of its twin. + </constant> <constant name="MOUSE_BUTTON_NONE" value="0" enum="MouseButton"> Enum value which doesn't correspond to any mouse button. This is used to initialize [enum MouseButton] properties with a generic state. </constant> diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml index a62c160080..da7f08e53d 100644 --- a/doc/classes/Bone2D.xml +++ b/doc/classes/Bone2D.xml @@ -15,7 +15,7 @@ <method name="apply_rest"> <return type="void" /> <description> - Stores the node's current transforms in [member rest]. + Resets the bone to the rest pose. This is equivalent to setting [member Node2D.transform] to [member rest]. </description> </method> <method name="get_autocalculate_length_and_angle" qualifiers="const"> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index d0de09e451..fc28622fdb 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -195,6 +195,15 @@ Shows the given property on the given [param object] in the editor's Inspector dock. If [param inspector_only] is [code]true[/code], plugins will not attempt to edit [param object]. </description> </method> + <method name="is_multi_window_enabled" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if multiple window support is enabled in the editor. Multiple window support is enabled if [i]all[/i] of these statements are true: + - [member EditorSettings.interface/multi_window/enable] is [code]true[/code]. + - [member EditorSettings.interface/editor/single_window_mode] is [code]false[/code]. + - [member Viewport.gui_embed_subwindows] is [code]false[/code]. This is forced to [code]true[/code] on platforms that don't support multiple windows such as Web, or when the [code]--single-window[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used. + </description> + </method> <method name="is_playing_scene" qualifiers="const"> <return type="bool" /> <description> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index b7f3ec9963..2f24acd684 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -495,7 +495,7 @@ <member name="filesystem/file_dialog/thumbnail_size" type="int" setter="" getter=""> The thumbnail size to use in the editor's file dialogs (in pixels). See also [member docks/filesystem/thumbnail_size]. </member> - <member name="filesystem/import/blender/blender3_path" type="String" setter="" getter=""> + <member name="filesystem/import/blender/blender_path" type="String" setter="" getter=""> The path to the directory containing the Blender executable used for converting the Blender 3D scene files [code].blend[/code] to glTF 2.0 format during import. Blender 3.0 or later is required. To enable this feature for your specific project, use [member ProjectSettings.filesystem/import/blender/enabled]. </member> @@ -616,6 +616,7 @@ </member> <member name="interface/editor/single_window_mode" type="bool" setter="" getter=""> If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window. + [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting. </member> <member name="interface/editor/ui_layout_direction" type="int" setter="" getter=""> Editor UI default layout direction. @@ -637,8 +638,9 @@ If [code]true[/code], display OpenType features marked as [code]hidden[/code] by the font file in the [Font] editor. </member> <member name="interface/multi_window/enable" type="bool" setter="" getter=""> - If [code]true[/code], the multi window support in editor is enabled. The following panels can become dedicated windows (made floating): Docks, Script editor, and Shader editor. + If [code]true[/code], multiple window support in editor is enabled. The following panels can become dedicated windows (i.e. made floating): Docks, Script editor, and Shader editor. [b]Note:[/b] When [member interface/editor/single_window_mode] is [code]true[/code], the multi window support is always disabled. + [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting. </member> <member name="interface/multi_window/maximize_window" type="bool" setter="" getter=""> If [code]true[/code], when panels are made floating they will be maximized. diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml index 48a6804290..7b6fc3f216 100644 --- a/doc/classes/InputEventKey.xml +++ b/doc/classes/InputEventKey.xml @@ -24,6 +24,12 @@ Returns a [String] representation of the event's [member keycode] and modifiers. </description> </method> + <method name="as_text_location" qualifiers="const"> + <return type="String" /> + <description> + Returns a [String] representation of the event's [member location]. This will be a blank string if the event is not specific to a location. + </description> + </method> <method name="as_text_physical_keycode" qualifiers="const"> <return type="String" /> <description> @@ -77,6 +83,9 @@ +-----+ +-----+ [/codeblock] </member> + <member name="location" type="int" setter="set_location" getter="get_location" enum="KeyLocation" default="0"> + Represents the location of a key which has both left and right versions, such as [kbd]Shift[/kbd] or [kbd]Alt[/kbd]. + </member> <member name="physical_keycode" type="int" setter="set_physical_keycode" getter="get_physical_keycode" enum="Key" default="0"> Represents the physical location of a key on the 101/102-key US QWERTY keyboard, which corresponds to one of the [enum Key] constants. To get a human-readable representation of the [InputEventKey], use [method OS.get_keycode_string] in combination with [method DisplayServer.keyboard_get_keycode_from_physical]: diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index e4f4d7682b..5fa43f868e 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -76,83 +76,112 @@ <method name="_get_property_list" qualifiers="virtual"> <return type="Dictionary[]" /> <description> - Override this method to customize how script properties should be handled by the engine. + Override this method to provide a custom list of additional properties to handle by the engine. Should return a property list, as an [Array] of dictionaries. The result is added to the array of [method get_property_list], and should be formatted in the same way. Each [Dictionary] must at least contain the [code]name[/code] and [code]type[/code] entries. - The example below displays [code]hammer_type[/code] in the Inspector dock, only if [code]holding_hammer[/code] is [code]true[/code]: + You can use [method _property_can_revert] and [method _property_get_revert] to customize the default values of the properties added by this method. + The example below displays a list of numbers shown as words going from [code]ZERO[/code] to [code]FIVE[/code], with [code]number_count[/code] controlling the size of the list: [codeblocks] [gdscript] @tool - extends Node2D + extends Node - @export var holding_hammer = false: - set(value): - holding_hammer = value + @export var number_count = 3: + set(nc): + number_count = nc + numbers.resize(number_count) notify_property_list_changed() - var hammer_type = 0 - - func _get_property_list(): - # By default, `hammer_type` is not visible in the editor. - var property_usage = PROPERTY_USAGE_NO_EDITOR - if holding_hammer: - property_usage = PROPERTY_USAGE_DEFAULT + var numbers = PackedInt32Array([0, 0, 0]) + func _get_property_list(): var properties = [] - properties.append({ - "name": "hammer_type", - "type": TYPE_INT, - "usage": property_usage, # See above assignment. - "hint": PROPERTY_HINT_ENUM, - "hint_string": "Wooden,Iron,Golden,Enchanted" - }) + + for i in range(number_count): + properties.append({ + "name": "number_%d" % i, + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": "ZERO,ONE,TWO,THREE,FOUR,FIVE", + }) return properties + + func _get(property): + if property.begins_with("number_"): + var index = property.get_slice("_", 1).to_int() + return numbers[index] + + func _set(property, value): + if property.begins_with("number_"): + var index = property.get_slice("_", 1).to_int() + numbers[index] = value + return true + return false [/gdscript] [csharp] [Tool] - public partial class MyNode2D : Node2D + public partial class MyNode : Node { - private bool _holdingHammer; + private int _numberCount; [Export] - public bool HoldingHammer + public int NumberCount { - get => _holdingHammer; + get => _numberCount; set { - _holdingHammer = value; + _numberCount = value; + _numbers.Resize(_numberCount); NotifyPropertyListChanged(); } } - public int HammerType { get; set; } + private List<int> _numbers = new(); public override Godot.Collections.Array<Godot.Collections.Dictionary> _GetPropertyList() { - // By default, `HammerType` is not visible in the editor. - var propertyUsage = PropertyUsageFlags.NoEditor; + var properties = new Godot.Collections.Array<Godot.Collections.Dictionary>(); - if (HoldingHammer) + for (int i = 0; i < _numberCount; i++) { - propertyUsage = PropertyUsageFlags.Default; + properties.Add(new Godot.Collections.Dictionary() + { + { "name", $"number_{i}" }, + { "type", (int)Variant.Type.Int }, + { "hint", (int)PropertyHint.Enum }, + { "hint_string", "Zero,One,Two,Three,Four,Five" }, + }); } - var properties = new Godot.Collections.Array<Godot.Collections.Dictionary>(); - properties.Add(new Godot.Collections.Dictionary() + return properties; + } + + public override Variant _Get(StringName property) + { + string propertyName = property.ToString(); + if (propertyName.StartsWith("number_")) { - { "name", "HammerType" }, - { "type", (int)Variant.Type.Int }, - { "usage", (int)propertyUsage }, // See above assignment. - { "hint", (int)PropertyHint.Enum }, - { "hint_string", "Wooden,Iron,Golden,Enchanted" } - }); + int index = int.Parse(propertyName.Substring("number_".Length)); + return _numbers[index]; + } + return default; + } - return properties; + public override bool _Set(StringName property, Variant value) + { + string propertyName = property.ToString(); + if (propertyName.StartsWith("number_")) + { + int index = int.Parse(propertyName.Substring("number_".Length)); + numbers[index] = value.As<int>(); + return true; + } + return false; } } [/csharp] [/codeblocks] - [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc. + [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc. If you want to customize exported properties, use [method _validate_property]. [b]Note:[/b] If the object's script is not [annotation @GDScript.@tool], this method will not be called in the editor. </description> </method> @@ -274,7 +303,7 @@ <return type="void" /> <param index="0" name="property" type="Dictionary" /> <description> - Override this method to customize existing properties. Every property info goes through this method. The dictionary contents is the same as in [method _get_property_list]. + Override this method to customize existing properties. Every property info goes through this method, except properties added with [method _get_property_list]. The dictionary contents is the same as in [method _get_property_list]. [codeblocks] [gdscript] @tool diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index cbd797273c..0876261a31 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -943,7 +943,7 @@ </member> <member name="filesystem/import/blender/enabled" type="bool" setter="" getter="" default="true"> If [code]true[/code], Blender 3D scene files with the [code].blend[/code] extension will be imported by converting them to glTF 2.0. - This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender3_path[/code]. Blender 3.0 or later is required. + This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender_path[/code]. Blender 3.0 or later is required. </member> <member name="filesystem/import/blender/enabled.android" type="bool" setter="" getter="" default="false"> Override for [member filesystem/import/blender/enabled] on Android where Blender can't easily be accessed from Godot. diff --git a/doc/classes/RDSamplerState.xml b/doc/classes/RDSamplerState.xml index 44f040dabd..f1ba630226 100644 --- a/doc/classes/RDSamplerState.xml +++ b/doc/classes/RDSamplerState.xml @@ -26,12 +26,13 @@ The mipmap LOD bias to use. Positive values will make the sampler blurrier at a given distance, while negative values will make the sampler sharper at a given distance (at the risk of looking grainy). Recommended values are between [code]-0.5[/code] and [code]0.0[/code]. Only effective if the sampler has mipmaps available. </member> <member name="mag_filter" type="int" setter="set_mag_filter" getter="get_mag_filter" enum="RenderingDevice.SamplerFilter" default="0"> - The sampler's magnification filter. + The sampler's magnification filter. It is the filtering method used when sampling texels that appear bigger than on-screen pixels. </member> <member name="max_lod" type="float" setter="set_max_lod" getter="get_max_lod" default="1e+20"> The maximum mipmap LOD bias to display (lowest resolution). Only effective if the sampler has mipmaps available. </member> <member name="min_filter" type="int" setter="set_min_filter" getter="get_min_filter" enum="RenderingDevice.SamplerFilter" default="0"> + The sampler's minification filter. It is the filtering method used when sampling texels that appear smaller than on-screen pixels. </member> <member name="min_lod" type="float" setter="set_min_lod" getter="get_min_lod" default="0.0"> The minimum mipmap LOD bias to display (highest resolution). Only effective if the sampler has mipmaps available. @@ -49,6 +50,7 @@ The repeat mode to use along the W axis of UV coordinates. This affects the returned values if sampling outside the UV bounds. Only effective for 3D samplers. </member> <member name="unnormalized_uvw" type="bool" setter="set_unnormalized_uvw" getter="get_unnormalized_uvw" default="false"> + If [code]true[/code], the texture will be sampled with coordinates ranging from 0 to the texture's resolution. Otherwise, the coordinates will be normalized and range from 0 to 1. </member> <member name="use_anisotropy" type="bool" setter="set_use_anisotropy" getter="get_use_anisotropy" default="false"> If [code]true[/code], perform anisotropic sampling. See [member anisotropy_max]. diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 44f6444a31..952c093eb6 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -1652,3 +1652,15 @@ Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, in return OK; } + +Error DocTools::load_xml(const uint8_t *p_data, int p_size) { + Ref<XMLParser> parser = memnew(XMLParser); + Error err = parser->_open_buffer(p_data, p_size); + if (err) { + return err; + } + + _load(parser); + + return OK; +} diff --git a/editor/doc_tools.h b/editor/doc_tools.h index 7f29cc238a..a6910baf28 100644 --- a/editor/doc_tools.h +++ b/editor/doc_tools.h @@ -56,6 +56,7 @@ public: Error _load(Ref<XMLParser> parser); Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size); + Error load_xml(const uint8_t *p_data, int p_size); }; #endif // DOC_TOOLS_H diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index dfe9504706..54789bdef1 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -702,15 +702,18 @@ EditorDockManager::EditorDockManager() { dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL); dock_vb->add_child(dock_select); - if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) { - dock_float = memnew(Button); - dock_float->set_text(TTR("Make Floating")); - dock_float->set_focus_mode(Control::FOCUS_NONE); - dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float)); - - dock_vb->add_child(dock_float); + dock_float = memnew(Button); + dock_float->set_text(TTR("Make Floating")); + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + dock_float->set_disabled(true); + dock_float->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text()); + } else { + dock_float->set_tooltip_text(TTR("Make this dock floating.")); } + dock_float->set_focus_mode(Control::FOCUS_NONE); + dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float)); + dock_vb->add_child(dock_float); dock_select_popup->reset_size(); } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index f0f7f87711..33862a6792 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -31,6 +31,7 @@ #include "editor_help.h" #include "core/core_constants.h" +#include "core/extension/gdextension.h" #include "core/input/input.h" #include "core/object/script_language.h" #include "core/os/keyboard.h" @@ -83,6 +84,7 @@ const Vector<String> classes_with_csharp_differences = { // TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe. // Might this be a problem? DocTools *EditorHelp::doc = nullptr; +DocTools *EditorHelp::ext_doc = nullptr; static bool _attempt_doc_load(const String &p_class) { // Docgen always happens in the outer-most class: it also generates docs for inner classes. @@ -2369,6 +2371,28 @@ String EditorHelp::get_cache_full_path() { return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res"); } +void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) { + if (!ext_doc) { + ext_doc = memnew(DocTools); + } + + ext_doc->load_xml(p_buffer, p_size); + + if (doc) { + doc->load_xml(p_buffer, p_size); + } +} + +void EditorHelp::remove_class(const String &p_class) { + if (ext_doc && ext_doc->has_doc(p_class)) { + ext_doc->remove_doc(p_class); + } + + if (doc && doc->has_doc(p_class)) { + doc->remove_doc(p_class); + } +} + void EditorHelp::_load_doc_thread(void *p_udata) { Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path()); if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == doc_version_hash) { @@ -2416,6 +2440,11 @@ void EditorHelp::_gen_doc_thread(void *p_udata) { void EditorHelp::_gen_extensions_docs() { doc->generate((DocTools::GENERATE_FLAG_SKIP_BASIC_TYPES | DocTools::GENERATE_FLAG_EXTENSION_CLASSES_ONLY)); + + // Append extra doc data, as it gets overridden by the generation step. + if (ext_doc) { + doc->merge_from(*ext_doc); + } } void EditorHelp::generate_doc(bool p_use_cache) { @@ -2554,6 +2583,11 @@ void EditorHelp::_bind_methods() { ADD_SIGNAL(MethodInfo("go_to_help")); } +void EditorHelp::init_gdext_pointers() { + GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer; + GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class; +} + EditorHelp::EditorHelp() { set_custom_minimum_size(Size2(150 * EDSCALE, 0)); diff --git a/editor/editor_help.h b/editor/editor_help.h index ff440a679a..896f0adf43 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -113,6 +113,7 @@ class EditorHelp : public VBoxContainer { RichTextLabel *class_desc = nullptr; HSplitContainer *h_split = nullptr; static DocTools *doc; + static DocTools *ext_doc; ConfirmationDialog *search_dialog = nullptr; LineEdit *search = nullptr; @@ -209,6 +210,9 @@ public: static void cleanup_doc(); static String get_cache_full_path(); + static void load_xml_buffer(const uint8_t *p_buffer, int p_size); + static void remove_class(const String &p_class); + void go_to_help(const String &p_help); void go_to_class(const String &p_class); void update_doc(); @@ -228,6 +232,8 @@ public: void update_toggle_scripts_button(); + static void init_gdext_pointers(); + EditorHelp(); ~EditorHelp(); }; diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index bad28ff43d..dcaf7fbd00 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -235,6 +235,10 @@ bool EditorInterface::is_distraction_free_mode_enabled() const { return EditorNode::get_singleton()->is_distraction_free_mode_enabled(); } +bool EditorInterface::is_multi_window_enabled() const { + return EditorNode::get_singleton()->is_multi_window_enabled(); +} + float EditorInterface::get_editor_scale() const { return EDSCALE; } @@ -445,6 +449,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("set_main_screen_editor", "name"), &EditorInterface::set_main_screen_editor); ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode); ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled); + ClassDB::bind_method(D_METHOD("is_multi_window_enabled"), &EditorInterface::is_multi_window_enabled); ClassDB::bind_method(D_METHOD("get_editor_scale"), &EditorInterface::get_editor_scale); diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 73e89ae2f2..9515a1226f 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -99,6 +99,7 @@ public: void set_main_screen_editor(const String &p_name); void set_distraction_free_mode(bool p_enter); bool is_distraction_free_mode_enabled() const; + bool is_multi_window_enabled() const; float get_editor_scale() const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 24bfba3844..23d5704048 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -870,7 +870,11 @@ void EditorNode::_resources_changed(const Vector<String> &p_resources) { } if (!res->editor_can_reload_from_file()) { - continue; + Ref<Script> scr = res; + // Scripts are reloaded via the script editor. + if (scr.is_null() || ScriptEditor::get_singleton()->get_open_scripts().has(scr)) { + continue; + } } if (!res->get_path().is_resource_file() && !res->get_path().is_absolute_path()) { continue; @@ -3711,6 +3715,10 @@ bool EditorNode::is_scene_open(const String &p_path) { return false; } +bool EditorNode::is_multi_window_enabled() const { + return !SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable"); +} + void EditorNode::fix_dependencies(const String &p_for_file) { dependency_fixer->edit(p_for_file); } @@ -4122,6 +4130,20 @@ void EditorNode::request_instantiate_scenes(const Vector<String> &p_files) { SceneTreeDock::get_singleton()->instantiate_scenes(p_files); } +String EditorNode::get_multiwindow_support_tooltip_text() const { + if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows()) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS)) { + return TTR("Multi-window support is not available because the `--single-window` command line argument was used to start the editor."); + } else { + return TTR("Multi-window support is not available because the current platform doesn't support multiple windows."); + } + } else if (EDITOR_GET("interface/editor/single_window_mode")) { + return TTR("Multi-window support is not available because Interface > Editor > Single Window Mode is enabled in the editor settings."); + } + + return TTR("Multi-window support is not available because Interface > Multi Window > Enable is disabled in the editor settings."); +} + void EditorNode::_inherit_request(String p_file) { current_menu_option = FILE_NEW_INHERITED_SCENE; _dialog_action(p_file); diff --git a/editor/editor_node.h b/editor/editor_node.h index f1dea0c11e..014b72c580 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -758,6 +758,8 @@ public: bool is_resource_read_only(Ref<Resource> p_resource, bool p_foreign_resources_are_writable = false); + String get_multiwindow_support_tooltip_text() const; + bool is_changing_scene() const; VBoxContainer *get_main_screen_control(); @@ -807,6 +809,7 @@ public: List<AdditiveNodeEntry> &p_addition_list); bool is_scene_open(const String &p_path); + bool is_multi_window_enabled() const; void setup_color_picker(ColorPicker *p_picker); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 25510122f4..3b1a69459d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -520,7 +520,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16") // Import (for glft module) - EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/import/blender/blender3_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index 2c46e1c20a..c29e83d624 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -79,7 +79,11 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo if (!text.is_empty()) { text += " " + TTR("or") + " "; } - text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical") + ")"; + text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical"); + if (key->get_location() != KeyLocation::UNSPECIFIED) { + text += " " + key->as_text_location(); + } + text += ")"; } if (key->get_key_label() != Key::NONE) { if (!text.is_empty()) { diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 0f483fcaef..22673bec64 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -64,6 +64,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c bool show_mods = false; bool show_device = false; bool show_key = false; + bool show_location = false; if (mod.is_valid()) { show_mods = true; @@ -77,12 +78,17 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c if (k.is_valid()) { show_key = true; - if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) { + Key phys_key = k->get_physical_keycode(); + if (k->get_keycode() == Key::NONE && phys_key == Key::NONE && k->get_key_label() != Key::NONE) { key_mode->select(KEYMODE_UNICODE); } else if (k->get_keycode() != Key::NONE) { key_mode->select(KEYMODE_KEYCODE); - } else if (k->get_physical_keycode() != Key::NONE) { + } else if (phys_key != Key::NONE) { key_mode->select(KEYMODE_PHY_KEYCODE); + if (phys_key == Key::SHIFT || phys_key == Key::CTRL || phys_key == Key::ALT || phys_key == Key::META) { + key_location->select((int)k->get_location()); + show_location = true; + } } else { // Invalid key. event = Ref<InputEvent>(); @@ -103,6 +109,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c mod_container->set_visible(show_mods); device_container->set_visible(show_device); key_mode->set_visible(show_key); + location_container->set_visible(show_location); additional_options_container->show(); // Update mode selector based on original key event. @@ -240,6 +247,9 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven k->set_physical_keycode(Key::NONE); k->set_keycode(Key::NONE); } + if (key_location->get_selected_id() == (int)KeyLocation::UNSPECIFIED) { + k->set_location(KeyLocation::UNSPECIFIED); + } } Ref<InputEventWithModifiers> mod = received_event; @@ -433,6 +443,17 @@ void InputEventConfigurationDialog::_key_mode_selected(int p_mode) { _set_event(k, original_event); } +void InputEventConfigurationDialog::_key_location_selected(int p_location) { + Ref<InputEventKey> k = event; + if (k.is_null()) { + return; + } + + k->set_location((KeyLocation)p_location); + + _set_event(k, original_event); +} + void InputEventConfigurationDialog::_input_list_item_selected() { TreeItem *selected = input_list_tree->get_selected(); @@ -594,6 +615,8 @@ void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p // Select "All Devices" by default. device_id_option->select(0); + // Also "all locations". + key_location->select(0); } if (!p_current_action_name.is_empty()) { @@ -726,5 +749,24 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { key_mode->hide(); additional_options_container->add_child(key_mode); + // Key Location Selection + + location_container = memnew(HBoxContainer); + location_container->hide(); + + Label *location_label = memnew(Label); + location_label->set_text(TTR("Physical location")); + location_container->add_child(location_label); + + key_location = memnew(OptionButton); + key_location->set_h_size_flags(Control::SIZE_EXPAND_FILL); + key_location->add_item(TTR("Any"), (int)KeyLocation::UNSPECIFIED); + key_location->add_item(TTR("Left"), (int)KeyLocation::LEFT); + key_location->add_item(TTR("Right"), (int)KeyLocation::RIGHT); + key_location->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_location_selected)); + + location_container->add_child(key_location); + additional_options_container->add_child(location_container); + main_vbox->add_child(additional_options_container); } diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h index bde0b73ade..3ef089be8b 100644 --- a/editor/input_event_configuration_dialog.h +++ b/editor/input_event_configuration_dialog.h @@ -99,6 +99,9 @@ private: OptionButton *key_mode = nullptr; + HBoxContainer *location_container = nullptr; + OptionButton *key_location = nullptr; + void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true); void _on_listen_input_changed(const Ref<InputEvent> &p_event); void _on_listen_focus_changed(); @@ -110,6 +113,7 @@ private: void _mod_toggled(bool p_checked, int p_index); void _autoremap_command_or_control_toggled(bool p_checked); void _key_mode_selected(int p_mode); + void _key_location_selected(int p_location); void _device_selection_changed(int p_option_button_index); void _set_current_device(int p_device); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 55191f44d4..c8e65e98a7 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -4074,18 +4074,19 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { script_forward->set_disabled(true); script_forward->set_tooltip_text(TTR("Go to next edited document.")); - if (p_wrapper->is_window_available()) { - menu_hb->add_child(memnew(VSeparator)); + menu_hb->add_child(memnew(VSeparator)); - make_floating = memnew(ScreenSelect); - make_floating->set_flat(true); + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + if (!make_floating->is_disabled()) { + // Override default ScreenSelect tooltip if multi-window support is available. make_floating->set_tooltip_text(TTR("Make the script editor floating.")); - make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); - - menu_hb->add_child(make_floating); - p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); } + menu_hb->add_child(make_floating); + p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed)); + tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed)); erase_tab_confirm = memnew(ConfirmationDialog); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 5798ff9d99..a018ec095b 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -643,20 +643,21 @@ ShaderEditorPlugin::ShaderEditorPlugin() { file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true); } - if (window_wrapper->is_window_available()) { - Control *padding = memnew(Control); - padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); - menu_hb->add_child(padding); - - make_floating = memnew(ScreenSelect); - make_floating->set_flat(true); + Control *padding = memnew(Control); + padding->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_hb->add_child(padding); + + make_floating = memnew(ScreenSelect); + make_floating->set_flat(true); + make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); + if (!make_floating->is_disabled()) { + // Override default ScreenSelect tooltip if multi-window support is available. make_floating->set_tooltip_text(TTR("Make the shader editor floating.")); - make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); - - menu_hb->add_child(make_floating); - window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); } + menu_hb->add_child(make_floating); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed)); + shader_list = memnew(ItemList); shader_list->set_auto_translate(false); shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 04a06ff732..484fa7b85f 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -282,6 +282,10 @@ void register_editor_types() { ei_singleton.editor_only = true; Engine::get_singleton()->add_singleton(ei_singleton); + // Required as GDExtensions can register docs at init time way before this + // class is actually instantiated. + EditorHelp::init_gdext_pointers(); + OS::get_singleton()->benchmark_end_measure("Editor", "Register Types"); } diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index a085c2e44f..b2b237269a 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -315,7 +315,7 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) { } WindowWrapper::WindowWrapper() { - if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || EDITOR_GET("interface/editor/single_window_mode") || !EDITOR_GET("interface/multi_window/enable")) { + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { return; } @@ -375,7 +375,9 @@ void ScreenSelect::_build_advanced_menu() { } void ScreenSelect::_emit_screen_signal(int p_screen_idx) { - emit_signal("request_open_in_screen", p_screen_idx); + if (!is_disabled()) { + emit_signal("request_open_in_screen", p_screen_idx); + } } void ScreenSelect::_bind_methods() { @@ -436,13 +438,19 @@ void ScreenSelect::pressed() { } ScreenSelect::ScreenSelect() { - set_tooltip_text(TTR("Make this panel floating.\nRight click to open the screen selector.")); set_button_mask(MouseButtonMask::RIGHT); set_flat(true); set_toggle_mode(true); set_focus_mode(FOCUS_NONE); set_action_mode(ACTION_MODE_BUTTON_PRESS); + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + set_disabled(true); + set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text()); + } else { + set_tooltip_text(TTR("Make this panel floating.\nRight-click to open the screen selector.")); + } + // Create the popup. const Size2 borders = Size2(4, 4) * EDSCALE; diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index e9ce92dd80..9c04f5b40e 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -44,9 +44,9 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { case Image::USED_CHANNELS_LA: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; case Image::USED_CHANNELS_R: - return EtcpakType::ETCPAK_TYPE_ETC2; + return EtcpakType::ETCPAK_TYPE_ETC2_R; case Image::USED_CHANNELS_RG: - return EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG; + return EtcpakType::ETCPAK_TYPE_ETC2_RG; case Image::USED_CHANNELS_RGB: return EtcpakType::ETCPAK_TYPE_ETC2; case Image::USED_CHANNELS_RGBA: @@ -114,6 +114,12 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { target_format = Image::FORMAT_ETC2_RGB8; r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) { + target_format = Image::FORMAT_ETC2_R11; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) { + target_format = Image::FORMAT_ETC2_RG11; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { target_format = Image::FORMAT_ETC2_RA_AS_RG; r_img->convert_rg_to_ra_rgba8(); @@ -224,22 +230,49 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { // Override the src_mip_read pointer to our temporary Vector. src_mip_read = padded_src.ptr(); } - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { - CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { - CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) { - CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) { - CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w); - } else { - ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format."); + + switch (p_compresstype) { + case EtcpakType::ETCPAK_TYPE_ETC1: + CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2: + CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: + case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: + CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_R: + CompressEtc2R8(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RG: + CompressEtc2RG8(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_DXT1: + CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_DXT5: + case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: + CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_R: + CompressRgtcR(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_RG: + CompressRgtcRG(src_mip_read, dest_mip_write, blocks, mip_w); + break; + + default: + ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format."); + break; } } diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index ff8bb635b4..9d5343740b 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -38,6 +38,8 @@ enum class EtcpakType { ETCPAK_TYPE_ETC2, ETCPAK_TYPE_ETC2_ALPHA, ETCPAK_TYPE_ETC2_RA_AS_RG, + ETCPAK_TYPE_ETC2_R, + ETCPAK_TYPE_ETC2_RG, ETCPAK_TYPE_DXT1, ETCPAK_TYPE_DXT5, ETCPAK_TYPE_DXT5_RA_AS_RG, diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml index 24f6dbd887..e3433e6e29 100644 --- a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml +++ b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml @@ -5,7 +5,7 @@ </brief_description> <description> Imports Blender scenes in the [code].blend[/code] file format through the glTF 2.0 3D import pipeline. This importer requires Blender to be installed by the user, so that it can be used to export the scene as glTF 2.0. - The location of the Blender binary is set via the [code]filesystem/import/blender/blender3_path[/code] editor setting. + The location of the Blender binary is set via the [code]filesystem/import/blender/blender_path[/code] editor setting. This importer is only used if [member ProjectSettings.filesystem/import/blender/enabled] is enabled, otherwise [code].blend[/code] files present in the project folder are not imported. Blend import requires Blender 3.0. Internally, the EditorSceneFormatImporterBlend uses the Blender glTF "Use Original" mode to reference external textures. diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp index 659a60e6a1..330310d92a 100644 --- a/modules/gltf/editor/editor_import_blend_runner.cpp +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -153,13 +153,7 @@ String dict_to_xmlrpc(const Dictionary &p_dict) { } Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) { - String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); - -#ifdef WINDOWS_ENABLED - blender_path = blender_path.path_join("blender.exe"); -#else - blender_path = blender_path.path_join("blender"); -#endif + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); List<String> args; args.push_back("--background"); @@ -198,6 +192,40 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { } } +HTTPClient::Status EditorImportBlendRunner::connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs) { + p_client->connect_to_host("127.0.0.1", rpc_port); + HTTPClient::Status status = p_client->get_status(); + + int attempts = 1; + int wait_usecs = 1000; + + bool done = false; + while (!done) { + OS::get_singleton()->delay_usec(wait_usecs); + status = p_client->get_status(); + switch (status) { + case HTTPClient::STATUS_RESOLVING: + case HTTPClient::STATUS_CONNECTING: { + p_client->poll(); + break; + } + case HTTPClient::STATUS_CONNECTED: { + done = true; + break; + } + default: { + if (attempts * wait_usecs < p_timeout_usecs) { + p_client->connect_to_host("127.0.0.1", rpc_port); + } else { + return status; + } + } + } + } + + return status; +} + Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { kill_timer->stop(); @@ -217,25 +245,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { // Connect to RPC server. Ref<HTTPClient> client = HTTPClient::create(); - client->connect_to_host("127.0.0.1", rpc_port); - - bool done = false; - while (!done) { - HTTPClient::Status status = client->get_status(); - switch (status) { - case HTTPClient::STATUS_RESOLVING: - case HTTPClient::STATUS_CONNECTING: { - client->poll(); - break; - } - case HTTPClient::STATUS_CONNECTED: { - done = true; - break; - } - default: { - ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status)); - } - } + HTTPClient::Status status = connect_blender_rpc(client, 1000000); + if (status != HTTPClient::STATUS_CONNECTED) { + ERR_FAIL_V_MSG(ERR_CONNECTION_ERROR, vformat("Unexpected status during RPC connection: %d", status)); } // Send XML request. @@ -246,9 +258,9 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } // Wait for response. - done = false; + bool done = false; while (!done) { - HTTPClient::Status status = client->get_status(); + status = client->get_status(); switch (status) { case HTTPClient::STATUS_REQUESTING: { client->poll(); diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h index b2b82394e1..626f3c9eba 100644 --- a/modules/gltf/editor/editor_import_blend_runner.h +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -33,6 +33,7 @@ #ifdef TOOLS_ENABLED +#include "core/io/http_client.h" #include "core/os/os.h" #include "scene/main/node.h" #include "scene/main/timer.h" @@ -60,6 +61,7 @@ public: bool is_running() { return blender_pid != 0 && OS::get_singleton()->is_process_running(blender_pid); } bool is_using_rpc() { return rpc_port != 0; } Error do_import(const Dictionary &p_options); + HTTPClient::Status connect_blender_rpc(const Ref<HTTPClient> &p_client, int p_timeout_usecs); EditorImportBlendRunner(); }; diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 4636782063..a91856c4a1 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -55,20 +55,7 @@ #endif static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { - String path = p_path; -#ifdef WINDOWS_ENABLED - path = path.path_join("blender.exe"); -#else - path = path.path_join("blender"); -#endif - -#if defined(MACOS_ENABLED) - if (!FileAccess::exists(path)) { - path = p_path.path_join("Blender"); - } -#endif - - if (!FileAccess::exists(path)) { + if (!FileAccess::exists(p_path)) { if (r_err) { *r_err = TTR("Path does not contain a Blender installation."); } @@ -77,7 +64,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino List<String> args; args.push_back("--version"); String pipe; - Error err = OS::get_singleton()->execute(path, args, &pipe); + Error err = OS::get_singleton()->execute(p_path, args, &pipe); if (err != OK) { if (r_err) { *r_err = TTR("Can't execute Blender binary."); @@ -87,7 +74,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino int bl = pipe.find("Blender "); if (bl == -1) { if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path); + *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path); } return false; } @@ -126,7 +113,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { - String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); if (blender_major_version == -1 || blender_minor_version == -1) { _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); @@ -369,7 +356,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const { bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled"); - if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path").operator String())) { + if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender_path").operator String())) { // Intending to import Blender, but blend not configured. return true; } @@ -409,11 +396,59 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path } } -bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path(String p_path) { - if (_test_blender_path(p_path)) { - auto_detected_path = p_path; - return true; +bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path() { + // Autodetect + auto_detected_path = ""; + +#if defined(MACOS_ENABLED) + Vector<String> find_paths = { + "/opt/homebrew/bin/blender", + "/opt/local/bin/blender", + "/usr/local/bin/blender", + "/usr/local/opt/blender", + "/Applications/Blender.app/Contents/MacOS/Blender", + }; + { + List<String> mdfind_args; + mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender"); + + String output; + Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); + if (err == OK) { + for (const String &find_path : output.split("\n")) { + find_paths.push_back(find_path.path_join("Contents/MacOS/Blender")); + } + } + } +#elif defined(WINDOWS_ENABLED) + Vector<String> find_paths = { + "C:\\Program Files\\Blender Foundation\\blender.exe", + "C:\\Program Files (x86)\\Blender Foundation\\blender.exe", + }; + { + char blender_opener_path[MAX_PATH]; + DWORD path_len = MAX_PATH; + HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len); + if (res == S_OK) { + find_paths.push_back(String(blender_opener_path).get_base_dir().path_join("blender.exe")); + } } + +#elif defined(UNIX_ENABLED) + Vector<String> find_paths = { + "/usr/bin/blender", + "/usr/local/bin/blender", + "/opt/blender/bin/blender", + }; +#endif + + for (const String &find_path : find_paths) { + if (_test_blender_path(find_path)) { + auto_detected_path = find_path; + return true; + } + } + return false; } @@ -427,7 +462,7 @@ void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_pat } void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() { if (blender_path->get_text() != String()) { - browse_dialog->set_current_dir(blender_path->get_text()); + browse_dialog->set_current_file(blender_path->get_text()); } browse_dialog->popup_centered_ratio(); @@ -479,76 +514,10 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog); } - String path = EDITOR_GET("filesystem/import/blender/blender3_path"); + String path = EDITOR_GET("filesystem/import/blender/blender_path"); - if (path == "") { - // Autodetect - auto_detected_path = ""; - -#if defined(MACOS_ENABLED) - - { - Vector<String> mdfind_paths; - { - List<String> mdfind_args; - mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender"); - - String output; - Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output); - if (err == OK) { - mdfind_paths = output.split("\n"); - } - } - - bool found = false; - for (const String &found_path : mdfind_paths) { - found = _autodetect_path(found_path.path_join("Contents/MacOS")); - if (found) { - break; - } - } - if (!found) { - found = _autodetect_path("/opt/homebrew/bin"); - } - if (!found) { - found = _autodetect_path("/opt/local/bin"); - } - if (!found) { - found = _autodetect_path("/usr/local/bin"); - } - if (!found) { - found = _autodetect_path("/usr/local/opt"); - } - if (!found) { - found = _autodetect_path("/Applications/Blender.app/Contents/MacOS"); - } - } -#elif defined(WINDOWS_ENABLED) - { - char blender_opener_path[MAX_PATH]; - DWORD path_len = MAX_PATH; - HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len); - if (res == S_OK && _autodetect_path(String(blender_opener_path).get_base_dir())) { - // Good. - } else if (_autodetect_path("C:\\Program Files\\Blender Foundation")) { - // Good. - } else { - _autodetect_path("C:\\Program Files (x86)\\Blender Foundation"); - } - } - -#elif defined(UNIX_ENABLED) - if (_autodetect_path("/usr/bin")) { - // Good. - } else if (_autodetect_path("/usr/local/bin")) { - // Good - } else { - _autodetect_path("/opt/blender/bin"); - } -#endif - if (auto_detected_path != "") { - path = auto_detected_path; - } + if (path.is_empty() && _autodetect_path()) { + path = auto_detected_path; } blender_path->set_text(path); @@ -569,7 +538,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { if (confirmed) { // Can only confirm a valid path. - EditorSettings::get_singleton()->set("filesystem/import/blender/blender3_path", blender_path->get_text()); + EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path->get_text()); EditorSettings::get_singleton()->save(); } else { // Disable Blender import diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index c1f4280170..ed1b19eaf3 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -95,7 +95,7 @@ class EditorFileSystemImportFormatSupportQueryBlend : public EditorFileSystemImp String auto_detected_path; void _validate_path(String p_path); - bool _autodetect_path(String p_path); + bool _autodetect_path(); void _path_confirmed(); diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 94c9d66f78..216309abf1 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -55,23 +55,39 @@ static void _editor_init() { // Blend to glTF importer. + String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); + if (blender_path.is_empty() && EditorSettings::get_singleton()->has_setting("filesystem/import/blender/blender3_path")) { + blender_path = EditorSettings::get_singleton()->get("filesystem/import/blender/blender3_path"); + + if (!blender_path.is_empty()) { +#if defined(MACOS_ENABLED) + if (blender_path.contains(".app")) { + blender_path += "/Contents/MacOS/Blender"; + } else { + blender_path += "/blender"; + } +#elif defined(WINDOWS_ENABLED) + blender_path += "\\blender.exe"; +#elif defined(UNIX_ENABLED) + blender_path += "/blender"; +#endif + + EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path); + } + + EditorSettings::get_singleton()->erase("filesystem/import/blender/blender3_path"); + EditorSettings::get_singleton()->save(); + } + bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled"); - String blender3_path = EDITOR_GET("filesystem/import/blender/blender3_path"); if (blend_enabled) { - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (blender3_path.is_empty()) { - WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported.")); - } else if (!da->dir_exists(blender3_path)) { - WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported.")); - } else { - Ref<EditorSceneFormatImporterBlend> importer; - importer.instantiate(); - ResourceImporterScene::add_scene_importer(importer); - - Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query; - blend_import_query.instantiate(); - EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query); - } + Ref<EditorSceneFormatImporterBlend> importer; + importer.instantiate(); + ResourceImporterScene::add_scene_importer(importer); + + Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query; + blend_import_query.instantiate(); + EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query); } memnew(EditorImportBlendRunner); EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton()); diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index bd194478d9..8e7f355114 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod ev->set_physical_keycode(physical_keycode); ev->set_key_label(fix_key_label(p_key_label, keycode)); ev->set_unicode(fix_unicode(unicode)); + ev->set_location(godot_location_from_android_code(p_physical_keycode)); ev->set_pressed(p_pressed); ev->set_echo(p_echo); diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp index f50437e82a..83ee98e8bc 100644 --- a/platform/android/android_keys_utils.cpp +++ b/platform/android/android_keys_utils.cpp @@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) { } return Key::UNKNOWN; } + +KeyLocation godot_location_from_android_code(unsigned int p_code) { + for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) { + if (android_godot_location_pairs[i].android_code == p_code) { + return android_godot_location_pairs[i].godot_code; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index 5cf5628a8b..77c0911f2b 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = { Key godot_code_from_android_code(unsigned int p_code); +// Key location determination. +struct AndroidGodotLocationPair { + unsigned int android_code = 0; + KeyLocation godot_code = KeyLocation::UNSPECIFIED; +}; + +static AndroidGodotLocationPair android_godot_location_pairs[] = { + { AKEYCODE_ALT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_CTRL_LEFT, KeyLocation::LEFT }, + { AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_META_LEFT, KeyLocation::LEFT }, + { AKEYCODE_META_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_MAX, KeyLocation::UNSPECIFIED } +}; + +KeyLocation godot_location_from_android_code(unsigned int p_code); + #endif // ANDROID_KEYS_UTILS_H diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 3d19222fa8..3fdcc07f0b 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -119,7 +119,7 @@ public: // MARK: Keyboard - void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed); + void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location); bool is_keyboard_active() const; // MARK: Motion diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index c31f503605..c660dc5697 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -247,7 +247,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) { // MARK: Keyboard -void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) { +void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) { Ref<InputEventKey> ev; ev.instantiate(); ev->set_echo(false); @@ -270,6 +270,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph ev->set_key_label(p_unshifted); ev->set_physical_keycode(p_physical); ev->set_unicode(fix_unicode(p_char)); + ev->set_location(p_location); perform_event(ev); } diff --git a/platform/ios/key_mapping_ios.h b/platform/ios/key_mapping_ios.h index 6cc61175bb..8874da3024 100644 --- a/platform/ios/key_mapping_ios.h +++ b/platform/ios/key_mapping_ios.h @@ -41,6 +41,7 @@ class KeyMappingIOS { public: static void initialize(); static Key remap_key(CFIndex p_keycode); + static KeyLocation key_location(CFIndex p_keycode); }; #endif // KEY_MAPPING_IOS_H diff --git a/platform/ios/key_mapping_ios.mm b/platform/ios/key_mapping_ios.mm index d2c84884d1..61f28aa84b 100644 --- a/platform/ios/key_mapping_ios.mm +++ b/platform/ios/key_mapping_ios.mm @@ -38,6 +38,7 @@ struct HashMapHasherKeys { }; HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map; +HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingIOS::initialize() { if (@available(iOS 13.4, *)) { @@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() { keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard. keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU; keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA; + + location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT; } } @@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) { } return Key::NONE; } + +KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) { + if (@available(iOS 13.4, *)) { + const KeyLocation *location = location_map.getptr(p_keycode); + if (location) { + return *location; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm index bc6eb63ed5..8b614662b7 100644 --- a/platform/ios/keyboard_input_view.mm +++ b/platform/ios/keyboard_input_view.mm @@ -116,8 +116,8 @@ - (void)deleteText:(NSInteger)charactersToDelete { for (int i = 0; i < charactersToDelete; i++) { - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } @@ -137,8 +137,8 @@ key = Key::SPACE; } - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm index 1f55670b68..6f6c04c2c8 100644 --- a/platform/ios/view_controller.mm +++ b/platform/ios/view_controller.mm @@ -78,13 +78,15 @@ us = u32lbl[0]; } + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + if (!u32text.is_empty() && !u32text.begins_with("UIKey")) { for (int i = 0; i < u32text.length(); i++) { const char32_t c = u32text[i]; - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } else { - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } } @@ -110,7 +112,9 @@ us = u32lbl[0]; } - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false); + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location); } } } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index f49f00e60f..20e2e897f2 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -3512,6 +3512,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool keypress = xkeyevent->type == KeyPress; Key keycode = KeyMappingX11::get_keycode(keysym_keycode); Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode); if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) { keycode -= 'a' - 'A'; @@ -3549,6 +3550,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_unicode(fix_unicode(tmp[i])); } + k->set_location(key_location); + k->set_echo(false); if (k->get_keycode() == Key::BACKTAB) { @@ -3574,6 +3577,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, Key keycode = KeyMappingX11::get_keycode(keysym_keycode); Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode); + KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode); + /* Phase 3, obtain a unicode character from the keysym */ // KeyMappingX11 also translates keysym to unicode. @@ -3673,6 +3678,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, if (keypress) { k->set_unicode(fix_unicode(unicode)); } + + k->set_location(key_location); + k->set_echo(p_echo); if (k->get_keycode() == Key::BACKTAB) { diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp index c0e6b91d57..b589a2a573 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.cpp +++ b/platform/linuxbsd/x11/key_mapping_x11.cpp @@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() { xkeysym_unicode_map[0x13BD] = 0x0153; xkeysym_unicode_map[0x13BE] = 0x0178; xkeysym_unicode_map[0x20AC] = 0x20AC; + + // Scancode to physical location map. + // Ctrl. + location_map[0x25] = KeyLocation::LEFT; + location_map[0x69] = KeyLocation::RIGHT; + // Shift. + location_map[0x32] = KeyLocation::LEFT; + location_map[0x3E] = KeyLocation::RIGHT; + // Alt. + location_map[0x40] = KeyLocation::LEFT; + location_map[0x6C] = KeyLocation::RIGHT; + // Meta. + location_map[0x85] = KeyLocation::LEFT; + location_map[0x86] = KeyLocation::RIGHT; } Key KeyMappingX11::get_keycode(KeySym p_keysym) { @@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) { } return 0; } + +KeyLocation KeyMappingX11::get_location(unsigned int p_code) { + const KeyLocation *location = location_map.getptr(p_code); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h index ae8fd67f27..a51ee5f48e 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.h +++ b/platform/linuxbsd/x11/key_mapping_x11.h @@ -54,6 +54,7 @@ class KeyMappingX11 { static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map; static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv; static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map; + static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; KeyMappingX11() {} @@ -64,6 +65,7 @@ public: static unsigned int get_xlibcode(Key p_keysym); static Key get_scancode(unsigned int p_code); static char32_t get_unicode_from_keysym(KeySym p_keysym); + static KeyLocation get_location(unsigned int p_code); }; #endif // KEY_MAPPING_X11_H diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index cdd17a7e76..24d4a349e2 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -75,6 +75,7 @@ public: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; }; struct WindowData { diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 2bfe16828a..396b943251 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -478,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); k->set_unicode(ke.unicode); + k->set_location(ke.location); _push_input(k); } else { @@ -506,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_keycode(ke.keycode); k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); + k->set_location(ke.location); if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { k->set_unicode(key_event_buffer[i + 1].unicode); diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 61065291d7..4505becbc2 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -606,6 +606,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = false; ds->push_to_key_event_buffer(ke); @@ -671,6 +672,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key(key); ke.key_label = KeyMappingMacOS::remap_key(key, mod, true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location(key); ds->push_to_key_event_buffer(ke); } @@ -698,6 +700,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = true; ds->push_to_key_event_buffer(ke); diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h index 1bda4eb406..f5b0ff8d02 100644 --- a/platform/macos/key_mapping_macos.h +++ b/platform/macos/key_mapping_macos.h @@ -45,6 +45,7 @@ public: static Key translate_key(unsigned int p_key); static unsigned int unmap_key(Key p_key); static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode); + static KeyLocation translate_location(unsigned int p_key); // Mapping for menu shortcuts. static String keycode_get_native_string(Key p_keycode); diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm index db3fa4e02d..b5e72048e7 100644 --- a/platform/macos/key_mapping_macos.mm +++ b/platform/macos/key_mapping_macos.mm @@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys; HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map; HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv; HashMap<Key, char32_t, HashMapHasherKeys> keycode_map; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingMacOS::initialize() { numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal @@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() { keycode_map[Key::BAR] = '|'; keycode_map[Key::BRACERIGHT] = '}'; keycode_map[Key::ASCIITILDE] = '~'; + + // Keysym -> physical location. + // Ctrl. + location_map[0x3b] = KeyLocation::LEFT; + location_map[0x3e] = KeyLocation::RIGHT; + // Shift. + location_map[0x38] = KeyLocation::LEFT; + location_map[0x3c] = KeyLocation::RIGHT; + // Alt/Option. + location_map[0x3a] = KeyLocation::LEFT; + location_map[0x3d] = KeyLocation::RIGHT; + // Meta/Command (yes, right < left). + location_map[0x36] = KeyLocation::RIGHT; + location_map[0x37] = KeyLocation::LEFT; } bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) { @@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_ } } +// Translates a macOS keycode to a Godot key location. +KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) { + const KeyLocation *location = location_map.getptr(p_key); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} + String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) { const char32_t *key = keycode_map.getptr(p_keycode); if (key) { diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index b4a190d47e..aacbe4879f 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false); Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true); + KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data()); DisplayServerWeb::KeyEvent ke; @@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin ke.physical_keycode = scancode; ke.key_label = fix_key_label(c, keycode); ke.unicode = fix_unicode(c); + ke.location = location; ke.mod = p_modifiers; if (ds->key_event_pos >= ds->key_event_buffer.size()) { @@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() { ev->set_physical_keycode(ke.physical_keycode); ev->set_key_label(ke.key_label); ev->set_unicode(ke.unicode); + ev->set_location(ke.location); if (ke.raw) { dom2godot_mod(ev, ke.mod, ke.keycode); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 140aef952b..682d10704f 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -95,6 +95,7 @@ private: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; int mod = 0; }; diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc index cd94b779c0..b20a3a46b9 100644 --- a/platform/web/dom_keys.inc +++ b/platform/web/dom_keys.inc @@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b return Key::NONE; #undef DOM2GODOT } + +KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) { +#define DOM2GODOT(m_str, m_godot_code) \ + if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \ + return KeyLocation::m_godot_code; \ + } + + DOM2GODOT("AltLeft", LEFT); + DOM2GODOT("AltRight", RIGHT); + DOM2GODOT("ControlLeft", LEFT); + DOM2GODOT("ControlRight", RIGHT); + DOM2GODOT("MetaLeft", LEFT); + DOM2GODOT("MetaRight", RIGHT); + DOM2GODOT("OSLeft", LEFT); + DOM2GODOT("OSRight", RIGHT); + DOM2GODOT("ShiftLeft", LEFT); + DOM2GODOT("ShiftRight", RIGHT); + + return KeyLocation::UNSPECIFIED; +#undef DOM2GODOT +} diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 1c9b86d35d..657caa7939 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -169,20 +169,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) { use_raw_input = true; - RAWINPUTDEVICE rid[1] = {}; - rid[0].usUsagePage = 0x01; - rid[0].usUsage = 0x02; + RAWINPUTDEVICE rid[2] = {}; + rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE rid[0].dwFlags = 0; + rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD + rid[1].dwFlags = 0; + if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) { // Follow the defined window rid[0].hwndTarget = windows[p_target_window].hWnd; + rid[1].hwndTarget = windows[p_target_window].hWnd; } else { // Follow the keyboard focus rid[0].hwndTarget = 0; + rid[1].hwndTarget = 0; } - if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) { + if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) { // Registration failed. use_raw_input = false; } @@ -3348,7 +3354,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_INPUT: { - if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) { + if (!use_raw_input) { break; } @@ -3366,7 +3372,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA RAWINPUT *raw = (RAWINPUT *)lpb; - if (raw->header.dwType == RIM_TYPEMOUSE) { + if (raw->header.dwType == RIM_TYPEKEYBOARD) { + if (raw->data.keyboard.VKey == VK_SHIFT) { + // If multiple Shifts are held down at the same time, + // Windows natively only sends a KEYUP for the last one to be released. + if (raw->data.keyboard.Flags & RI_KEY_BREAK) { + if (GetAsyncKeyState(VK_SHIFT) < 0) { + // A Shift is released, but another Shift is still held + ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); + + KeyEvent ke; + ke.shift = false; + ke.alt = alt_mem; + ke.control = control_mem; + ke.meta = meta_mem; + ke.uMsg = WM_KEYUP; + ke.window_id = window_id; + + ke.wParam = VK_SHIFT; + // data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift. + // Bit 30 -> key was previously down, bit 31 -> key is being released. + ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31; + key_event_buffer[key_event_pos++] = ke; + } + } + } + } else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) { Ref<InputEventMouseMotion> mm; mm.instantiate(); @@ -4371,6 +4402,7 @@ void DisplayServerWindows::_process_key_events() { } Key key_label = keycode; Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); + KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); static BYTE keyboard_state[256]; memset(keyboard_state, 0, 256); @@ -4397,6 +4429,7 @@ void DisplayServerWindows::_process_key_events() { } k->set_keycode(keycode); k->set_physical_keycode(physical_keycode); + k->set_location(location); k->set_key_label(key_label); if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index b376854c0c..20905d0fe9 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map; HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingWindows::initialize() { // VK_LBUTTON (0x01) @@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() { scansym_map_ext[0x6C] = Key::LAUNCHMAIL; scansym_map_ext[0x6D] = Key::LAUNCHMEDIA; scansym_map_ext[0x78] = Key::MEDIARECORD; + + // Scancode to physical location map. + // Shift. + location_map[0x2A] = KeyLocation::LEFT; + location_map[0x36] = KeyLocation::RIGHT; + // Meta. + location_map[0x5B] = KeyLocation::LEFT; + location_map[0x5C] = KeyLocation::RIGHT; + // Ctrl and Alt must be handled differently. } Key KeyMappingWindows::get_keysym(unsigned int p_code) { @@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) { p_code == VK_RIGHT || p_code == VK_DOWN; } + +KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) { + // Right- ctrl and alt have the same scancode as left, but are in the extended keys. + const Key *key = scansym_map.getptr(p_code); + if (key && (*key == Key::CTRL || *key == Key::ALT)) { + return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT; + } + const KeyLocation *location = location_map.getptr(p_code); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index a98aa7ed68..e6f184a2cc 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -47,6 +47,7 @@ public: static unsigned int get_scancode(Key p_keycode); static Key get_scansym(unsigned int p_code, bool p_extended); static bool is_extended_key(unsigned int p_code); + static KeyLocation get_location(unsigned int p_code, bool p_extended); }; #endif // KEY_MAPPING_WINDOWS_H diff --git a/tests/core/input/test_input_event_key.h b/tests/core/input/test_input_event_key.h index 3317941fad..80918542ce 100644 --- a/tests/core/input/test_input_event_key.h +++ b/tests/core/input/test_input_event_key.h @@ -85,6 +85,16 @@ TEST_CASE("[InputEventKey] Key correctly stores and retrieves unicode") { CHECK(key.get_unicode() != 'y'); } +TEST_CASE("[InputEventKey] Key correctly stores and retrieves location") { + InputEventKey key; + + CHECK(key.get_location() == KeyLocation::UNSPECIFIED); + + key.set_location(KeyLocation::LEFT); + CHECK(key.get_location() == KeyLocation::LEFT); + CHECK(key.get_location() != KeyLocation::RIGHT); +} + TEST_CASE("[InputEventKey] Key correctly stores and checks echo") { InputEventKey key; @@ -144,32 +154,36 @@ TEST_CASE("[InputEventKey] Key correctly converts itself to text") { TEST_CASE("[InputEventKey] Key correctly converts its state to a string representation") { InputEventKey none_key; - CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, pressed=false, echo=false"); + CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, location=unspecified, pressed=false, echo=false"); // Set physical key to Escape. none_key.set_physical_keycode(Key::ESCAPE); - CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, pressed=false, echo=false"); + CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, location=unspecified, pressed=false, echo=false"); InputEventKey key; // Set physical to None, set keycode to Space. key.set_keycode(Key::SPACE); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=false, echo=false"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=unspecified, pressed=false, echo=false"); + + // Set location + key.set_location(KeyLocation::RIGHT); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=false, echo=false"); // Set pressed to true. key.set_pressed(true); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=false"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=false"); // set echo to true. key.set_echo(true); - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=true"); // Press Ctrl and Alt. key.set_ctrl_pressed(true); key.set_alt_pressed(true); #ifdef MACOS_ENABLED - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, location=right, pressed=true, echo=true"); #else - CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, pressed=true, echo=true"); + CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, location=right, pressed=true, echo=true"); #endif } @@ -291,6 +305,34 @@ TEST_CASE("[IsMatch] Keys are correctly matched") { CHECK(key2.is_match(match, true) == true); CHECK(key2.is_match(no_match, true) == false); + + // Physical key with location. + InputEventKey key3; + key3.set_keycode(Key::NONE); + key3.set_physical_keycode(Key::SHIFT); + + Ref<InputEventKey> loc_ref = key.create_reference(Key::NONE); + + loc_ref->set_keycode(Key::SHIFT); + loc_ref->set_physical_keycode(Key::SHIFT); + + CHECK(key3.is_match(loc_ref, false) == true); + key3.set_location(KeyLocation::UNSPECIFIED); + CHECK(key3.is_match(loc_ref, false) == true); + + loc_ref->set_location(KeyLocation::LEFT); + CHECK(key3.is_match(loc_ref, false) == true); + + key3.set_location(KeyLocation::LEFT); + CHECK(key3.is_match(loc_ref, false) == true); + + key3.set_location(KeyLocation::RIGHT); + CHECK(key3.is_match(loc_ref, false) == false); + + // Keycode key with location. + key3.set_physical_keycode(Key::NONE); + key3.set_keycode(Key::SHIFT); + CHECK(key3.is_match(loc_ref, false) == true); } } // namespace TestInputEventKey diff --git a/thirdparty/README.md b/thirdparty/README.md index 533f4592e1..c3f0e25b9a 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -225,6 +225,9 @@ Files extracted from upstream source: ``` - `AUTHORS.txt` and `LICENSE.txt` +Two files (`ProcessRGB.{cpp,hpp}`) have been modified to provide ETC2_R and ETC2_RG compression, +the changes are based on the existing code. + Two files (`ProcessRgtc.{cpp,hpp}`) have been added to provide RGTC compression implementation, based on library's `ProcessDxtc.{cpp,hpp}`. diff --git a/thirdparty/etcpak/ProcessRGB.cpp b/thirdparty/etcpak/ProcessRGB.cpp index 4dc3bf23af..0caa687bc6 100644 --- a/thirdparty/etcpak/ProcessRGB.cpp +++ b/thirdparty/etcpak/ProcessRGB.cpp @@ -4181,3 +4181,145 @@ void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size } while( --blocks ); } + +// -- GODOT start -- +void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) +{ + int w = 0; + uint8_t r[4*4]; + do + { +#ifdef __SSE4_1__ + __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); + __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); + __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); + __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); + + _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); + + __m128i c0 = _mm_castps_si128( px0 ); + __m128i c1 = _mm_castps_si128( px1 ); + __m128i c2 = _mm_castps_si128( px2 ); + __m128i c3 = _mm_castps_si128( px3 ); + + __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); + + __m128i a0 = _mm_shuffle_epi8( c0, mask ); + __m128i a1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i a2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i a3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + __m128i s0 = _mm_or_si128( a0, a1 ); + __m128i s1 = _mm_or_si128( a2, a3 ); + __m128i s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)r, s2 ); + + src += 4; +#else + auto ptr8 = r; + for( int x=0; x<4; x++ ) + { + auto v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src += width; + v = *src; + *ptr8++ = (v & 0xff0000) >> 16; + src -= width * 3 - 1; + } +#endif + if( ++w == width/4 ) + { + src += width * 3; + w = 0; + } + *dst++ = ProcessAlpha_ETC2( r ); + } + while( --blocks ); +} + +void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) +{ + int w = 0; + uint8_t rg[4*4*2]; + do + { +#ifdef __SSE4_1__ + __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); + __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); + __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); + __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); + + _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); + + __m128i c0 = _mm_castps_si128( px0 ); + __m128i c1 = _mm_castps_si128( px1 ); + __m128i c2 = _mm_castps_si128( px2 ); + __m128i c3 = _mm_castps_si128( px3 ); + + __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); + + __m128i r0 = _mm_shuffle_epi8( c0, mask ); + __m128i r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + __m128i s0 = _mm_or_si128( r0, r1 ); + __m128i s1 = _mm_or_si128( r2, r3 ); + __m128i s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)rg, s2 ); + + mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); + + r0 = _mm_shuffle_epi8( c0, mask ); + r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + + s0 = _mm_or_si128( r0, r1 ); + s1 = _mm_or_si128( r2, r3 ); + s2 = _mm_or_si128( s0, s1 ); + + _mm_store_si128( (__m128i*)&rg[16], s2 ); + src += 4; +#else + auto ptrr = rg; + auto ptrg = ptrr + 16; + for( int x=0; x<4; x++ ) + { + auto v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src += width; + v = *src; + *ptrr++ = (v & 0xff0000) >> 16; + *ptrg++ = (v & 0xff00) >> 8; + src -= width * 3 - 1; + } +#endif + if( ++w == width/4 ) + { + src += width * 3; + w = 0; + } + *dst++ = ProcessAlpha_ETC2( rg ); + *dst++ = ProcessAlpha_ETC2( &rg[16] ); + } + while( --blocks ); +} +// -- GODOT end -- diff --git a/thirdparty/etcpak/ProcessRGB.hpp b/thirdparty/etcpak/ProcessRGB.hpp index 043b46e636..050ea42562 100644 --- a/thirdparty/etcpak/ProcessRGB.hpp +++ b/thirdparty/etcpak/ProcessRGB.hpp @@ -9,5 +9,8 @@ void CompressEtc1Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_ void CompressEtc1RgbDither( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); void CompressEtc2Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); - +// -- GODOT start -- +void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); +void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); +// -- GODOT end -- #endif diff --git a/thirdparty/etcpak/ProcessRgtc.cpp b/thirdparty/etcpak/ProcessRgtc.cpp index 3a283b743b..5eec2648db 100644 --- a/thirdparty/etcpak/ProcessRgtc.cpp +++ b/thirdparty/etcpak/ProcessRgtc.cpp @@ -6,8 +6,49 @@ #include <assert.h> #include <string.h> +#if defined __AVX__ && !defined __SSE4_1__ +# define __SSE4_1__ +#endif + +#if defined __SSE4_1__ || defined __AVX2__ +# ifdef _MSC_VER +# include <intrin.h> +# else +# include <x86intrin.h> +# ifndef _mm256_cvtsi256_si32 +# define _mm256_cvtsi256_si32( v ) ( _mm_cvtsi128_si32( _mm256_castsi256_si128( v ) ) ) +# endif +# endif +#endif + static const uint8_t AlphaIndexTable[8] = { 1, 7, 6, 5, 4, 3, 2, 0 }; +static const uint8_t AlphaIndexTable_SSE[64] = { + 9, 15, 14, 13, 12, 11, 10, 8, 57, 63, 62, 61, 60, 59, 58, 56, + 49, 55, 54, 53, 52, 51, 50, 48, 41, 47, 46, 45, 44, 43, 42, 40, + 33, 39, 38, 37, 36, 35, 34, 32, 25, 31, 30, 29, 28, 27, 26, 24, + 17, 23, 22, 21, 20, 19, 18, 16, 1, 7, 6, 5, 4, 3, 2, 0, +}; + +static const uint16_t DivTableAlpha[256] = { + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe38e, 0xcccc, 0xba2e, 0xaaaa, 0x9d89, 0x9249, 0x8888, 0x8000, + 0x7878, 0x71c7, 0x6bca, 0x6666, 0x6186, 0x5d17, 0x590b, 0x5555, 0x51eb, 0x4ec4, 0x4bda, 0x4924, 0x469e, 0x4444, 0x4210, 0x4000, + 0x3e0f, 0x3c3c, 0x3a83, 0x38e3, 0x3759, 0x35e5, 0x3483, 0x3333, 0x31f3, 0x30c3, 0x2fa0, 0x2e8b, 0x2d82, 0x2c85, 0x2b93, 0x2aaa, + 0x29cb, 0x28f5, 0x2828, 0x2762, 0x26a4, 0x25ed, 0x253c, 0x2492, 0x23ee, 0x234f, 0x22b6, 0x2222, 0x2192, 0x2108, 0x2082, 0x2000, + 0x1f81, 0x1f07, 0x1e91, 0x1e1e, 0x1dae, 0x1d41, 0x1cd8, 0x1c71, 0x1c0e, 0x1bac, 0x1b4e, 0x1af2, 0x1a98, 0x1a41, 0x19ec, 0x1999, + 0x1948, 0x18f9, 0x18ac, 0x1861, 0x1818, 0x17d0, 0x178a, 0x1745, 0x1702, 0x16c1, 0x1681, 0x1642, 0x1605, 0x15c9, 0x158e, 0x1555, + 0x151d, 0x14e5, 0x14af, 0x147a, 0x1446, 0x1414, 0x13e2, 0x13b1, 0x1381, 0x1352, 0x1323, 0x12f6, 0x12c9, 0x129e, 0x1273, 0x1249, + 0x121f, 0x11f7, 0x11cf, 0x11a7, 0x1181, 0x115b, 0x1135, 0x1111, 0x10ec, 0x10c9, 0x10a6, 0x1084, 0x1062, 0x1041, 0x1020, 0x1000, + 0x0fe0, 0x0fc0, 0x0fa2, 0x0f83, 0x0f66, 0x0f48, 0x0f2b, 0x0f0f, 0x0ef2, 0x0ed7, 0x0ebb, 0x0ea0, 0x0e86, 0x0e6c, 0x0e52, 0x0e38, + 0x0e1f, 0x0e07, 0x0dee, 0x0dd6, 0x0dbe, 0x0da7, 0x0d90, 0x0d79, 0x0d62, 0x0d4c, 0x0d36, 0x0d20, 0x0d0b, 0x0cf6, 0x0ce1, 0x0ccc, + 0x0cb8, 0x0ca4, 0x0c90, 0x0c7c, 0x0c69, 0x0c56, 0x0c43, 0x0c30, 0x0c1e, 0x0c0c, 0x0bfa, 0x0be8, 0x0bd6, 0x0bc5, 0x0bb3, 0x0ba2, + 0x0b92, 0x0b81, 0x0b70, 0x0b60, 0x0b50, 0x0b40, 0x0b30, 0x0b21, 0x0b11, 0x0b02, 0x0af3, 0x0ae4, 0x0ad6, 0x0ac7, 0x0ab8, 0x0aaa, + 0x0a9c, 0x0a8e, 0x0a80, 0x0a72, 0x0a65, 0x0a57, 0x0a4a, 0x0a3d, 0x0a30, 0x0a23, 0x0a16, 0x0a0a, 0x09fd, 0x09f1, 0x09e4, 0x09d8, + 0x09cc, 0x09c0, 0x09b4, 0x09a9, 0x099d, 0x0991, 0x0986, 0x097b, 0x0970, 0x0964, 0x095a, 0x094f, 0x0944, 0x0939, 0x092f, 0x0924, + 0x091a, 0x090f, 0x0905, 0x08fb, 0x08f1, 0x08e7, 0x08dd, 0x08d3, 0x08ca, 0x08c0, 0x08b7, 0x08ad, 0x08a4, 0x089a, 0x0891, 0x0888, + 0x087f, 0x0876, 0x086d, 0x0864, 0x085b, 0x0853, 0x084a, 0x0842, 0x0839, 0x0831, 0x0828, 0x0820, 0x0818, 0x0810, 0x0808, 0x0800, +}; + static etcpak_force_inline uint64_t ProcessAlpha( const uint8_t* src ) { uint8_t solid8 = *src; @@ -40,20 +81,72 @@ static etcpak_force_inline uint64_t ProcessAlpha( const uint8_t* src ) return max | ( min << 8 ) | ( data << 16 ); } +#ifdef __SSE4_1__ +static etcpak_force_inline uint64_t Process_Alpha_SSE( __m128i a ) +{ + __m128i solidCmp = _mm_shuffle_epi8( a, _mm_setzero_si128() ); + __m128i cmpRes = _mm_cmpeq_epi8( a, solidCmp ); + if( _mm_testc_si128( cmpRes, _mm_set1_epi32( -1 ) ) ) + { + return _mm_cvtsi128_si32( a ) & 0xFF; + } + + __m128i a1 = _mm_shuffle_epi32( a, _MM_SHUFFLE( 2, 3, 0, 1 ) ); + __m128i max1 = _mm_max_epu8( a, a1 ); + __m128i min1 = _mm_min_epu8( a, a1 ); + __m128i amax2 = _mm_shuffle_epi32( max1, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128i amin2 = _mm_shuffle_epi32( min1, _MM_SHUFFLE( 0, 0, 2, 2 ) ); + __m128i max2 = _mm_max_epu8( max1, amax2 ); + __m128i min2 = _mm_min_epu8( min1, amin2 ); + __m128i amax3 = _mm_alignr_epi8( max2, max2, 2 ); + __m128i amin3 = _mm_alignr_epi8( min2, min2, 2 ); + __m128i max3 = _mm_max_epu8( max2, amax3 ); + __m128i min3 = _mm_min_epu8( min2, amin3 ); + __m128i amax4 = _mm_alignr_epi8( max3, max3, 1 ); + __m128i amin4 = _mm_alignr_epi8( min3, min3, 1 ); + __m128i max = _mm_max_epu8( max3, amax4 ); + __m128i min = _mm_min_epu8( min3, amin4 ); + __m128i minmax = _mm_unpacklo_epi8( max, min ); + + __m128i r = _mm_sub_epi8( max, min ); + int range = _mm_cvtsi128_si32( r ) & 0xFF; + __m128i rv = _mm_set1_epi16( DivTableAlpha[range] ); + + __m128i v = _mm_sub_epi8( a, min ); + + __m128i lo16 = _mm_unpacklo_epi8( v, _mm_setzero_si128() ); + __m128i hi16 = _mm_unpackhi_epi8( v, _mm_setzero_si128() ); + + __m128i lomul = _mm_mulhi_epu16( lo16, rv ); + __m128i himul = _mm_mulhi_epu16( hi16, rv ); + + __m128i p0 = _mm_packus_epi16( lomul, himul ); + __m128i p1 = _mm_or_si128( _mm_and_si128( p0, _mm_set1_epi16( 0x3F ) ), _mm_srai_epi16( _mm_and_si128( p0, _mm_set1_epi16( 0x3F00 ) ), 5 ) ); + __m128i p2 = _mm_packus_epi16( p1, p1 ); + + uint64_t pi = _mm_cvtsi128_si64( p2 ); + uint64_t data = 0; + for( int i=0; i<8; i++ ) + { + uint64_t idx = AlphaIndexTable_SSE[(pi>>(i*8)) & 0x3F]; + data |= idx << (i*6); + } + return (uint64_t)(uint16_t)_mm_cvtsi128_si32( minmax ) | ( data << 16 ); +} +#endif + void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t width) { int i = 0; auto ptr = dst; do { - uint32_t rgba[4 * 4]; - uint8_t r[4 * 4]; +#ifdef __SSE4_1__ + __m128i px0 = _mm_loadu_si128( (__m128i*)( src + width * 0 ) ); + __m128i px1 = _mm_loadu_si128( (__m128i*)( src + width * 1 ) ); + __m128i px2 = _mm_loadu_si128( (__m128i*)( src + width * 2 ) ); + __m128i px3 = _mm_loadu_si128( (__m128i*)( src + width * 3 ) ); - auto tmp = (char *)rgba; - memcpy(tmp, src + width * 0, 4 * 4); - memcpy(tmp + 4 * 4, src + width * 1, 4 * 4); - memcpy(tmp + 8 * 4, src + width * 2, 4 * 4); - memcpy(tmp + 12 * 4, src + width * 3, 4 * 4); src += 4; if (++i == width / 4) { @@ -61,11 +154,38 @@ void CompressRgtcR(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t w i = 0; } - for (int i = 0; i < 16; i++) + __m128i mask = _mm_setr_epi32( 0x0c080400, -1, -1, -1 ); + + __m128i m0 = _mm_shuffle_epi8( px0, mask ); + __m128i m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + __m128i m4 = _mm_or_si128( m0, m1 ); + __m128i m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); +#else + uint8_t r[4 * 4]; + auto rgba = src; + for (int i = 0; i < 4; i++) + { + r[i * 4] = rgba[0] & 0xff; + r[i * 4 + 1] = rgba[1] & 0xff; + r[i * 4 + 2] = rgba[2] & 0xff; + r[i * 4 + 3] = rgba[3] & 0xff; + + rgba += width; + } + + src += 4; + if (++i == width / 4) { - r[i] = rgba[i] & 0x000000FF; + src += width * 3; + i = 0; } + *ptr++ = ProcessAlpha(r); +#endif } while (--blocks); } @@ -76,15 +196,12 @@ void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t auto ptr = dst; do { - uint32_t rgba[4 * 4]; - uint8_t r[4 * 4]; - uint8_t g[4 * 4]; - - auto tmp = (char *)rgba; - memcpy(tmp, src + width * 0, 4 * 4); - memcpy(tmp + 4 * 4, src + width * 1, 4 * 4); - memcpy(tmp + 8 * 4, src + width * 2, 4 * 4); - memcpy(tmp + 12 * 4, src + width * 3, 4 * 4); +#ifdef __SSE4_1__ + __m128i px0 = _mm_loadu_si128( (__m128i*)( src + width * 0 ) ); + __m128i px1 = _mm_loadu_si128( (__m128i*)( src + width * 1 ) ); + __m128i px2 = _mm_loadu_si128( (__m128i*)( src + width * 2 ) ); + __m128i px3 = _mm_loadu_si128( (__m128i*)( src + width * 3 ) ); + src += 4; if (++i == width / 4) { @@ -92,13 +209,55 @@ void CompressRgtcRG(const uint32_t *src, uint64_t *dst, uint32_t blocks, size_t i = 0; } - for (int i = 0; i < 16; i++) + __m128i mask = _mm_setr_epi32( 0x0c080400, -1, -1, -1 ); + + __m128i m0 = _mm_shuffle_epi8( px0, mask ); + __m128i m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + __m128i m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + __m128i m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + __m128i m4 = _mm_or_si128( m0, m1 ); + __m128i m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); + + mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); + + m0 = _mm_shuffle_epi8( px0, mask ); + m1 = _mm_shuffle_epi8( px1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); + m2 = _mm_shuffle_epi8( px2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); + m3 = _mm_shuffle_epi8( px3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); + m4 = _mm_or_si128( m0, m1 ); + m5 = _mm_or_si128( m2, m3 ); + + *ptr++ = Process_Alpha_SSE(_mm_or_si128( m4, m5 )); +#else + uint8_t rg[4 * 4 * 2]; + auto rgba = src; + for (int i = 0; i < 4; i++) { - r[i] = rgba[i] & 0x000000FF; - g[i] = (rgba[i] & 0x0000FF00) >> 8; + rg[i * 4] = rgba[0] & 0xff; + rg[i * 4 + 1] = rgba[1] & 0xff; + rg[i * 4 + 2] = rgba[2] & 0xff; + rg[i * 4 + 3] = rgba[3] & 0xff; + + rg[16 + i * 4] = (rgba[0] & 0xff00) >> 8; + rg[16 + i * 4 + 1] = (rgba[1] & 0xff00) >> 8; + rg[16 + i * 4 + 2] = (rgba[2] & 0xff00) >> 8; + rg[16 + i * 4 + 3] = (rgba[3] & 0xff00) >> 8; + + rgba += width; } - *ptr++ = ProcessAlpha(r); - *ptr++ = ProcessAlpha(g); + + src += 4; + if (++i == width / 4) + { + src += width * 3; + i = 0; + } + + *ptr++ = ProcessAlpha(rg); + *ptr++ = ProcessAlpha(&rg[16]); +#endif } while (--blocks); } diff --git a/thirdparty/etcpak/patches/etc2-r-rg.patch b/thirdparty/etcpak/patches/etc2-r-rg.patch new file mode 100644 index 0000000000..5d6c117bf7 --- /dev/null +++ b/thirdparty/etcpak/patches/etc2-r-rg.patch @@ -0,0 +1,164 @@ +diff --git a/thirdparty/etcpak/ProcessRGB.cpp b/thirdparty/etcpak/ProcessRGB.cpp +index 4dc3bf23af..0caa687bc6 100644 +--- a/thirdparty/etcpak/ProcessRGB.cpp ++++ b/thirdparty/etcpak/ProcessRGB.cpp +@@ -4181,3 +4181,145 @@ void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size + } + while( --blocks ); + } ++ ++// -- GODOT start -- ++void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) ++{ ++ int w = 0; ++ uint8_t r[4*4]; ++ do ++ { ++#ifdef __SSE4_1__ ++ __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); ++ __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); ++ __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); ++ __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); ++ ++ _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); ++ ++ __m128i c0 = _mm_castps_si128( px0 ); ++ __m128i c1 = _mm_castps_si128( px1 ); ++ __m128i c2 = _mm_castps_si128( px2 ); ++ __m128i c3 = _mm_castps_si128( px3 ); ++ ++ __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); ++ ++ __m128i a0 = _mm_shuffle_epi8( c0, mask ); ++ __m128i a1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ __m128i a2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ __m128i a3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ __m128i s0 = _mm_or_si128( a0, a1 ); ++ __m128i s1 = _mm_or_si128( a2, a3 ); ++ __m128i s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)r, s2 ); ++ ++ src += 4; ++#else ++ auto ptr8 = r; ++ for( int x=0; x<4; x++ ) ++ { ++ auto v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src += width; ++ v = *src; ++ *ptr8++ = (v & 0xff0000) >> 16; ++ src -= width * 3 - 1; ++ } ++#endif ++ if( ++w == width/4 ) ++ { ++ src += width * 3; ++ w = 0; ++ } ++ *dst++ = ProcessAlpha_ETC2( r ); ++ } ++ while( --blocks ); ++} ++ ++void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ) ++{ ++ int w = 0; ++ uint8_t rg[4*4*2]; ++ do ++ { ++#ifdef __SSE4_1__ ++ __m128 px0 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 0 ) ) ); ++ __m128 px1 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 1 ) ) ); ++ __m128 px2 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 2 ) ) ); ++ __m128 px3 = _mm_castsi128_ps( _mm_loadu_si128( (__m128i*)( src + width * 3 ) ) ); ++ ++ _MM_TRANSPOSE4_PS( px0, px1, px2, px3 ); ++ ++ __m128i c0 = _mm_castps_si128( px0 ); ++ __m128i c1 = _mm_castps_si128( px1 ); ++ __m128i c2 = _mm_castps_si128( px2 ); ++ __m128i c3 = _mm_castps_si128( px3 ); ++ ++ __m128i mask = _mm_setr_epi32( 0x0e0a0602, -1, -1, -1 ); ++ ++ __m128i r0 = _mm_shuffle_epi8( c0, mask ); ++ __m128i r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ __m128i r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ __m128i r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ __m128i s0 = _mm_or_si128( r0, r1 ); ++ __m128i s1 = _mm_or_si128( r2, r3 ); ++ __m128i s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)rg, s2 ); ++ ++ mask = _mm_setr_epi32( 0x0d090501, -1, -1, -1 ); ++ ++ r0 = _mm_shuffle_epi8( c0, mask ); ++ r1 = _mm_shuffle_epi8( c1, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 3, 0, 3 ) ) ); ++ r2 = _mm_shuffle_epi8( c2, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 3, 0, 3, 3 ) ) ); ++ r3 = _mm_shuffle_epi8( c3, _mm_shuffle_epi32( mask, _MM_SHUFFLE( 0, 3, 3, 3 ) ) ); ++ ++ s0 = _mm_or_si128( r0, r1 ); ++ s1 = _mm_or_si128( r2, r3 ); ++ s2 = _mm_or_si128( s0, s1 ); ++ ++ _mm_store_si128( (__m128i*)&rg[16], s2 ); ++ src += 4; ++#else ++ auto ptrr = rg; ++ auto ptrg = ptrr + 16; ++ for( int x=0; x<4; x++ ) ++ { ++ auto v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src += width; ++ v = *src; ++ *ptrr++ = (v & 0xff0000) >> 16; ++ *ptrg++ = (v & 0xff00) >> 8; ++ src -= width * 3 - 1; ++ } ++#endif ++ if( ++w == width/4 ) ++ { ++ src += width * 3; ++ w = 0; ++ } ++ *dst++ = ProcessAlpha_ETC2( rg ); ++ *dst++ = ProcessAlpha_ETC2( &rg[16] ); ++ } ++ while( --blocks ); ++} ++// -- GODOT end -- +diff --git a/thirdparty/etcpak/ProcessRGB.hpp b/thirdparty/etcpak/ProcessRGB.hpp +index 043b46e636..050ea42562 100644 +--- a/thirdparty/etcpak/ProcessRGB.hpp ++++ b/thirdparty/etcpak/ProcessRGB.hpp +@@ -9,5 +9,8 @@ void CompressEtc1Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_ + void CompressEtc1RgbDither( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); + void CompressEtc2Rgb( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); + void CompressEtc2Rgba( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width, bool useHeuristics ); +- ++// -- GODOT start -- ++void CompressEtc2R8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); ++void CompressEtc2RG8( const uint32_t* src, uint64_t* dst, uint32_t blocks, size_t width ); ++// -- GODOT end -- + #endif |