diff --git a/.editorconfig b/.editorconfig index 91784612cf..a2f93af3f5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,24 +3,18 @@ root = true [*] charset = utf-8 end_of_line = lf +indent_size = 4 indent_style = tab insert_final_newline = true - -[*.{cpp,hpp,c,h,mm}] +max_line_length = 120 trim_trailing_whitespace = true -[{*.gradle,AndroidManifest.xml}] -indent_style = space -indent_size = 4 - [{*.py,SConstruct,SCsub}] indent_style = space -indent_size = 4 -# YAML requires indentation with spaces instead of tabs. [{*.{yml,yaml},.clang{-format,-tidy,d}}] -indent_style = space indent_size = 2 +indent_style = space [*.svg] insert_final_newline = false diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index 025c4b2a94..20c65e128c 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -98,6 +98,7 @@ jobs: cd platform/android/java ./gradlew generateGodotEditor ./gradlew generateGodotHorizonOSEditor + ./gradlew generateGodotPicoOSEditor cd ../../.. ls -l bin/android_editor_builds/ diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 61fa04915a..d042e28b42 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -175,12 +175,12 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po } else { const int ms = waits[i]; OS::get_singleton()->delay_usec(ms * 1000); - print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec."); + print_verbose("Remote Debugger: Connection failed with status: '" + String::num_int64(tcp_client->get_status()) + "', retrying in " + String::num_int64(ms) + " msec."); } } if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num(tcp_client->get_status()))); + ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num_int64(tcp_client->get_status()))); return FAILED; } connected = true; diff --git a/core/extension/gdextension_special_compat_hashes.cpp b/core/extension/gdextension_special_compat_hashes.cpp index 59e7c9ea2b..389c8c3250 100644 --- a/core/extension/gdextension_special_compat_hashes.cpp +++ b/core/extension/gdextension_special_compat_hashes.cpp @@ -257,7 +257,8 @@ void GDExtensionSpecialCompatHashes::initialize() { #endif }); mappings.insert("DirAccess", { - { "list_dir_begin", 2018049411, 2610976713 }, + { "list_dir_begin", 2018049411, 166280745 }, + { "list_dir_begin", 2610976713, 166280745 }, { "copy", 198434953, 1063198817 }, { "copy_absolute", 198434953, 1063198817 }, }); @@ -279,7 +280,8 @@ void GDExtensionSpecialCompatHashes::initialize() { { "global_menu_add_multistate_item", 3431222859, 3297554655 }, { "global_menu_add_separator", 1041533178, 3214812433 }, { "tts_speak", 3741216677, 903992738 }, - { "is_touchscreen_available", 4162880507, 3323674545 }, + { "is_touchscreen_available", 4162880507, 36873697 }, + { "is_touchscreen_available", 3323674545, 36873697 }, { "screen_set_orientation", 2629526904, 2211511631 }, { "window_get_native_handle", 2709193271, 1096425680 }, { "window_set_title", 3043792800, 441246282 }, @@ -550,6 +552,9 @@ void GDExtensionSpecialCompatHashes::initialize() { { "tr", 2475554935, 1195764410 }, { "tr_n", 4021311862, 162698058 }, }); + mappings.insert("OpenXRAPIExtension", { + { "transform_from_pose", 3255299855, 2963875352 }, + }); mappings.insert("OptionButton", { { "add_item", 3043792800, 2697778442 }, { "add_icon_item", 3944051090, 3781678508 }, @@ -773,7 +778,7 @@ void GDExtensionSpecialCompatHashes::initialize() { { "push_paragraph", 3218895358, 3089306873 }, { "push_list", 4036303897, 3017143144 }, { "push_table", 1125058220, 2623499273 }, - { "set_table_column_expand", 4132157579, 2185176273 }, + { "set_table_column_expand", 4258957458, 2185176273 }, #ifdef REAL_T_IS_DOUBLE { "add_image", 3346058748, 1507062345 }, { "push_dropcap", 981432822, 763534173 }, @@ -914,6 +919,9 @@ void GDExtensionSpecialCompatHashes::initialize() { { "set_cells_terrain_path", 3072115677, 3578627656 }, { "get_used_cells_by_id", 4152068407, 2931012785 }, }); + mappings.insert("TileMapLayer", { + { "notify_runtime_tile_data_update", 2275361663, 3218959716 }, + }); mappings.insert("TileMapPattern", { { "set_cell", 634000503, 2224802556 }, }); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index e773ab538d..fd7c250283 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -633,7 +633,7 @@ void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); ClassDB::bind_static_method("DirAccess", D_METHOD("create_temp", "prefix", "keep"), &DirAccess::_create_temp, DEFVAL(""), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("list_dir_begin"), &DirAccess::list_dir_begin); ClassDB::bind_method(D_METHOD("get_next"), &DirAccess::_get_next); ClassDB::bind_method(D_METHOD("current_is_dir"), &DirAccess::current_is_dir); ClassDB::bind_method(D_METHOD("list_dir_end"), &DirAccess::list_dir_end); diff --git a/core/io/image.cpp b/core/io/image.cpp index fa7911cace..1624f19581 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -1137,7 +1137,7 @@ static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, } bool Image::is_size_po2() const { - return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height); + return is_power_of_2(width) && is_power_of_2(height); } void Image::resize_to_po2(bool p_square, Interpolation p_interpolation) { @@ -3955,6 +3955,97 @@ String Image::get_format_name(Format p_format) { return format_names[p_format]; } +uint32_t Image::get_format_component_mask(Format p_format) { + const uint32_t r = 1; + const uint32_t rg = 3; + const uint32_t rgb = 7; + const uint32_t rgba = 15; + + switch (p_format) { + case FORMAT_L8: + return rgb; + case FORMAT_LA8: + return rgba; + case FORMAT_R8: + return r; + case FORMAT_RG8: + return rg; + case FORMAT_RGB8: + return rgb; + case FORMAT_RGBA8: + return rgba; + case FORMAT_RGBA4444: + return rgba; + case FORMAT_RGB565: + return rgb; + case FORMAT_RF: + return r; + case FORMAT_RGF: + return rg; + case FORMAT_RGBF: + return rgb; + case FORMAT_RGBAF: + return rgba; + case FORMAT_RH: + return r; + case FORMAT_RGH: + return rg; + case FORMAT_RGBH: + return rgb; + case FORMAT_RGBAH: + return rgba; + case FORMAT_RGBE9995: + return rgba; + case FORMAT_DXT1: + return rgb; + case FORMAT_DXT3: + return rgb; + case FORMAT_DXT5: + return rgba; + case FORMAT_RGTC_R: + return r; + case FORMAT_RGTC_RG: + return rg; + case FORMAT_BPTC_RGBA: + return rgba; + case FORMAT_BPTC_RGBF: + return rgb; + case FORMAT_BPTC_RGBFU: + return rgb; + case FORMAT_ETC: + return rgb; + case FORMAT_ETC2_R11: + return r; + case FORMAT_ETC2_R11S: + return r; + case FORMAT_ETC2_RG11: + return rg; + case FORMAT_ETC2_RG11S: + return rg; + case FORMAT_ETC2_RGB8: + return rgb; + case FORMAT_ETC2_RGBA8: + return rgba; + case FORMAT_ETC2_RGB8A1: + return rgba; + case FORMAT_ETC2_RA_AS_RG: + return rgba; + case FORMAT_DXT5_RA_AS_RG: + return rgba; + case FORMAT_ASTC_4x4: + return rgba; + case FORMAT_ASTC_4x4_HDR: + return rgba; + case FORMAT_ASTC_8x8: + return rgba; + case FORMAT_ASTC_8x8_HDR: + return rgba; + default: + ERR_PRINT("Unhandled format."); + return rgba; + } +} + Error Image::load_png_from_buffer(const Vector &p_array) { return _load_from_buffer(p_array, _png_mem_loader_func); } diff --git a/core/io/image.h b/core/io/image.h index 3ccb135221..796ba35a48 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -397,6 +397,7 @@ public: Ref get_region(const Rect2i &p_area) const; static String get_format_name(Format p_format); + static uint32_t get_format_component_mask(Format p_format); Error load_png_from_buffer(const Vector &p_array); Error load_jpg_from_buffer(const Vector &p_array); diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 1059ac3865..95db2e8997 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -1961,6 +1961,11 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_ ERR_FAIL_V_MSG(nullptr, vformat("Method definition provides more arguments than the method actually has '%s::%s'.", instance_type, mdname)); } + if (p_defcount > p_bind->get_argument_count()) { + memdelete(p_bind); + ERR_FAIL_V_MSG(nullptr, vformat("Method definition for '%s::%s' provides more default arguments than the method has arguments.", instance_type, mdname)); + } + p_bind->set_argument_names(method_name.args); if (!p_compatibility) { diff --git a/core/object/method_bind.cpp b/core/object/method_bind.cpp index 4cbba21642..fcba882257 100644 --- a/core/object/method_bind.cpp +++ b/core/object/method_bind.cpp @@ -37,29 +37,15 @@ #include "method_bind.h" uint32_t MethodBind::get_hash() const { - uint32_t hash = hash_murmur3_one_32(has_return() ? 1 : 0); - hash = hash_murmur3_one_32(get_argument_count(), hash); - - for (int i = (has_return() ? -1 : 0); i < get_argument_count(); i++) { - PropertyInfo pi = i == -1 ? get_return_info() : get_argument_info(i); - hash = hash_murmur3_one_32(get_argument_type(i), hash); - if (pi.class_name != StringName()) { - hash = hash_murmur3_one_32(pi.class_name.operator String().hash(), hash); - } - } - - hash = hash_murmur3_one_32(get_default_argument_count(), hash); + MethodInfo mi; + mi.return_val = get_return_info(); + mi.flags = get_hint_flags(); for (int i = 0; i < get_argument_count(); i++) { - if (has_default_argument(i)) { - Variant v = get_default_argument(i); - hash = hash_murmur3_one_32(v.hash(), hash); - } + mi.arguments.push_back(get_argument_info(i)); } + mi.default_arguments = default_arguments; - hash = hash_murmur3_one_32(is_const(), hash); - hash = hash_murmur3_one_32(is_vararg(), hash); - - return hash_fmix32(hash); + return mi.get_compatibility_hash(); } PropertyInfo MethodBind::get_argument_info(int p_argument) const { diff --git a/core/object/object.cpp b/core/object/object.cpp index 4a1b562d5c..5cf56dc085 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -167,7 +167,6 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { return mi; } -// This was copied from MethodBind::get_hash() so that the compatibility hashes for virtual and non-virtual methods would be the same. uint32_t MethodInfo::get_compatibility_hash() const { bool has_return = (return_val.type != Variant::NIL) || (return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); diff --git a/core/os/memory.cpp b/core/os/memory.cpp index 5028c67cd9..1ad552221a 100644 --- a/core/os/memory.cpp +++ b/core/os/memory.cpp @@ -66,10 +66,6 @@ SafeNumeric Memory::max_usage; SafeNumeric Memory::alloc_count; -inline bool is_power_of_2(size_t x) { - return x && ((x & (x - 1U)) == 0U); -} - void *Memory::alloc_aligned_static(size_t p_bytes, size_t p_alignment) { DEV_ASSERT(is_power_of_2(p_alignment)); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 3c8d3dbfe3..32ec329f08 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -552,18 +552,7 @@ bool String::operator==(const char32_t *p_str) const { return true; } - int l = length(); - - const char32_t *dst = get_data(); - - /* Compare char by char */ - for (int i = 0; i < l; i++) { - if (p_str[i] != dst[i]) { - return false; - } - } - - return true; + return memcmp(ptr(), p_str, len * sizeof(char32_t)) == 0; } bool String::operator==(const String &p_str) const { @@ -574,23 +563,11 @@ bool String::operator==(const String &p_str) const { return true; } - int l = length(); - - const char32_t *src = get_data(); - const char32_t *dst = p_str.get_data(); - - /* Compare char by char */ - for (int i = 0; i < l; i++) { - if (src[i] != dst[i]) { - return false; - } - } - - return true; + return memcmp(ptr(), p_str.ptr(), length() * sizeof(char32_t)) == 0; } bool String::operator==(const StrRange &p_str_range) const { - int len = p_str_range.len; + const int len = p_str_range.len; if (length() != len) { return false; @@ -599,17 +576,7 @@ bool String::operator==(const StrRange &p_str_range) const { return true; } - const char32_t *c_str = p_str_range.c_str; - const char32_t *dst = &operator[](0); - - /* Compare char by char */ - for (int i = 0; i < len; i++) { - if (c_str[i] != dst[i]) { - return false; - } - } - - return true; + return memcmp(ptr(), p_str_range.c_str, len * sizeof(char32_t)) == 0; } bool operator==(const char *p_chr, const String &p_str) { @@ -3575,25 +3542,15 @@ int String::rfindn(const char *p_str, int p_from) const { } bool String::ends_with(const String &p_string) const { - int l = p_string.length(); + const int l = p_string.length(); if (l > length()) { return false; } - if (l == 0) { return true; } - const char32_t *p = &p_string[0]; - const char32_t *s = &operator[](length() - l); - - for (int i = 0; i < l; i++) { - if (p[i] != s[i]) { - return false; - } - } - - return true; + return memcmp(ptr() + (length() - l), p_string.ptr(), l * sizeof(char32_t)) == 0; } bool String::ends_with(const char *p_string) const { @@ -3622,25 +3579,15 @@ bool String::ends_with(const char *p_string) const { } bool String::begins_with(const String &p_string) const { - int l = p_string.length(); + const int l = p_string.length(); if (l > length()) { return false; } - if (l == 0) { return true; } - const char32_t *p = &p_string[0]; - const char32_t *s = &operator[](0); - - for (int i = 0; i < l; i++) { - if (p[i] != s[i]) { - return false; - } - } - - return true; + return memcmp(ptr(), p_string.ptr(), l * sizeof(char32_t)) == 0; } bool String::begins_with(const char *p_string) const { diff --git a/core/typedefs.h b/core/typedefs.h index 31e99db20f..611978ec24 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -137,6 +137,12 @@ constexpr auto CLAMP(const T m_a, const T2 m_min, const T3 m_max) { /* Functions to handle powers of 2 and shifting. */ +// Returns `true` if a positive integer is a power of 2, `false` otherwise. +template +inline bool is_power_of_2(const T x) { + return x && ((x & (x - 1)) == 0); +} + // Function to find the next power of 2 to an integer. static _FORCE_INLINE_ unsigned int next_power_of_2(unsigned int x) { if (x == 0) { diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h index 1261afd9c8..fa80ff1143 100644 --- a/core/variant/native_ptr.h +++ b/core/variant/native_ptr.h @@ -123,7 +123,7 @@ struct GDExtensionPtr { template struct GetTypeInfo> { - static const Variant::Type VARIANT_TYPE = Variant::NIL; + static const Variant::Type VARIANT_TYPE = Variant::INT; static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; static inline PropertyInfo get_class_info() { return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDExtensionConstPtr::get_name()); @@ -132,7 +132,7 @@ struct GetTypeInfo> { template struct GetTypeInfo> { - static const Variant::Type VARIANT_TYPE = Variant::NIL; + static const Variant::Type VARIANT_TYPE = Variant::INT; static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; static inline PropertyInfo get_class_info() { return PropertyInfo(Variant::INT, String(), PROPERTY_HINT_INT_IS_POINTER, GDExtensionPtr::get_name()); diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index be469080ed..e52e727400 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -46,10 +46,20 @@ Returns the enabled state of the given particle flag (see [enum ParticleFlags] for options). + + + + + Requests the particles to process for extra process time during a single frame. + Useful for particle playback, if used in combination with [member use_fixed_seed] or by calling [method restart] with parameter [code]keep_seed[/code] set to [code]true[/code]. + + + Restarts the particle emitter. + If [param keep_seed] is [code]true[/code], the current random seed will be preserved. Useful for seeking and playback. @@ -263,6 +273,9 @@ Each particle's vertical scale will vary along this [Curve]. Should be a unit [Curve]. [member split_scale] must be enabled. + + Sets the random seed used by the particle system. Only effective if [member use_fixed_seed] is [code]true[/code]. + Particle system's running speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. @@ -284,6 +297,9 @@ Particle texture. If [code]null[/code], particles will be squares. + + If [code]true[/code], particles will use the same seed for every simulation using the seed defined in [member seed]. This is useful for situations where the visual outcome should be consistent across replays, for example when using Movie Maker mode. + diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 805299556a..cf80fecca6 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -52,10 +52,20 @@ Returns the enabled state of the given particle flag (see [enum ParticleFlags] for options). + + + + + Requests the particles to process for extra process time during a single frame. + Useful for particle playback, if used in combination with [member use_fixed_seed] or by calling [method restart] with parameter [code]keep_seed[/code] set to [code]true[/code]. + + + Restarts the particle emitter. + If [param keep_seed] is [code]true[/code], the current random seed will be preserved. Useful for seeking and playback. @@ -301,6 +311,9 @@ Curve for the scale over life, along the z axis. + + Sets the random seed used by the particle system. Only effective if [member use_fixed_seed] is [code]true[/code]. + Particle system's running speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. @@ -319,6 +332,9 @@ Minimum tangent acceleration. + + If [code]true[/code], particles will use the same seed for every simulation using the seed defined in [member seed]. This is useful for situations where the visual outcome should be consistent across replays, for example when using Movie Maker mode. + The [AABB] that determines the node's region which needs to be visible on screen for the particle system to be active. Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/b] editor tool. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 2971a60057..2a70f7041e 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -123,7 +123,7 @@ Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS and Windows. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS, Windows, and Android. @@ -1937,7 +1937,7 @@ The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b] - The display server supports initiating window drag operation on demand. See [method window_start_drag]. + The display server supports initiating window drag and resize operations on demand. See [method window_start_drag] and [method window_start_resize]. Display server supports [constant WINDOW_FLAG_EXCLUDE_FROM_CAPTURE] window flag. diff --git a/doc/classes/EditorSceneFormatImporter.xml b/doc/classes/EditorSceneFormatImporter.xml index b14810133e..3440fdc140 100644 --- a/doc/classes/EditorSceneFormatImporter.xml +++ b/doc/classes/EditorSceneFormatImporter.xml @@ -13,17 +13,16 @@ - - - - - + Return supported file extensions for this scene importer. + Override to add general import options. These will appear in the main import dock on the editor. Add options via [method add_import_option] and [method add_import_option_advanced]. + [b]Note:[/b] All [EditorSceneFormatImporter] and [EditorScenePostImportPlugin] instances will add options for all files. It is good practice to check the file extension when [param path] is non-empty. + When the user is editing project settings, [param path] will be empty. It is recommended to add all options when [param path] is empty to allow the user to customize Import Defaults. @@ -32,6 +31,7 @@ + Should return [code]true[/code] to show the given option, [code]false[/code] to hide the given option, or [code]null[/code] to ignore. @@ -40,6 +40,27 @@ + Perform the bulk of the scene import logic here, for example using [GLTFDocument] or [FBXDocument]. + + + + + + + + Add a specific import option (name and default value only). This function can only be called from [method _get_import_options]. + + + + + + + + + + + + Add a specific import option. This function can only be called from [method _get_import_options]. diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 056dfc6d32..e7048a06ba 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -480,6 +480,15 @@ The 3D editor gizmo color used for [Skeleton3D] nodes. + + The 3D editor gizmo color used for [SpringBoneCollision3D] nodes. + + + The 3D editor gizmo color used for [SpringBoneCollision3D] nodes with inside mode. + + + The 3D editor gizmo color used for [SpringBoneSimulator3D] nodes. + The 3D editor gizmo color used for [AudioStreamPlayer3D]'s emission angle. diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index fa7935f78a..a075d49f51 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -41,11 +41,21 @@ [b]Note:[/b] [method emit_particle] is only supported on the Forward+ and Mobile rendering methods, not Compatibility. + + + + + Requests the particles to process for extra process time during a single frame. + Useful for particle playback, if used in combination with [member use_fixed_seed] or by calling [method restart] with parameter [code]keep_seed[/code] set to [code]true[/code]. + + + Restarts the particle emission cycle, clearing existing particles. To avoid particles vanishing from the viewport, wait for the [signal finished] signal before calling. [b]Note:[/b] The [signal finished] signal is only emitted by [member one_shot] emitters. + If [param keep_seed] is [code]true[/code], the current random seed will be preserved. Useful for seeking and playback. @@ -105,6 +115,9 @@ Emission lifetime randomness ratio. + + Sets the random seed used by the particle system. Only effective if [member use_fixed_seed] is [code]true[/code]. + Particle system's running speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. @@ -129,6 +142,9 @@ The number of sections to use for the particle trail rendering. Higher values can result in smoother trail curves, at the cost of performance due to increased mesh complexity. See also [member trail_section_subdivisions]. Only effective if [member trail_enabled] is [code]true[/code]. + + If [code]true[/code], particles will use the same seed for every simulation using the seed defined in [member seed]. This is useful for situations where the visual outcome should be consistent across replays, for example when using Movie Maker mode. + The [Rect2] that determines the node's region which needs to be visible on screen for the particle system to be active. Grow the rect if particles suddenly appear/disappear when the node enters/exits the screen. The [Rect2] can be grown via code or with the [b]Particles → Generate Visibility Rect[/b] editor tool. diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 9cd0ea88ec..ddd4fc831e 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -46,11 +46,21 @@ Returns the [Mesh] that is drawn at index [param pass]. + + + + + Requests the particles to process for extra process time during a single frame. + Useful for particle playback, if used in combination with [member use_fixed_seed] or by calling [method restart] with parameter [code]keep_seed[/code] set to [code]true[/code]. + + + Restarts the particle emission cycle, clearing existing particles. To avoid particles vanishing from the viewport, wait for the [signal finished] signal before calling. [b]Note:[/b] The [signal finished] signal is only emitted by [member one_shot] emitters. + If [param keep_seed] is [code]true[/code], the current random seed will be preserved. Useful for seeking and playback. @@ -136,6 +146,9 @@ Emission randomness ratio. + + Sets the random seed used by the particle system. Only effective if [member use_fixed_seed] is [code]true[/code]. + Speed scaling ratio. A value of [code]0[/code] can be used to pause the particles. @@ -153,6 +166,9 @@ + + If [code]true[/code], particles will use the same seed for every simulation using the seed defined in [member seed]. This is useful for situations where the visual outcome should be consistent across replays, for example when using Movie Maker mode. + The [AABB] that determines the node's region which needs to be visible on screen for the particle system to be active. [member GeometryInstance3D.extra_cull_margin] is added on each of the AABB's axes. Particle collisions and attraction will only occur within this area. Grow the box if particles suddenly appear/disappear when the node enters/exits the screen. The [AABB] can be grown via code or with the [b]Particles → Generate AABB[/b] editor tool. diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml index 31ce1dba4a..731245fb48 100644 --- a/doc/classes/NavigationRegion2D.xml +++ b/doc/classes/NavigationRegion2D.xml @@ -23,6 +23,12 @@ Bakes the [NavigationPolygon]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread. + + + + Returns the axis-aligned rectangle for the region's transformed navigation mesh. + + diff --git a/doc/classes/NavigationRegion3D.xml b/doc/classes/NavigationRegion3D.xml index ad31fd0632..019afce30b 100644 --- a/doc/classes/NavigationRegion3D.xml +++ b/doc/classes/NavigationRegion3D.xml @@ -23,6 +23,12 @@ Bakes the [NavigationMesh]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread. Baking on separate thread is useful because navigation baking is not a cheap operation. When it is completed, it automatically sets the new [NavigationMesh]. Please note that baking on separate thread may be very slow if geometry is parsed from meshes as async access to each mesh involves heavy synchronization. Also, please note that baking on a separate thread is automatically disabled on operating systems that cannot use threads (such as Web with threads disabled). + + + + Returns the axis-aligned bounding box for the region's transformed navigation mesh. + + diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index 41ef298bc3..95db5591d0 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -784,6 +784,13 @@ Creates a new region. + + + + + Returns the axis-aligned rectangle for the [param region]'s transformed navigation mesh. + + diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 6f0c68740a..64da1cdade 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -925,6 +925,13 @@ Creates a new region. + + + + + Returns the axis-aligned bounding box for the [param region]'s transformed navigation mesh. + + diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index e1c97a74ba..8336899e86 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -346,6 +346,10 @@ The amount of particles to spawn from the subemitter node when the particle expires. [b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired. + + The amount of particles to spawn from the subemitter node when the particle spawns. + [b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired. + The frequency at which particles should be emitted from the subemitter node. One particle will be spawned every [member sub_emitter_frequency] seconds. [b]Note:[/b] This value shouldn't exceed [member GPUParticles2D.amount] or [member GPUParticles3D.amount] defined on the [i]subemitter node[/i] (not the main node), relative to the subemitter's particle lifetime. If the number of particles is exceeded, no new particles will spawn from the subemitter until enough particles have expired. @@ -521,7 +525,9 @@ - + + + Represents the size of the [enum SubEmitterMode] enum. diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 4b370871b3..2caa23e0b3 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -81,6 +81,14 @@ [/codeblock] + + + + + Returns the address of the given [param buffer] which can be passed to shaders in any way to access underlying data. Buffer must have been created with this feature enabled. + [b]Note:[/b] You must check that the GPU supports this functionality by calling [method has_feature] with [constant SUPPORTS_BUFFER_DEVICE_ADDRESS] as a parameter. + + @@ -672,6 +680,13 @@ This is only used by Vulkan in debug builds. Redot must also be started with the [code]--extra-gpu-memory-tracking[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. + + + + + Returns [code]true[/code] if the [param feature] is supported by the GPU. + + @@ -688,9 +703,11 @@ + Creates a new index buffer. It can be accessed with the RID that is returned. Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method. + Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it. @@ -1056,9 +1073,11 @@ + Creates a new uniform buffer. It can be accessed with the RID that is returned. Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method. + Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it. @@ -1093,9 +1112,11 @@ + It can be accessed with the RID that is returned. Once finished with your RID, you will want to free the RID using the RenderingDevice's [method free_rid] method. + Optionally, set [param enable_device_address] if you wish to use [method buffer_get_device_address] functionality and the GPU supports it. @@ -2047,6 +2068,9 @@ + + Allows usage of [method buffer_get_device_address] on supported GPUs. + Sampler uniform. @@ -2418,6 +2442,9 @@ Floating-point specialization constant. + + Features support for buffer device address extension. + Maximum number of uniform sets that can be bound at a given time. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index ad3746b050..8dd90799ba 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -2559,6 +2559,7 @@ + @@ -2593,6 +2594,29 @@ Returns the [RenderingDevice] [RID] handle of the [MultiMesh], which can be used as any other buffer on the Rendering Device. + + + + + Returns the [RenderingDevice] [RID] handle of the [MultiMesh] command buffer. This [RID] is only valid if [code]use_indirect[/code] is set to [code]true[/code] when allocating data through [method multimesh_allocate_data]. It can be used to directly modify the instance count via buffer. + The data structure is dependent on both how many surfaces the mesh contains and whether it is indexed or not, the buffer has 5 integers in it, with the last unused if the mesh is not indexed. + Each of the values in the buffer correspond to these options: + [codeblock lang=text] + Indexed: + 0 - indexCount; + 1 - instanceCount; + 2 - firstIndex; + 3 - vertexOffset; + 4 - firstInstance; + Non Indexed: + 0 - vertexCount; + 1 - instanceCount; + 2 - firstVertex; + 3 - firstInstance; + 4 - unused; + [/codeblock] + + @@ -2935,6 +2959,14 @@ Add particle system to list of particle systems that need to be updated. Update will take place on the next frame, or on the next call to [method instances_cull_aabb], [method instances_cull_convex], or [method instances_cull_ray]. + + + + + + Requests particles to process for extra process time during a single frame. + + diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 544f76d1c7..2662cb072b 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -597,6 +597,7 @@ + Edits the selected column's expansion options. If [param expand] is [code]true[/code], the column expands in proportion to its expansion ratio versus the other columns' ratios. For example, 2 columns with ratios 3 and 4 plus 70 pixels in available width would expand 30 and 40 pixels, respectively. diff --git a/doc/classes/SpringBoneCollision3D.xml b/doc/classes/SpringBoneCollision3D.xml new file mode 100644 index 0000000000..ea4556674c --- /dev/null +++ b/doc/classes/SpringBoneCollision3D.xml @@ -0,0 +1,35 @@ + + + + A base class of the collision that interacts with [SpringBoneSimulator3D]. + + + A collision can be a child of [SpringBoneSimulator3D]. If it is not a child of [SpringBoneSimulator3D], it has no effect. + The colliding and sliding are done in the [SpringBoneSimulator3D]'s modification process in order of its collision list which is set by [method SpringBoneSimulator3D.set_collision_path]. If [method SpringBoneSimulator3D.are_all_child_collisions_enabled] is [code]true[/code], the order matches [SceneTree]. + If [member bone] is set, it synchronizes with the bone pose of the ancestor [Skeleton3D], which is done in before the [SpringBoneSimulator3D]'s modification process as the pre-process. + + + + + + + + Get parent [Skeleton3D] node of the parent [SpringBoneSimulator3D] if found. + + + + + + The index of the attached bone. + + + The name of the attached bone. + + + The offset of the position from [Skeleton3D]'s [member bone] pose position. + + + The offset of the rotation from [Skeleton3D]'s [member bone] pose rotation. + + + diff --git a/doc/classes/SpringBoneCollisionCapsule3D.xml b/doc/classes/SpringBoneCollisionCapsule3D.xml new file mode 100644 index 0000000000..9af9aca331 --- /dev/null +++ b/doc/classes/SpringBoneCollisionCapsule3D.xml @@ -0,0 +1,22 @@ + + + + A capsule shape collision that interacts with [SpringBoneSimulator3D]. + + + A capsule shape collision that interacts with [SpringBoneSimulator3D]. + + + + + + The capsule's height. + + + If [code]true[/code], the collision acts to trap the joint within the collision. + + + The capsule's radius. + + + diff --git a/doc/classes/SpringBoneCollisionPlane3D.xml b/doc/classes/SpringBoneCollisionPlane3D.xml new file mode 100644 index 0000000000..d9f7199f5d --- /dev/null +++ b/doc/classes/SpringBoneCollisionPlane3D.xml @@ -0,0 +1,11 @@ + + + + A infinite plane collision that interacts with [SpringBoneSimulator3D]. + + + A infinite plane collision that interacts with [SpringBoneSimulator3D]. It is an infinite size XZ plane, and the +Y direction is treated as normal. + + + + diff --git a/doc/classes/SpringBoneCollisionSphere3D.xml b/doc/classes/SpringBoneCollisionSphere3D.xml new file mode 100644 index 0000000000..3ccb42a93e --- /dev/null +++ b/doc/classes/SpringBoneCollisionSphere3D.xml @@ -0,0 +1,19 @@ + + + + A sphere shape collision that interacts with [SpringBoneSimulator3D]. + + + A sphere shape collision that interacts with [SpringBoneSimulator3D]. + + + + + + If [code]true[/code], the collision acts to trap the joint within the collision. + + + The sphere's radius. + + + diff --git a/doc/classes/SpringBoneSimulator3D.xml b/doc/classes/SpringBoneSimulator3D.xml new file mode 100644 index 0000000000..95c357f60b --- /dev/null +++ b/doc/classes/SpringBoneSimulator3D.xml @@ -0,0 +1,645 @@ + + + + A [SkeletonModifier3D] to apply inertial wavering to bone chains. + + + This [SkeletonModifier3D] can be used to wiggle hair, cloth, and tails. This modifier behaves differently from [PhysicalBoneSimulator3D] as it attempts to return the original pose after modification. + If you setup [method set_root_bone] and [method set_end_bone], it is treated as one bone chain. Note that it does not support a branched chain like Y-shaped chains. + When a bone chain is created, an array is generated from the bones that exist in between and listed in the joint list. + Several properties can be applied to each joint, such as [method set_joint_stiffness], [method set_joint_drag], and [method set_joint_gravity]. + For simplicity, you can set values to all joints at the same time by using a [Curve]. If you want to specify detailed values individually, set [method set_individual_config] to [code]true[/code]. + For physical simulation, [SpringBoneSimulator3D] can have children as self-standing collisions that are not related to [PhysicsServer3D], see also [SpringBoneCollision3D]. + + + + + + + + + Returns [code]true[/code] if the all child [SpringBoneCollision3D]s are contained in the collision list at [param index] in the settings. + + + + + + + Clears all collisions from the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + Clears all exclude collisions from the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + Clears all settings. + + + + + + + Returns the center bone index of the bone chain. + + + + + + + Returns the center bone name of the bone chain. + + + + + + + Returns what the center originates from in the bone chain. + + + + + + + Returns the center node path of the bone chain. + + + + + + + Returns the collision count of the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + Returns the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + Returns the drag force damping curve of the bone chain. + + + + + + + Returns the drag force damping curve of the bone chain. + + + + + + + Returns the end bone index of the bone chain. + + + + + + + Returns the end bone's tail direction of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + Returns the end bone's tail length of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + Returns the end bone name of the bone chain. + + + + + + + Returns the exclude collision count of the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + Returns the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + Returns the gravity amount of the bone chain. + + + + + + + Returns the gravity amount damping curve of the bone chain. + + + + + + + Returns the gravity direction of the bone chain. + + + + + + + + Returns the bone index at [param joint] in the bone chain's joint list. + + + + + + + + Returns the bone name at [param joint] in the bone chain's joint list. + + + + + + + Returns the joint count of the bone chain's joint list. + + + + + + + + Returns the drag force at [param joint] in the bone chain's joint list. + + + + + + + + Returns the gravity amount at [param joint] in the bone chain's joint list. + + + + + + + + Returns the gravity direction at [param joint] in the bone chain's joint list. + + + + + + + + Returns the radius at [param joint] in the bone chain's joint list. + + + + + + + + Returns the rotation axis at [param joint] in the bone chain's joint list. + + + + + + + + Returns the stiffness force at [param joint] in the bone chain's joint list. + + + + + + + Returns the joint radius of the bone chain. + + + + + + + Returns the joint radius damping curve of the bone chain. + + + + + + + Returns the root bone index of the bone chain. + + + + + + + Returns the root bone name of the bone chain. + + + + + + + Returns the rotation axis of the bone chain. + + + + + + + Returns the stiffness force of the bone chain. + + + + + + + Returns the stiffness force damping curve of the bone chain. + + + + + + + Returns [code]true[/code] if the config can be edited individually for each joint. + + + + + + + Returns [code]true[/code] if the end bone is extended to have the tail. + + + + + + Resets a simulating state with respect to the current bone pose. + It is useful to prevent the simulation result getting violent. For example, calling this immediately after a call to [method AnimationPlayer.play] without a fading, or within the previous [signal SkeletonModifier3D.modification_processed] signal if it's condition changes significantly. + + + + + + + + Sets the center bone index of the bone chain. + + + + + + + + Sets the center bone name of the bone chain. + + + + + + + + Sets what the center originates from in the bone chain. + Bone movement is calculated based on the difference in relative distance between center and bone in the previous and next frames. + For example, if the parent [Skeleton3D] is used as the center, the bones are considered to have not moved if the [Skeleton3D] moves in the world. + In this case, only a change in the bone pose is considered to be a bone movement. + + + + + + + + Sets the center node path of the bone chain. + + + + + + + + Sets the number of collisions in the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + + Sets the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + Sets the drag force of the bone chain. The greater the value, the more suppressed the wiggling. + The value is scaled by [method set_drag_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the drag force damping curve of the bone chain. + + + + + + + + If sets [param enabled] to [code]true[/code], the all child [SpringBoneCollision3D]s are collided and [method set_exclude_collision_path] is enabled as an exclusion list at [param index] in the settings. + If sets [param enabled] to [code]false[/code], you need to manually register all valid collisions with [method set_collision_path]. + + + + + + + + Sets the end bone index of the bone chain. + + + + + + + + Sets the end bone tail direction of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + + Sets the end bone tail length of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + + Sets the end bone name of the bone chain. + [b]Note:[/b] End bone must be the root bone or a child of the root bone. If they are the same, the tail must be extended by [method set_extend_end_bone] to jiggle the bone. + + + + + + + + Sets the number of exclude collisions in the exclude collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + + Sets the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + If [param enabled] is [code]true[/code], the end bone is extended to have the tail. + The extended tail config is allocated to the last element in the joint list. + In other words, if you set [param enabled] is [code]false[/code], the config of last element in the joint list has no effect in the simulated result. + + + + + + + + Sets the gravity amount of the bone chain. + If [param gravity] is not [code]0[/code], the modified pose will not return to the original pose since it is always affected by gravity. + The value is scaled by [method set_gravity_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the gravity amount damping curve of the bone chain. + + + + + + + + Sets the gravity direction of the bone chain. + The value is cached in each joint setting in the joint list. + + + + + + + + If [param enabled] is [code]true[/code], the config can be edited individually for each joint. + + + + + + + + + Sets the drag force at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the gravity amount at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the gravity direction at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the joint radius at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the rotation axis at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the stiffness force at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + Sets the joint radius of the bone chain. It is used to move and slide with the [SpringBoneCollision3D] in the collision list. + The value is scaled by [method set_radius_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the joint radius damping curve of the bone chain. + + + + + + + + Sets the root bone index of the bone chain. + + + + + + + + Sets the root bone name of the bone chain. + + + + + + + + Sets the rotation axis of the bone chain. If sets a specific axis, it acts like a hinge joint. + The value is cached in each joint setting in the joint list. + + + + + + + + Sets the stiffness force of the bone chain. The greater the value, the faster it recovers to its initial pose. + If [param stiffness] is [code]0[/code], the modified pose will not return to the original pose. + The value is scaled by [method set_stiffness_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the stiffness force damping curve of the bone chain. + + + + + + The number of settings. + + + + + Enumerated value for the +X axis. + + + Enumerated value for the -X axis. + + + Enumerated value for the +Y axis. + + + Enumerated value for the -Y axis. + + + Enumerated value for the +Z axis. + + + Enumerated value for the -Z axis. + + + Enumerated value for the axis from a parent bone to the child bone. + + + The world origin is defined as center. + + + The [Node3D] specified by [method set_center_node] is defined as center. + If [Node3D] is not found, the parent [Skeleton3D] is treated as center. + + + The bone pose origin of the parent [Skeleton3D] specified by [method set_center_bone] is defined as center. + If [Node3D] is not found, the parent [Skeleton3D] is treated as center. + + + Enumerated value for the rotation of the X axis. + + + Enumerated value for the rotation of the Y axis. + + + Enumerated value for the rotation of the Z axis. + + + Enumerated value for the unconstrained rotation. + + + diff --git a/doc/classes/TextureProgressBar.xml b/doc/classes/TextureProgressBar.xml index 12e516a97b..aeefbc02f8 100644 --- a/doc/classes/TextureProgressBar.xml +++ b/doc/classes/TextureProgressBar.xml @@ -31,7 +31,7 @@ - If [code]true[/code], Redot treats the bar's textures like in [NinePatchRect]. Use the [code]stretch_margin_*[/code] properties like [member stretch_margin_bottom] to set up the nine patch's 3×3 grid. When using a radial [member fill_mode], this setting will enable stretching. + If [code]true[/code], Redot treats the bar's textures like in [NinePatchRect]. Use the [code]stretch_margin_*[/code] properties like [member stretch_margin_bottom] to set up the nine patch's 3×3 grid. When using a radial [member fill_mode], this setting will only enable stretching for [member texture_progress], while [member texture_under] and [member texture_over] will be treated like in [NinePatchRect]. Offsets [member texture_progress] if [member fill_mode] is [constant FILL_CLOCKWISE], [constant FILL_COUNTER_CLOCKWISE], or [constant FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE]. diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index f9505124ef..e8a74f0be0 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -64,7 +64,7 @@ - Returns the custom data value for custom data layer named [param layer_name]. + Returns the custom data value for custom data layer named [param layer_name]. To check if a custom data layer exists, use [method has_custom_data]. @@ -122,6 +122,13 @@ Returns the tile's terrain bit for the given [param peering_bit] direction. To check that a direction is valid, use [method is_valid_terrain_peering_bit]. + + + + + Returns whether there exists a custom data layer named [param layer_name]. + + diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml index 19b6e8d92c..a24f920daf 100644 --- a/doc/classes/TileSet.xml +++ b/doc/classes/TileSet.xml @@ -319,6 +319,13 @@ Returns if there is a coodinates-level proxy for the given identifiers. + + + + + Returns if there is a custom data layer named [param layer_name]. + + diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index 0f1693279c..50310b6967 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -907,6 +907,11 @@ void RenderingDeviceDriverD3D12::buffer_unmap(BufferID p_buffer) { buf_info->resource->Unmap(0, &VOID_RANGE); } +uint64_t RenderingDeviceDriverD3D12::buffer_get_device_address(BufferID p_buffer) { + const BufferInfo *buf_info = (const BufferInfo *)p_buffer.id; + return buf_info->resource->GetGPUVirtualAddress(); +} + /*****************/ /**** TEXTURE ****/ /*****************/ @@ -6273,6 +6278,8 @@ bool RenderingDeviceDriverD3D12::has_feature(Features p_feature) { return vrs_capabilities.ss_image_supported; case SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS: return true; + case SUPPORTS_BUFFER_DEVICE_ADDRESS: + return true; default: return false; } diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h index ea34a0a38e..0c978467b2 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.h +++ b/drivers/d3d12/rendering_device_driver_d3d12.h @@ -286,6 +286,7 @@ public: virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) override final; virtual uint8_t *buffer_map(BufferID p_buffer) override final; virtual void buffer_unmap(BufferID p_buffer) override final; + virtual uint64_t buffer_get_device_address(BufferID p_buffer) override final; /*****************/ /**** TEXTURE ****/ diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index f2275431a7..8552bc1521 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -472,9 +472,9 @@ void RasterizerGLES3::set_boot_image(const Ref &p_image, const Color &p_c glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); glViewport(0, 0, win_size.width, win_size.height); glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); glDepthMask(GL_FALSE); - glClearColor(p_color.r, p_color.g, p_color.b, 1.0); + glClearColor(p_color.r, p_color.g, p_color.b, OS::get_singleton()->is_layered_allowed() ? p_color.a : 1.0); glClear(GL_COLOR_BUFFER_BIT); RID texture = texture_storage->texture_allocate(); diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index f13c83dab8..c5a7876fac 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -1520,7 +1520,7 @@ void MeshStorage::_multimesh_free(RID p_rid) { multimesh_owner.free(p_rid); } -void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) { +void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL(multimesh); @@ -2043,6 +2043,10 @@ void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector &p_ } } +RID MeshStorage::_multimesh_get_command_buffer_rd_rid(RID p_multimesh) const { + ERR_FAIL_V_MSG(RID(), "GLES3 does not implement indirect multimeshes."); +} + RID MeshStorage::_multimesh_get_buffer_rd_rid(RID p_multimesh) const { ERR_FAIL_V_MSG(RID(), "GLES3 does not contain a Rid for the multimesh buffer."); } diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index c828de6132..5da43624dc 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -504,7 +504,7 @@ public: virtual RID _multimesh_allocate() override; virtual void _multimesh_initialize(RID p_rid) override; virtual void _multimesh_free(RID p_rid) override; - virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override; + virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override; virtual int _multimesh_get_instance_count(RID p_multimesh) const override; virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override; @@ -523,6 +523,7 @@ public: virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override; virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override; virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override; + virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override; virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override; virtual Vector _multimesh_get_buffer(RID p_multimesh) const override; diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp index b37d47c29f..e3dea7ff7c 100644 --- a/drivers/gles3/storage/particles_storage.cpp +++ b/drivers/gles3/storage/particles_storage.cpp @@ -225,6 +225,19 @@ void ParticlesStorage::particles_set_pre_process_time(RID p_particles, double p_ ERR_FAIL_NULL(particles); particles->pre_process_time = p_time; } + +void ParticlesStorage::particles_request_process_time(RID p_particles, real_t p_request_process_time) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_NULL(particles); + particles->request_process_time = p_request_process_time; +} + +void ParticlesStorage::particles_set_seed(RID p_particles, uint32_t p_seed) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_NULL(particles); + particles->random_seed = p_seed; +} + void ParticlesStorage::particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) { Particles *particles = particles_owner.get_or_null(p_particles); ERR_FAIL_NULL(particles); @@ -509,7 +522,6 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta if (p_particles->clear) { p_particles->cycle_number = 0; - p_particles->random_seed = Math::rand(); } else if (new_phase < p_particles->phase) { if (p_particles->one_shot) { p_particles->emitting = false; @@ -1138,6 +1150,24 @@ void ParticlesStorage::update_particles() { } } + if (particles->request_process_time > 0.0) { + double frame_time; + if (fixed_fps > 0) { + frame_time = 1.0 / fixed_fps; + } else { + frame_time = 1.0 / 30.0; + } + float tmp_scale = particles->speed_scale; + particles->speed_scale = 1.0; + double todo = particles->request_process_time; + while (todo >= 0) { + _particles_process(particles, frame_time); + todo -= frame_time; + } + particles->speed_scale = tmp_scale; + particles->request_process_time = 0.0; + } + // Copy particles to instance buffer and pack Color/Custom. // We don't have camera information here, so don't copy here if we need camera information for view depth or align mode. if (particles->draw_order != RS::PARTICLES_DRAW_ORDER_VIEW_DEPTH && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD && particles->transform_align != RS::PARTICLES_TRANSFORM_ALIGN_Z_BILLBOARD_Y_TO_VELOCITY) { diff --git a/drivers/gles3/storage/particles_storage.h b/drivers/gles3/storage/particles_storage.h index 3b0d4c04f0..85c96bbcdb 100644 --- a/drivers/gles3/storage/particles_storage.h +++ b/drivers/gles3/storage/particles_storage.h @@ -159,6 +159,7 @@ private: int amount = 0; double lifetime = 1.0; double pre_process_time = 0.0; + real_t request_process_time = 0.0; real_t explosiveness = 0.0; real_t randomness = 0.0; bool restart_request = false; @@ -248,6 +249,7 @@ private: Particles() : update_list(this) { + random_seed = Math::rand(); } }; @@ -327,6 +329,7 @@ public: virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override; virtual void particles_set_pre_process_time(RID p_particles, double p_time) override; + virtual void particles_request_process_time(RID p_particles, real_t p_request_process_time) override; virtual void particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) override; virtual void particles_set_randomness_ratio(RID p_particles, real_t p_ratio) override; virtual void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) override; @@ -342,6 +345,7 @@ public: virtual void particles_set_collision_base_size(RID p_particles, real_t p_size) override; virtual void particles_set_transform_align(RID p_particles, RS::ParticlesTransformAlign p_transform_align) override; + virtual void particles_set_seed(RID p_particles, uint32_t p_seed) override; virtual void particles_set_trails(RID p_particles, bool p_enable, double p_length) override; virtual void particles_set_trail_bind_poses(RID p_particles, const Vector &p_bind_poses) override; diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 8428fef788..37f9514597 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -336,16 +336,7 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS: /* Texture API */ -Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type, bool &r_compressed, bool p_force_decompress) const { - Config *config = Config::get_singleton(); - r_gl_format = 0; - Ref image = p_image; - r_compressed = false; - r_real_format = p_format; - - bool need_decompress = false; - bool decompress_ra_to_rg = false; - +static inline Error _get_gl_uncompressed_format(Image::Format p_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type) { switch (p_format) { case Image::FORMAT_L8: { if (RasterizerGLES3::is_gles_over_gl()) { @@ -373,55 +364,51 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_internal_format = GL_R8; r_gl_format = GL_RED; r_gl_type = GL_UNSIGNED_BYTE; - } break; case Image::FORMAT_RG8: { r_gl_internal_format = GL_RG8; r_gl_format = GL_RG; r_gl_type = GL_UNSIGNED_BYTE; - } break; case Image::FORMAT_RGB8: { r_gl_internal_format = GL_RGB8; r_gl_format = GL_RGB; r_gl_type = GL_UNSIGNED_BYTE; - } break; case Image::FORMAT_RGBA8: { - r_gl_format = GL_RGBA; r_gl_internal_format = GL_RGBA8; + r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; - } break; case Image::FORMAT_RGBA4444: { r_gl_internal_format = GL_RGBA4; r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_SHORT_4_4_4_4; - + } break; + case Image::FORMAT_RGB565: { + r_gl_internal_format = GL_RGB565; + r_gl_format = GL_RGB; + r_gl_type = GL_UNSIGNED_SHORT_5_6_5; } break; case Image::FORMAT_RF: { r_gl_internal_format = GL_R32F; r_gl_format = GL_RED; r_gl_type = GL_FLOAT; - } break; case Image::FORMAT_RGF: { r_gl_internal_format = GL_RG32F; r_gl_format = GL_RG; r_gl_type = GL_FLOAT; - } break; case Image::FORMAT_RGBF: { r_gl_internal_format = GL_RGB32F; r_gl_format = GL_RGB; r_gl_type = GL_FLOAT; - } break; case Image::FORMAT_RGBAF: { r_gl_internal_format = GL_RGBA32F; r_gl_format = GL_RGBA; r_gl_type = GL_FLOAT; - } break; case Image::FORMAT_RH: { r_gl_internal_format = GL_R16F; @@ -432,26 +419,48 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_internal_format = GL_RG16F; r_gl_format = GL_RG; r_gl_type = GL_HALF_FLOAT; - } break; case Image::FORMAT_RGBH: { r_gl_internal_format = GL_RGB16F; r_gl_format = GL_RGB; r_gl_type = GL_HALF_FLOAT; - } break; case Image::FORMAT_RGBAH: { r_gl_internal_format = GL_RGBA16F; r_gl_format = GL_RGBA; r_gl_type = GL_HALF_FLOAT; - } break; case Image::FORMAT_RGBE9995: { r_gl_internal_format = GL_RGB9_E5; r_gl_format = GL_RGB; r_gl_type = GL_UNSIGNED_INT_5_9_9_9_REV; - } break; + default: { + return ERR_UNAVAILABLE; + } + } + + return OK; +} + +Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type, bool &r_compressed, bool p_force_decompress) const { + Config *config = Config::get_singleton(); + r_gl_format = 0; + Ref image = p_image; + r_compressed = false; + r_real_format = p_format; + + if (!Image::is_format_compressed(p_format)) { + Error err = _get_gl_uncompressed_format(p_format, r_gl_format, r_gl_internal_format, r_gl_type); + ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("The image format %d is not supported by the Compatibility renderer.", p_format)); + return p_image; + } + + // For compressed images, some formats may not be supported by the current device and will require decompression. + bool need_decompress = false; + bool decompress_ra_to_rg = false; + + switch (p_format) { case Image::FORMAT_DXT1: { if (config->s3tc_supported) { r_gl_internal_format = _EXT_COMPRESSED_RGBA_S3TC_DXT1_EXT; @@ -538,7 +547,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RED; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -549,7 +557,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RED; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -560,7 +567,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RG; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -571,7 +577,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RG; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -583,7 +588,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGB; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -594,7 +598,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -605,7 +608,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -644,7 +646,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -655,7 +656,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -666,7 +666,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -677,7 +676,6 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_gl_format = GL_RGBA; r_gl_type = GL_UNSIGNED_BYTE; r_compressed = true; - } else { need_decompress = true; } @@ -692,41 +690,17 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I image = image->duplicate(); image->decompress(); ERR_FAIL_COND_V(image->is_compressed(), image); + if (decompress_ra_to_rg) { image->convert_ra_rgba8_to_rg(); image->convert(Image::FORMAT_RG8); } - switch (image->get_format()) { - case Image::FORMAT_RG8: { - r_gl_format = GL_RG; - r_gl_internal_format = GL_RG8; - r_gl_type = GL_UNSIGNED_BYTE; - r_real_format = Image::FORMAT_RG8; - r_compressed = false; - } break; - case Image::FORMAT_RGB8: { - r_gl_format = GL_RGB; - r_gl_internal_format = GL_RGB; - r_gl_type = GL_UNSIGNED_BYTE; - r_real_format = Image::FORMAT_RGB8; - r_compressed = false; - } break; - case Image::FORMAT_RGBA8: { - r_gl_format = GL_RGBA; - r_gl_internal_format = GL_RGBA; - r_gl_type = GL_UNSIGNED_BYTE; - r_real_format = Image::FORMAT_RGBA8; - r_compressed = false; - } break; - default: { - image->convert(Image::FORMAT_RGBA8); - r_gl_format = GL_RGBA; - r_gl_internal_format = GL_RGBA; - r_gl_type = GL_UNSIGNED_BYTE; - r_real_format = Image::FORMAT_RGBA8; - r_compressed = false; - } break; - } + + Error err = _get_gl_uncompressed_format(image->get_format(), r_gl_format, r_gl_internal_format, r_gl_type); + ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("The image format %d is not supported by the Compatibility renderer.", image->get_format())); + + r_real_format = image->get_format(); + r_compressed = false; } return image; diff --git a/drivers/metal/inflection_map.h b/drivers/metal/inflection_map.h new file mode 100644 index 0000000000..87a55e49e3 --- /dev/null +++ b/drivers/metal/inflection_map.h @@ -0,0 +1,127 @@ +/**************************************************************************/ +/* inflection_map.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef INFLECTION_MAP_H +#define INFLECTION_MAP_H + +#include "core/templates/hash_map.h" +#include "core/templates/local_vector.h" + +/// An unordered map that splits elements between a fast-access vector of LinearCount consecutively +/// indexed elements, and a slower-access map holding sparse indexes larger than LinearCount. +/// +/// \tparam KeyType is used to lookup values, and must be a type that is convertible to an unsigned integer. +/// \tparam ValueType must have an empty constructor (default or otherwise). +/// \tparam LinearCount +/// \tparam IndexType must be a type that is convertible to an unsigned integer (eg. uint8_t...uint64_t), and which is large enough to represent the number of values in this map. +template +class InflectionMap { +public: + using value_type = ValueType; + class Iterator { + InflectionMap *map; + IndexType index; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using pointer = value_type *; + using reference = value_type &; + + Iterator() : + map(nullptr), index(0) {} + Iterator(InflectionMap &p_m, const IndexType p_i) : + map(&p_m), index(p_i) {} + + Iterator &operator=(const Iterator &p_it) { + map = p_it.map; + index = p_it.index; + return *this; + } + + ValueType *operator->() { return &map->_values[index]; } + ValueType &operator*() { return map->_values[index]; } + operator ValueType *() { return &map->_values[index]; } + + bool operator==(const Iterator &p_it) const { return map == p_it.map && index == p_it.index; } + bool operator!=(const Iterator &p_it) const { return map != p_it.map || index != p_it.index; } + + Iterator &operator++() { + index++; + return *this; + } + Iterator operator++(int) { + Iterator t = *this; + index++; + return t; + } + + bool is_valid() const { return index < map->_values.size(); } + }; + + const ValueType &operator[](const KeyType p_idx) const { return get_value(p_idx); } + ValueType &operator[](const KeyType p_idx) { return get_value(p_idx); } + + Iterator begin() { return Iterator(*this, 0); } + Iterator end() { return Iterator(*this, _values.size()); } + + bool is_empty() { return _values.is_empty(); } + size_t size() { return _values.size(); } + void reserve(size_t p_new_cap) { _values.reserve(p_new_cap); } + +protected: + static constexpr IndexType INVALID = std::numeric_limits::max(); + typedef struct IndexValue { + IndexType value = INVALID; + } IndexValue; + + // Returns a reference to the value at the index. + // If the index has not been initialized, add an empty element at + // the end of the values array, and set the index to its position. + ValueType &get_value(KeyType p_idx) { + IndexValue *val_idx = p_idx < LinearCount ? &_linear_indexes[p_idx] : _inflection_indexes.getptr(p_idx); + if (val_idx == nullptr || val_idx->value == INVALID) { + _values.push_back({}); + if (val_idx == nullptr) { + val_idx = &_inflection_indexes.insert(p_idx, {})->value; + } + val_idx->value = _values.size() - 1; + } + return _values[val_idx->value]; + } + + TightLocalVector _values; + HashMap _inflection_indexes; + IndexValue _linear_indexes[LinearCount]; +}; + +#endif // INFLECTION_MAP_H diff --git a/drivers/metal/metal_device_properties.h b/drivers/metal/metal_device_properties.h index 35efa7de6f..fd86163265 100644 --- a/drivers/metal/metal_device_properties.h +++ b/drivers/metal/metal_device_properties.h @@ -72,9 +72,14 @@ typedef NS_OPTIONS(NSUInteger, SampleCount) { SampleCount64 = (1UL << 6), }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) MetalFeatures { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MetalFeatures { uint32_t mslVersion = 0; MTLGPUFamily highestFamily = MTLGPUFamilyApple4; + bool supportsBCTextureCompression = false; + bool supportsDepth24Stencil8 = false; + bool supports32BitFloatFiltering = false; + bool supports32BitMSAA = false; + bool supportsMac = TARGET_OS_OSX; MTLLanguageVersion mslVersionEnum = MTLLanguageVersion1_2; SampleCount supportedSampleCounts = SampleCount1; long hostMemoryPageSize = 0; @@ -86,6 +91,8 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) MetalFeatures { bool tessellationShader = false; /**< If true, tessellation shaders are supported. */ bool imageCubeArray = false; /**< If true, image cube arrays are supported. */ MTLArgumentBuffersTier argument_buffers_tier = MTLArgumentBuffersTier1; + /// If true, argument encoders are required to encode arguments into an argument buffer. + bool needs_arg_encoders = true; bool metal_fx_spatial = false; /**< If true, Metal FX spatial functions are supported. */ bool metal_fx_temporal = false; /**< If true, Metal FX temporal functions are supported. */ }; @@ -128,7 +135,7 @@ struct MetalLimits { BitField subgroupSupportedOperations; /**< The subgroup operations supported by the device. */ }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MetalDeviceProperties { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MetalDeviceProperties { private: void init_features(id p_device); void init_limits(id p_device); diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm index 68d73a9719..694dea04a0 100644 --- a/drivers/metal/metal_device_properties.mm +++ b/drivers/metal/metal_device_properties.mm @@ -61,11 +61,11 @@ #define KIBI (1024) #define MEBI (KIBI * KIBI) -#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 140000) || (TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED < 170000) +#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 140000) || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED < 170000) #define MTLGPUFamilyApple9 (MTLGPUFamily)1009 #endif -API_AVAILABLE(macos(11.0), ios(14.0)) +API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MTLGPUFamily &operator--(MTLGPUFamily &p_family) { p_family = static_cast(static_cast(p_family) - 1); if (p_family < MTLGPUFamilyApple1) { @@ -86,6 +86,21 @@ void MetalDeviceProperties::init_features(id p_device) { } } + if (@available(macOS 11, iOS 16.4, tvOS 16.4, *)) { + features.supportsBCTextureCompression = p_device.supportsBCTextureCompression; + } else { + features.supportsBCTextureCompression = false; + } + +#if TARGET_OS_OSX + features.supportsDepth24Stencil8 = p_device.isDepth24Stencil8PixelFormatSupported; +#endif + + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + features.supports32BitFloatFiltering = p_device.supports32BitFloatFiltering; + features.supports32BitMSAA = p_device.supports32BitMSAA; + } + features.hostMemoryPageSize = sysconf(_SC_PAGESIZE); for (SampleCount sc = SampleCount1; sc <= SampleCount64; sc <<= 1) { @@ -103,7 +118,11 @@ void MetalDeviceProperties::init_features(id p_device) { features.simdReduction = [p_device supportsFamily:MTLGPUFamilyApple7]; features.argument_buffers_tier = p_device.argumentBuffersSupport; - if (@available(macOS 13.0, iOS 16.0, *)) { + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { + features.needs_arg_encoders = !([p_device supportsFamily:MTLGPUFamilyMetal3] && features.argument_buffers_tier == MTLArgumentBuffersTier2); + } + + if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { features.metal_fx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:p_device]; features.metal_fx_temporal = [MTLFXTemporalScalerDescriptor supportsDevice:p_device]; } @@ -115,12 +134,12 @@ void MetalDeviceProperties::init_features(id p_device) { features.mslVersion = SPIRV_CROSS_NAMESPACE::CompilerMSL::Options::make_msl_version(m_maj, m_min) switch (features.mslVersionEnum) { -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000 +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 180000 || __TV_OS_VERSION_MAX_ALLOWED >= 180000 case MTLLanguageVersion3_2: setMSLVersion(3, 2); break; #endif -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 170000 || __TV_OS_VERSION_MAX_ALLOWED >= 170000 case MTLLanguageVersion3_1: setMSLVersion(3, 1); break; @@ -294,7 +313,7 @@ void MetalDeviceProperties::init_limits(id p_device) { limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1; - if (@available(macOS 14.0, iOS 17.0, *)) { + if (@available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) { limits.temporalScalerInputContentMinScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMinScaleForDevice:p_device]; limits.temporalScalerInputContentMaxScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMaxScaleForDevice:p_device]; } else { diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h index 74d8ce714a..2d78c932f1 100644 --- a/drivers/metal/metal_objects.h +++ b/drivers/metal/metal_objects.h @@ -182,7 +182,7 @@ struct ClearAttKey { } }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDResourceFactory { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDResourceFactory { private: RenderingDeviceDriverMetal *device_driver; @@ -200,7 +200,7 @@ public: ~MDResourceFactory() = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDResourceCache { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDResourceCache { private: typedef HashMap, HashableHasher> HashMap; std::unique_ptr resource_factory; @@ -249,7 +249,7 @@ struct MDSubpass { MTLFmtCaps getRequiredFmtCapsForAttachmentAt(uint32_t p_index) const; }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) MDAttachment { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDAttachment { private: uint32_t index = 0; uint32_t firstUseSubpassIndex = 0; @@ -301,7 +301,7 @@ public: bool shouldClear(MDSubpass const &p_subpass, bool p_is_stencil) const; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPass { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDRenderPass { public: Vector attachments; Vector subpasses; @@ -313,7 +313,7 @@ public: MDRenderPass(Vector &p_attachments, Vector &p_subpasses); }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDCommandBuffer { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDCommandBuffer { private: RenderingDeviceDriverMetal *device_driver = nullptr; id queue = nil; @@ -559,7 +559,7 @@ public: #define MTLBindingAccessWriteOnly MTLArgumentAccessWriteOnly #endif -struct API_AVAILABLE(macos(11.0), ios(14.0)) BindingInfo { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) BindingInfo { MTLDataType dataType = MTLDataTypeNone; uint32_t index = 0; MTLBindingAccess access = MTLBindingAccessReadOnly; @@ -610,16 +610,16 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) BindingInfo { using RDC = RenderingDeviceCommons; -typedef API_AVAILABLE(macos(11.0), ios(14.0)) HashMap BindingInfoMap; +typedef API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) HashMap BindingInfoMap; -struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformInfo { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) UniformInfo { uint32_t binding; ShaderStageUsage active_stages = None; BindingInfoMap bindings; BindingInfoMap bindings_secondary; }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformSet { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) UniformSet { LocalVector uniforms; uint32_t buffer_size = 0; HashMap offsets; @@ -696,7 +696,7 @@ struct ShaderCacheEntry { ~ShaderCacheEntry() = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDShader { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDShader { public: CharString name; Vector sets; @@ -709,7 +709,7 @@ public: virtual ~MDShader() = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDComputeShader final : public MDShader { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDComputeShader final : public MDShader { public: struct { uint32_t binding = -1; @@ -727,7 +727,7 @@ public: MDComputeShader(CharString p_name, Vector p_sets, bool p_uses_argument_buffers, MDLibrary *p_kernel); }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderShader final : public MDShader { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDRenderShader final : public MDShader { public: struct { struct { @@ -788,7 +788,7 @@ struct BoundUniformSet { void merge_into(ResourceUsageMap &p_dst) const; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDUniformSet { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDUniformSet { private: void bind_uniforms_argument_buffers(MDShader *p_shader, MDCommandBuffer::RenderState &p_state); void bind_uniforms_direct(MDShader *p_shader, MDCommandBuffer::RenderState &p_state); @@ -806,7 +806,7 @@ public: BoundUniformSet &bound_uniform_set(MDShader *p_shader, id p_device, ResourceUsageMap &p_resource_usage); }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDPipeline { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDPipeline { public: MDPipelineType type; @@ -815,7 +815,7 @@ public: virtual ~MDPipeline() = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPipeline final : public MDPipeline { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDRenderPipeline final : public MDPipeline { public: id state = nil; id depth_stencil = nil; @@ -892,7 +892,7 @@ public: ~MDRenderPipeline() final = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDComputePipeline final : public MDPipeline { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDComputePipeline final : public MDPipeline { public: id state = nil; struct { @@ -906,7 +906,7 @@ public: ~MDComputePipeline() final = default; }; -class API_AVAILABLE(macos(11.0), ios(14.0)) MDFrameBuffer { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MDFrameBuffer { Vector textures; public: diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm index 859a3821c8..6ea749365b 100644 --- a/drivers/metal/metal_objects.mm +++ b/drivers/metal/metal_objects.mm @@ -64,7 +64,7 @@ void MDCommandBuffer::begin() { DEV_ASSERT(commandBuffer == nil); - commandBuffer = queue.commandBufferWithUnretainedReferences; + commandBuffer = queue.commandBuffer; } void MDCommandBuffer::end() { diff --git a/drivers/metal/pixel_formats.h b/drivers/metal/pixel_formats.h index ac6e14e8db..dfdb73bd53 100644 --- a/drivers/metal/pixel_formats.h +++ b/drivers/metal/pixel_formats.h @@ -56,14 +56,13 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +#import "inflection_map.h" +#import "metal_device_properties.h" + #import "servers/rendering/rendering_device.h" #import -static const uint32_t _mtlPixelFormatCount = 256; -static const uint32_t _mtlPixelFormatCoreCount = MTLPixelFormatX32_Stencil8 + 2; // The actual last enum value is not available on iOS. -static const uint32_t _mtlVertexFormatCount = MTLVertexFormatHalf + 1; - #pragma mark - #pragma mark Metal format capabilities @@ -184,13 +183,20 @@ enum class MTLFormatType { Compressed, /**< A block-compressed color. */ }; -typedef struct Extent2D { +struct Extent2D { uint32_t width; uint32_t height; -} Extent2D; +}; + +struct ComponentMapping { + RD::TextureSwizzle r = RD::TEXTURE_SWIZZLE_IDENTITY; + RD::TextureSwizzle g = RD::TEXTURE_SWIZZLE_IDENTITY; + RD::TextureSwizzle b = RD::TEXTURE_SWIZZLE_IDENTITY; + RD::TextureSwizzle a = RD::TEXTURE_SWIZZLE_IDENTITY; +}; /** Describes the properties of a DataFormat, including the corresponding Metal pixel and vertex format. */ -typedef struct DataFormatDesc { +struct DataFormatDesc { RD::DataFormat dataFormat; MTLPixelFormat mtlPixelFormat; MTLPixelFormat mtlPixelFormatSubstitute; @@ -201,6 +207,7 @@ typedef struct DataFormatDesc { Extent2D blockTexelSize; uint32_t bytesPerBlock; MTLFormatType formatType; + ComponentMapping componentMapping; const char *name; bool hasReportedSubstitution; @@ -211,24 +218,31 @@ typedef struct DataFormatDesc { inline bool vertexIsSupported() const { return (mtlVertexFormat != MTLVertexFormatInvalid); } inline bool vertexIsSupportedOrSubstitutable() const { return vertexIsSupported() || (mtlVertexFormatSubstitute != MTLVertexFormatInvalid); } -} DataFormatDesc; + + bool needsSwizzle() const { + return (componentMapping.r != RD::TEXTURE_SWIZZLE_IDENTITY || + componentMapping.g != RD::TEXTURE_SWIZZLE_IDENTITY || + componentMapping.b != RD::TEXTURE_SWIZZLE_IDENTITY || + componentMapping.a != RD::TEXTURE_SWIZZLE_IDENTITY); + } +}; /** Describes the properties of a MTLPixelFormat or MTLVertexFormat. */ -typedef struct MTLFormatDesc { +struct MTLFormatDesc { union { MTLPixelFormat mtlPixelFormat; MTLVertexFormat mtlVertexFormat; }; - RD::DataFormat dataFormat; + RD::DataFormat dataFormat = RD::DATA_FORMAT_MAX; MTLFmtCaps mtlFmtCaps; MTLViewClass mtlViewClass; MTLPixelFormat mtlPixelFormatLinear; const char *name = nullptr; inline bool isSupported() const { return (mtlPixelFormat != MTLPixelFormatInvalid) && (mtlFmtCaps != kMTLFmtCapsNone); } -} MTLFormatDesc; +}; -class API_AVAILABLE(macos(11.0), ios(14.0)) PixelFormats { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) PixelFormats { using DataFormat = RD::DataFormat; public: @@ -355,7 +369,10 @@ public: */ size_t getBytesPerLayer(MTLPixelFormat p_format, size_t p_bytes_per_row, uint32_t p_texel_rows_per_layer); - /** Returns the Metal format capabilities supported by the specified Redot format, without substitution. */ + /** Returns whether or not the specified Godot format requires swizzling to use with Metal. */ + bool needsSwizzle(DataFormat p_format); + + /** Returns the Metal format capabilities supported by the specified Godot format, without substitution. */ MTLFmtCaps getCapabilities(DataFormat p_format, bool p_extended = false); /** Returns the Metal format capabilities supported by the specified Metal format. */ @@ -369,48 +386,28 @@ public: #pragma mark Construction - explicit PixelFormats(id p_device); + explicit PixelFormats(id p_device, const MetalFeatures &p_feat); protected: - id device; - DataFormatDesc &getDataFormatDesc(DataFormat p_format); DataFormatDesc &getDataFormatDesc(MTLPixelFormat p_format); MTLFormatDesc &getMTLPixelFormatDesc(MTLPixelFormat p_format); + MTLFmtCaps &getMTLPixelFormatCapsIf(MTLPixelFormat mtlPixFmt, bool cond); MTLFormatDesc &getMTLVertexFormatDesc(MTLVertexFormat p_format); + void initDataFormatCapabilities(); void initMTLPixelFormatCapabilities(); - void initMTLVertexFormatCapabilities(); - void buildMTLFormatMaps(); + void initMTLVertexFormatCapabilities(const MetalFeatures &p_feat); + void modifyMTLFormatCapabilities(const MetalFeatures &p_feat); void buildDFFormatMaps(); - void modifyMTLFormatCapabilities(); - void modifyMTLFormatCapabilities(id p_device); - void addMTLPixelFormatCapabilities(id p_device, - MTLFeatureSet p_feature_set, - MTLPixelFormat p_format, - MTLFmtCaps p_caps); - void addMTLPixelFormatCapabilities(id p_device, - MTLGPUFamily p_family, - MTLPixelFormat p_format, - MTLFmtCaps p_caps); - void disableMTLPixelFormatCapabilities(MTLPixelFormat p_format, - MTLFmtCaps p_caps); - void disableAllMTLPixelFormatCapabilities(MTLPixelFormat p_format); - void addMTLVertexFormatCapabilities(id p_device, - MTLFeatureSet p_feature_set, - MTLVertexFormat p_format, - MTLFmtCaps p_caps); + void addMTLPixelFormatDescImpl(MTLPixelFormat p_pix_fmt, MTLPixelFormat p_pix_fmt_linear, + MTLViewClass p_view_class, MTLFmtCaps p_fmt_caps, const char *p_name); + void addMTLVertexFormatDescImpl(MTLVertexFormat p_vert_fmt, MTLFmtCaps p_vert_caps, const char *name); - DataFormatDesc _dataFormatDescriptions[RD::DATA_FORMAT_MAX]; - MTLFormatDesc _mtlPixelFormatDescriptions[_mtlPixelFormatCount]; - MTLFormatDesc _mtlVertexFormatDescriptions[_mtlVertexFormatCount]; - - // Most Metal formats have small values and are mapped by simple lookup array. - // Outliers are mapped by a map. - uint16_t _mtlFormatDescIndicesByMTLPixelFormatsCore[_mtlPixelFormatCoreCount]; - HashMap _mtlFormatDescIndicesByMTLPixelFormatsExt; - - uint16_t _mtlFormatDescIndicesByMTLVertexFormats[_mtlVertexFormatCount]; + id device; + InflectionMap _data_format_descs; + InflectionMap _mtl_pixel_format_descs; // The actual last enum value is not available on iOS. + TightLocalVector _mtl_vertex_format_descs; }; #pragma clang diagnostic pop diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm index 473d6b75ad..6099e68fef 100644 --- a/drivers/metal/pixel_formats.mm +++ b/drivers/metal/pixel_formats.mm @@ -99,17 +99,6 @@ #define MTLVertexFormatFloatRGB9E5 MTLVertexFormatInvalid #endif -/** Selects and returns one of the values, based on the platform OS. */ -_FORCE_INLINE_ constexpr MTLFmtCaps select_platform_caps(MTLFmtCaps p_macOS_val, MTLFmtCaps p_iOS_val) { -#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST - return p_iOS_val; -#elif TARGET_OS_OSX - return p_macOS_val; -#else -#error "unsupported platform" -#endif -} - template void clear(T *p_val, size_t p_count = 1) { memset(p_val, 0, sizeof(T) * p_count); @@ -209,6 +198,10 @@ size_t PixelFormats::getBytesPerLayer(MTLPixelFormat p_format, size_t p_bytes_pe return Math::division_round_up(p_texel_rows_per_layer, getDataFormatDesc(p_format).blockTexelSize.height) * p_bytes_per_row; } +bool PixelFormats::needsSwizzle(DataFormat p_format) { + return getDataFormatDesc(p_format).needsSwizzle(); +} + MTLFmtCaps PixelFormats::getCapabilities(DataFormat p_format, bool p_extended) { return getCapabilities(getDataFormatDesc(p_format).mtlPixelFormat, p_extended); } @@ -220,7 +213,7 @@ MTLFmtCaps PixelFormats::getCapabilities(MTLPixelFormat p_format, bool p_extende return caps; } // Now get caps of all formats in the view class. - for (MTLFormatDesc &otherDesc : _mtlPixelFormatDescriptions) { + for (MTLFormatDesc &otherDesc : _mtl_vertex_format_descs) { if (otherDesc.mtlViewClass == mtlDesc.mtlViewClass) { caps |= otherDesc.mtlFmtCaps; } @@ -253,8 +246,7 @@ MTLVertexFormat PixelFormats::getMTLVertexFormat(DataFormat p_format) { } DataFormatDesc &PixelFormats::getDataFormatDesc(DataFormat p_format) { - CRASH_BAD_INDEX_MSG(p_format, RD::DATA_FORMAT_MAX, "Attempting to describe an invalid DataFormat"); - return _dataFormatDescriptions[p_format]; + return _data_format_descs[p_format]; } DataFormatDesc &PixelFormats::getDataFormatDesc(MTLPixelFormat p_format) { @@ -263,51 +255,53 @@ DataFormatDesc &PixelFormats::getDataFormatDesc(MTLPixelFormat p_format) { // Return a reference to the Metal format descriptor corresponding to the MTLPixelFormat. MTLFormatDesc &PixelFormats::getMTLPixelFormatDesc(MTLPixelFormat p_format) { - uint16_t fmtIdx = ((p_format < _mtlPixelFormatCoreCount) - ? _mtlFormatDescIndicesByMTLPixelFormatsCore[p_format] - : _mtlFormatDescIndicesByMTLPixelFormatsExt[p_format]); - return _mtlPixelFormatDescriptions[fmtIdx]; + return _mtl_pixel_format_descs[p_format]; } // Return a reference to the Metal format descriptor corresponding to the MTLVertexFormat. MTLFormatDesc &PixelFormats::getMTLVertexFormatDesc(MTLVertexFormat p_format) { - uint16_t fmtIdx = (p_format < _mtlVertexFormatCount) ? _mtlFormatDescIndicesByMTLVertexFormats[p_format] : 0; - return _mtlVertexFormatDescriptions[fmtIdx]; + return _mtl_vertex_format_descs[p_format]; } -PixelFormats::PixelFormats(id p_device) : +PixelFormats::PixelFormats(id p_device, const MetalFeatures &p_feat) : device(p_device) { initMTLPixelFormatCapabilities(); - initMTLVertexFormatCapabilities(); - buildMTLFormatMaps(); - modifyMTLFormatCapabilities(); + initMTLVertexFormatCapabilities(p_feat); + modifyMTLFormatCapabilities(p_feat); initDataFormatCapabilities(); buildDFFormatMaps(); } -#define addDfFormatDescFull(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \ - CRASH_BAD_INDEX_MSG(RD::DATA_FORMAT_##DATA_FMT, RD::DATA_FORMAT_MAX, "Attempting to describe too many DataFormats"); \ - _dataFormatDescriptions[RD::DATA_FORMAT_##DATA_FMT] = { RD::DATA_FORMAT_##DATA_FMT, MTLPixelFormat##MTL_FMT, MTLPixelFormat##MTL_FMT_ALT, MTLVertexFormat##MTL_VTX_FMT, MTLVertexFormat##MTL_VTX_FMT_ALT, \ - CSPC, CSCB, { BLK_W, BLK_H }, BLK_BYTE_CNT, MTLFormatType::MVK_FMT_TYPE, "DATA_FORMAT_" #DATA_FMT, false } +#define addDataFormatDescFull(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) \ + dfFmt = RD::DATA_FORMAT_##DATA_FMT; \ + _data_format_descs[dfFmt] = { dfFmt, MTLPixelFormat##MTL_FMT, MTLPixelFormat##MTL_FMT_ALT, MTLVertexFormat##MTL_VTX_FMT, MTLVertexFormat##MTL_VTX_FMT_ALT, \ + CSPC, CSCB, { BLK_W, BLK_H }, BLK_BYTE_CNT, MTLFormatType::MVK_FMT_TYPE, \ + { RD::TEXTURE_SWIZZLE_##SWIZ_R, RD::TEXTURE_SWIZZLE_##SWIZ_G, RD::TEXTURE_SWIZZLE_##SWIZ_B, RD::TEXTURE_SWIZZLE_##SWIZ_A }, \ + "DATA_FORMAT_" #DATA_FMT, false } -#define addDataFormatDesc(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \ - addDfFormatDescFull(DATA_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) +#define addDataFormatDesc(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \ + addDataFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, IDENTITY, IDENTITY, IDENTITY, IDENTITY) + +#define addDataFormatDescSwizzled(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) \ + addDataFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) #define addDfFormatDescChromaSubsampling(DATA_FMT, MTL_FMT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT) \ - addDfFormatDescFull(DATA_FMT, MTL_FMT, Invalid, Invalid, Invalid, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, ColorFloat) + addDataFormatDescFull(DATA_FMT, MTL_FMT, Invalid, Invalid, Invalid, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, ColorFloat, IDENTITY, IDENTITY, IDENTITY, IDENTITY) void PixelFormats::initDataFormatCapabilities() { - clear(_dataFormatDescriptions, RD::DATA_FORMAT_MAX); + _data_format_descs.reserve(RD::DATA_FORMAT_MAX + 1); // reserve enough space to avoid reallocs + DataFormat dfFmt; + addDataFormatDesc(R4G4_UNORM_PACK8, Invalid, Invalid, Invalid, Invalid, 1, 1, 1, ColorFloat); addDataFormatDesc(R4G4_UNORM_PACK8, Invalid, Invalid, Invalid, Invalid, 1, 1, 1, ColorFloat); addDataFormatDesc(R4G4B4A4_UNORM_PACK16, ABGR4Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); - addDataFormatDesc(B4G4R4A4_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); + addDataFormatDescSwizzled(B4G4R4A4_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat, B, G, R, A); addDataFormatDesc(R5G6B5_UNORM_PACK16, B5G6R5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); - addDataFormatDesc(B5G6R5_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); + addDataFormatDescSwizzled(B5G6R5_UNORM_PACK16, B5G6R5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat, B, G, R, A); addDataFormatDesc(R5G5B5A1_UNORM_PACK16, A1BGR5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); - addDataFormatDesc(B5G5R5A1_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); + addDataFormatDescSwizzled(B5G5R5A1_UNORM_PACK16, A1BGR5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat, B, G, R, A); addDataFormatDesc(A1R5G5B5_UNORM_PACK16, BGR5A1Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat); addDataFormatDesc(R8_UNORM, R8Unorm, Invalid, UCharNormalized, UChar2Normalized, 1, 1, 1, ColorFloat); @@ -351,11 +345,11 @@ void PixelFormats::initDataFormatCapabilities() { addDataFormatDesc(R8G8B8A8_SRGB, RGBA8Unorm_sRGB, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat); addDataFormatDesc(B8G8R8A8_UNORM, BGRA8Unorm, Invalid, UChar4Normalized_BGRA, Invalid, 1, 1, 4, ColorFloat); - addDataFormatDesc(B8G8R8A8_SNORM, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat); + addDataFormatDescSwizzled(B8G8R8A8_SNORM, RGBA8Snorm, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat, B, G, R, A); addDataFormatDesc(B8G8R8A8_USCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat); addDataFormatDesc(B8G8R8A8_SSCALED, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat); - addDataFormatDesc(B8G8R8A8_UINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorUInt8); - addDataFormatDesc(B8G8R8A8_SINT, Invalid, Invalid, Invalid, Invalid, 1, 1, 4, ColorInt8); + addDataFormatDescSwizzled(B8G8R8A8_UINT, RGBA8Uint, Invalid, Invalid, Invalid, 1, 1, 4, ColorUInt8, B, G, R, A); + addDataFormatDescSwizzled(B8G8R8A8_SINT, RGBA8Sint, Invalid, Invalid, Invalid, 1, 1, 4, ColorInt8, B, G, R, A); addDataFormatDesc(B8G8R8A8_SRGB, BGRA8Unorm_sRGB, Invalid, Invalid, Invalid, 1, 1, 4, ColorFloat); addDataFormatDesc(A8B8G8R8_UNORM_PACK32, RGBA8Unorm, Invalid, UChar4Normalized, Invalid, 1, 1, 4, ColorFloat); @@ -565,735 +559,470 @@ void PixelFormats::initDataFormatCapabilities() { addDfFormatDescChromaSubsampling(G16_B16_R16_3PLANE_444_UNORM, Invalid, 3, 16, 1, 1, 6); } -#define addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR) \ - CRASH_BAD_INDEX_MSG(fmtIdx, _mtlPixelFormatCount, "Adding too many pixel formats"); \ - _mtlPixelFormatDescriptions[fmtIdx++] = { .mtlPixelFormat = MTLPixelFormat##MTL_FMT, RD::DATA_FORMAT_MAX, select_platform_caps(kMTLFmtCaps##MACOS_CAPS, kMTLFmtCaps##IOS_CAPS), MTLViewClass::VIEW_CLASS, MTLPixelFormat##MTL_FMT_LINEAR, "MTLPixelFormat" #MTL_FMT } +void PixelFormats::addMTLPixelFormatDescImpl(MTLPixelFormat p_pix_fmt, MTLPixelFormat p_pix_fmt_linear, + MTLViewClass p_view_class, MTLFmtCaps p_fmt_caps, const char *p_name) { + _mtl_pixel_format_descs[p_pix_fmt] = { .mtlPixelFormat = p_pix_fmt, DataFormat::DATA_FORMAT_MAX, p_fmt_caps, p_view_class, p_pix_fmt_linear, p_name }; +} -#define addMTLPixelFormatDesc(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS) \ - addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT) +#define addMTLPixelFormatDescFull(mtlFmt, mtlFmtLinear, viewClass, appleGPUCaps) \ + addMTLPixelFormatDescImpl(MTLPixelFormat##mtlFmt, MTLPixelFormat##mtlFmtLinear, MTLViewClass::viewClass, \ + appleGPUCaps, "MTLPixelFormat" #mtlFmt) -#define addMTLPixelFormatDescSRGB(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR) \ - addMTLPixelFormatDescFull(MTL_FMT, VIEW_CLASS, IOS_CAPS, MACOS_CAPS, MTL_FMT_LINEAR) +#define addMTLPixelFormatDesc(mtlFmt, viewClass, appleGPUCaps) \ + addMTLPixelFormatDescFull(mtlFmt, mtlFmt, viewClass, kMTLFmtCaps##appleGPUCaps) + +#define addMTLPixelFormatDescSRGB(mtlFmt, viewClass, appleGPUCaps, mtlFmtLinear) \ + /* Cannot write to sRGB textures in the simulator */ \ + if (TARGET_OS_SIMULATOR) { \ + MTLFmtCaps appleFmtCaps = kMTLFmtCaps##appleGPUCaps; \ + flags::clear(appleFmtCaps, kMTLFmtCapsWrite); \ + addMTLPixelFormatDescFull(mtlFmt, mtlFmtLinear, viewClass, appleFmtCaps); \ + } else { \ + addMTLPixelFormatDescFull(mtlFmt, mtlFmtLinear, viewClass, kMTLFmtCaps##appleGPUCaps); \ + } void PixelFormats::initMTLPixelFormatCapabilities() { - clear(_mtlPixelFormatDescriptions, _mtlPixelFormatCount); + _mtl_pixel_format_descs.reserve(1024); - uint32_t fmtIdx = 0; - - // When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count. - - // MTLPixelFormatInvalid must come first. - addMTLPixelFormatDesc(Invalid, None, None, None); + // MTLPixelFormatInvalid must come first. Use addMTLPixelFormatDescImpl to avoid guard code. + addMTLPixelFormatDescImpl(MTLPixelFormatInvalid, MTLPixelFormatInvalid, MTLViewClass::None, kMTLFmtCapsNone, "MTLPixelFormatInvalid"); // Ordinary 8-bit pixel formats. - addMTLPixelFormatDesc(A8Unorm, Color8, RF, RF); - addMTLPixelFormatDesc(R8Unorm, Color8, All, All); - addMTLPixelFormatDescSRGB(R8Unorm_sRGB, Color8, RFCMRB, None, R8Unorm); - addMTLPixelFormatDesc(R8Snorm, Color8, RFWCMB, All); - addMTLPixelFormatDesc(R8Uint, Color8, RWCM, RWCM); - addMTLPixelFormatDesc(R8Sint, Color8, RWCM, RWCM); + addMTLPixelFormatDesc(A8Unorm, Color8, All); + addMTLPixelFormatDesc(R8Unorm, Color8, All); + addMTLPixelFormatDescSRGB(R8Unorm_sRGB, Color8, All, R8Unorm); + addMTLPixelFormatDesc(R8Snorm, Color8, All); + addMTLPixelFormatDesc(R8Uint, Color8, RWCM); + addMTLPixelFormatDesc(R8Sint, Color8, RWCM); - // Ordinary 16-bit pixel formats. - addMTLPixelFormatDesc(R16Unorm, Color16, RFWCMB, All); - addMTLPixelFormatDesc(R16Snorm, Color16, RFWCMB, All); - addMTLPixelFormatDesc(R16Uint, Color16, RWCM, RWCM); - addMTLPixelFormatDesc(R16Sint, Color16, RWCM, RWCM); - addMTLPixelFormatDesc(R16Float, Color16, All, All); + // Ordinary 16-bit pixel formats + addMTLPixelFormatDesc(R16Unorm, Color16, RFWCMB); + addMTLPixelFormatDesc(R16Snorm, Color16, RFWCMB); + addMTLPixelFormatDesc(R16Uint, Color16, RWCM); + addMTLPixelFormatDesc(R16Sint, Color16, RWCM); + addMTLPixelFormatDesc(R16Float, Color16, All); - addMTLPixelFormatDesc(RG8Unorm, Color16, All, All); - addMTLPixelFormatDescSRGB(RG8Unorm_sRGB, Color16, RFCMRB, None, RG8Unorm); - addMTLPixelFormatDesc(RG8Snorm, Color16, RFWCMB, All); - addMTLPixelFormatDesc(RG8Uint, Color16, RWCM, RWCM); - addMTLPixelFormatDesc(RG8Sint, Color16, RWCM, RWCM); + addMTLPixelFormatDesc(RG8Unorm, Color16, All); + addMTLPixelFormatDescSRGB(RG8Unorm_sRGB, Color16, All, RG8Unorm); + addMTLPixelFormatDesc(RG8Snorm, Color16, All); + addMTLPixelFormatDesc(RG8Uint, Color16, RWCM); + addMTLPixelFormatDesc(RG8Sint, Color16, RWCM); - // Packed 16-bit pixel formats. - addMTLPixelFormatDesc(B5G6R5Unorm, Color16, RFCMRB, None); - addMTLPixelFormatDesc(A1BGR5Unorm, Color16, RFCMRB, None); - addMTLPixelFormatDesc(ABGR4Unorm, Color16, RFCMRB, None); - addMTLPixelFormatDesc(BGR5A1Unorm, Color16, RFCMRB, None); + // Packed 16-bit pixel formats + addMTLPixelFormatDesc(B5G6R5Unorm, Color16, RFCMRB); + addMTLPixelFormatDesc(A1BGR5Unorm, Color16, RFCMRB); + addMTLPixelFormatDesc(ABGR4Unorm, Color16, RFCMRB); + addMTLPixelFormatDesc(BGR5A1Unorm, Color16, RFCMRB); - // Ordinary 32-bit pixel formats. - addMTLPixelFormatDesc(R32Uint, Color32, RC, RWCM); - addMTLPixelFormatDesc(R32Sint, Color32, RC, RWCM); - addMTLPixelFormatDesc(R32Float, Color32, RCMB, All); + // Ordinary 32-bit pixel formats + addMTLPixelFormatDesc(R32Uint, Color32, RWC); + addMTLPixelFormatDesc(R32Sint, Color32, RWC); + addMTLPixelFormatDesc(R32Float, Color32, All); - addMTLPixelFormatDesc(RG16Unorm, Color32, RFWCMB, All); - addMTLPixelFormatDesc(RG16Snorm, Color32, RFWCMB, All); - addMTLPixelFormatDesc(RG16Uint, Color32, RWCM, RWCM); - addMTLPixelFormatDesc(RG16Sint, Color32, RWCM, RWCM); - addMTLPixelFormatDesc(RG16Float, Color32, All, All); + addMTLPixelFormatDesc(RG16Unorm, Color32, RFWCMB); + addMTLPixelFormatDesc(RG16Snorm, Color32, RFWCMB); + addMTLPixelFormatDesc(RG16Uint, Color32, RWCM); + addMTLPixelFormatDesc(RG16Sint, Color32, RWCM); + addMTLPixelFormatDesc(RG16Float, Color32, All); - addMTLPixelFormatDesc(RGBA8Unorm, Color32, All, All); - addMTLPixelFormatDescSRGB(RGBA8Unorm_sRGB, Color32, RFCMRB, RFCMRB, RGBA8Unorm); - addMTLPixelFormatDesc(RGBA8Snorm, Color32, RFWCMB, All); - addMTLPixelFormatDesc(RGBA8Uint, Color32, RWCM, RWCM); - addMTLPixelFormatDesc(RGBA8Sint, Color32, RWCM, RWCM); + addMTLPixelFormatDesc(RGBA8Unorm, Color32, All); + addMTLPixelFormatDescSRGB(RGBA8Unorm_sRGB, Color32, All, RGBA8Unorm); + addMTLPixelFormatDesc(RGBA8Snorm, Color32, All); + addMTLPixelFormatDesc(RGBA8Uint, Color32, RWCM); + addMTLPixelFormatDesc(RGBA8Sint, Color32, RWCM); - addMTLPixelFormatDesc(BGRA8Unorm, Color32, All, All); - addMTLPixelFormatDescSRGB(BGRA8Unorm_sRGB, Color32, RFCMRB, RFCMRB, BGRA8Unorm); + addMTLPixelFormatDesc(BGRA8Unorm, Color32, All); + addMTLPixelFormatDescSRGB(BGRA8Unorm_sRGB, Color32, All, BGRA8Unorm); - // Packed 32-bit pixel formats. - addMTLPixelFormatDesc(RGB10A2Unorm, Color32, RFCMRB, All); - addMTLPixelFormatDesc(RGB10A2Uint, Color32, RCM, RWCM); - addMTLPixelFormatDesc(RG11B10Float, Color32, RFCMRB, All); - addMTLPixelFormatDesc(RGB9E5Float, Color32, RFCMRB, RF); + // Packed 32-bit pixel formats + addMTLPixelFormatDesc(RGB10A2Unorm, Color32, All); + addMTLPixelFormatDesc(BGR10A2Unorm, Color32, All); + addMTLPixelFormatDesc(RGB10A2Uint, Color32, RWCM); + addMTLPixelFormatDesc(RG11B10Float, Color32, All); + addMTLPixelFormatDesc(RGB9E5Float, Color32, All); - // Ordinary 64-bit pixel formats. - addMTLPixelFormatDesc(RG32Uint, Color64, RC, RWCM); - addMTLPixelFormatDesc(RG32Sint, Color64, RC, RWCM); - addMTLPixelFormatDesc(RG32Float, Color64, RCB, All); + // Ordinary 64-bit pixel formats + addMTLPixelFormatDesc(RG32Uint, Color64, RWCM); + addMTLPixelFormatDesc(RG32Sint, Color64, RWCM); + addMTLPixelFormatDesc(RG32Float, Color64, All); - addMTLPixelFormatDesc(RGBA16Unorm, Color64, RFWCMB, All); - addMTLPixelFormatDesc(RGBA16Snorm, Color64, RFWCMB, All); - addMTLPixelFormatDesc(RGBA16Uint, Color64, RWCM, RWCM); - addMTLPixelFormatDesc(RGBA16Sint, Color64, RWCM, RWCM); - addMTLPixelFormatDesc(RGBA16Float, Color64, All, All); + addMTLPixelFormatDesc(RGBA16Unorm, Color64, RFWCMB); + addMTLPixelFormatDesc(RGBA16Snorm, Color64, RFWCMB); + addMTLPixelFormatDesc(RGBA16Uint, Color64, RWCM); + addMTLPixelFormatDesc(RGBA16Sint, Color64, RWCM); + addMTLPixelFormatDesc(RGBA16Float, Color64, All); - // Ordinary 128-bit pixel formats. - addMTLPixelFormatDesc(RGBA32Uint, Color128, RC, RWCM); - addMTLPixelFormatDesc(RGBA32Sint, Color128, RC, RWCM); - addMTLPixelFormatDesc(RGBA32Float, Color128, RC, All); + // Ordinary 128-bit pixel formats + addMTLPixelFormatDesc(RGBA32Uint, Color128, RWC); + addMTLPixelFormatDesc(RGBA32Sint, Color128, RWC); + addMTLPixelFormatDesc(RGBA32Float, Color128, All); - // Compressed pixel formats. - addMTLPixelFormatDesc(PVRTC_RGBA_2BPP, PVRTC_RGBA_2BPP, RF, None); - addMTLPixelFormatDescSRGB(PVRTC_RGBA_2BPP_sRGB, PVRTC_RGBA_2BPP, RF, None, PVRTC_RGBA_2BPP); - addMTLPixelFormatDesc(PVRTC_RGBA_4BPP, PVRTC_RGBA_4BPP, RF, None); - addMTLPixelFormatDescSRGB(PVRTC_RGBA_4BPP_sRGB, PVRTC_RGBA_4BPP, RF, None, PVRTC_RGBA_4BPP); + // Compressed pixel formats + addMTLPixelFormatDesc(PVRTC_RGBA_2BPP, PVRTC_RGBA_2BPP, RF); + addMTLPixelFormatDescSRGB(PVRTC_RGBA_2BPP_sRGB, PVRTC_RGBA_2BPP, RF, PVRTC_RGBA_2BPP); + addMTLPixelFormatDesc(PVRTC_RGBA_4BPP, PVRTC_RGBA_4BPP, RF); + addMTLPixelFormatDescSRGB(PVRTC_RGBA_4BPP_sRGB, PVRTC_RGBA_4BPP, RF, PVRTC_RGBA_4BPP); - addMTLPixelFormatDesc(ETC2_RGB8, ETC2_RGB8, RF, None); - addMTLPixelFormatDescSRGB(ETC2_RGB8_sRGB, ETC2_RGB8, RF, None, ETC2_RGB8); - addMTLPixelFormatDesc(ETC2_RGB8A1, ETC2_RGB8A1, RF, None); - addMTLPixelFormatDescSRGB(ETC2_RGB8A1_sRGB, ETC2_RGB8A1, RF, None, ETC2_RGB8A1); - addMTLPixelFormatDesc(EAC_RGBA8, EAC_RGBA8, RF, None); - addMTLPixelFormatDescSRGB(EAC_RGBA8_sRGB, EAC_RGBA8, RF, None, EAC_RGBA8); - addMTLPixelFormatDesc(EAC_R11Unorm, EAC_R11, RF, None); - addMTLPixelFormatDesc(EAC_R11Snorm, EAC_R11, RF, None); - addMTLPixelFormatDesc(EAC_RG11Unorm, EAC_RG11, RF, None); - addMTLPixelFormatDesc(EAC_RG11Snorm, EAC_RG11, RF, None); + addMTLPixelFormatDesc(ETC2_RGB8, ETC2_RGB8, RF); + addMTLPixelFormatDescSRGB(ETC2_RGB8_sRGB, ETC2_RGB8, RF, ETC2_RGB8); + addMTLPixelFormatDesc(ETC2_RGB8A1, ETC2_RGB8A1, RF); + addMTLPixelFormatDescSRGB(ETC2_RGB8A1_sRGB, ETC2_RGB8A1, RF, ETC2_RGB8A1); + addMTLPixelFormatDesc(EAC_RGBA8, EAC_RGBA8, RF); + addMTLPixelFormatDescSRGB(EAC_RGBA8_sRGB, EAC_RGBA8, RF, EAC_RGBA8); + addMTLPixelFormatDesc(EAC_R11Unorm, EAC_R11, RF); + addMTLPixelFormatDesc(EAC_R11Snorm, EAC_R11, RF); + addMTLPixelFormatDesc(EAC_RG11Unorm, EAC_RG11, RF); + addMTLPixelFormatDesc(EAC_RG11Snorm, EAC_RG11, RF); - addMTLPixelFormatDesc(ASTC_4x4_LDR, ASTC_4x4, None, None); - addMTLPixelFormatDescSRGB(ASTC_4x4_sRGB, ASTC_4x4, None, None, ASTC_4x4_LDR); - addMTLPixelFormatDesc(ASTC_4x4_HDR, ASTC_4x4, None, None); - addMTLPixelFormatDesc(ASTC_5x4_LDR, ASTC_5x4, None, None); - addMTLPixelFormatDescSRGB(ASTC_5x4_sRGB, ASTC_5x4, None, None, ASTC_5x4_LDR); - addMTLPixelFormatDesc(ASTC_5x4_HDR, ASTC_5x4, None, None); - addMTLPixelFormatDesc(ASTC_5x5_LDR, ASTC_5x5, None, None); - addMTLPixelFormatDescSRGB(ASTC_5x5_sRGB, ASTC_5x5, None, None, ASTC_5x5_LDR); - addMTLPixelFormatDesc(ASTC_5x5_HDR, ASTC_5x5, None, None); - addMTLPixelFormatDesc(ASTC_6x5_LDR, ASTC_6x5, None, None); - addMTLPixelFormatDescSRGB(ASTC_6x5_sRGB, ASTC_6x5, None, None, ASTC_6x5_LDR); - addMTLPixelFormatDesc(ASTC_6x5_HDR, ASTC_6x5, None, None); - addMTLPixelFormatDesc(ASTC_6x6_LDR, ASTC_6x6, None, None); - addMTLPixelFormatDescSRGB(ASTC_6x6_sRGB, ASTC_6x6, None, None, ASTC_6x6_LDR); - addMTLPixelFormatDesc(ASTC_6x6_HDR, ASTC_6x6, None, None); - addMTLPixelFormatDesc(ASTC_8x5_LDR, ASTC_8x5, None, None); - addMTLPixelFormatDescSRGB(ASTC_8x5_sRGB, ASTC_8x5, None, None, ASTC_8x5_LDR); - addMTLPixelFormatDesc(ASTC_8x5_HDR, ASTC_8x5, None, None); - addMTLPixelFormatDesc(ASTC_8x6_LDR, ASTC_8x6, None, None); - addMTLPixelFormatDescSRGB(ASTC_8x6_sRGB, ASTC_8x6, None, None, ASTC_8x6_LDR); - addMTLPixelFormatDesc(ASTC_8x6_HDR, ASTC_8x6, None, None); - addMTLPixelFormatDesc(ASTC_8x8_LDR, ASTC_8x8, None, None); - addMTLPixelFormatDescSRGB(ASTC_8x8_sRGB, ASTC_8x8, None, None, ASTC_8x8_LDR); - addMTLPixelFormatDesc(ASTC_8x8_HDR, ASTC_8x8, None, None); - addMTLPixelFormatDesc(ASTC_10x5_LDR, ASTC_10x5, None, None); - addMTLPixelFormatDescSRGB(ASTC_10x5_sRGB, ASTC_10x5, None, None, ASTC_10x5_LDR); - addMTLPixelFormatDesc(ASTC_10x5_HDR, ASTC_10x5, None, None); - addMTLPixelFormatDesc(ASTC_10x6_LDR, ASTC_10x6, None, None); - addMTLPixelFormatDescSRGB(ASTC_10x6_sRGB, ASTC_10x6, None, None, ASTC_10x6_LDR); - addMTLPixelFormatDesc(ASTC_10x6_HDR, ASTC_10x6, None, None); - addMTLPixelFormatDesc(ASTC_10x8_LDR, ASTC_10x8, None, None); - addMTLPixelFormatDescSRGB(ASTC_10x8_sRGB, ASTC_10x8, None, None, ASTC_10x8_LDR); - addMTLPixelFormatDesc(ASTC_10x8_HDR, ASTC_10x8, None, None); - addMTLPixelFormatDesc(ASTC_10x10_LDR, ASTC_10x10, None, None); - addMTLPixelFormatDescSRGB(ASTC_10x10_sRGB, ASTC_10x10, None, None, ASTC_10x10_LDR); - addMTLPixelFormatDesc(ASTC_10x10_HDR, ASTC_10x10, None, None); - addMTLPixelFormatDesc(ASTC_12x10_LDR, ASTC_12x10, None, None); - addMTLPixelFormatDescSRGB(ASTC_12x10_sRGB, ASTC_12x10, None, None, ASTC_12x10_LDR); - addMTLPixelFormatDesc(ASTC_12x10_HDR, ASTC_12x10, None, None); - addMTLPixelFormatDesc(ASTC_12x12_LDR, ASTC_12x12, None, None); - addMTLPixelFormatDescSRGB(ASTC_12x12_sRGB, ASTC_12x12, None, None, ASTC_12x12_LDR); - addMTLPixelFormatDesc(ASTC_12x12_HDR, ASTC_12x12, None, None); + addMTLPixelFormatDesc(ASTC_4x4_LDR, ASTC_4x4, RF); + addMTLPixelFormatDescSRGB(ASTC_4x4_sRGB, ASTC_4x4, RF, ASTC_4x4_LDR); + addMTLPixelFormatDesc(ASTC_4x4_HDR, ASTC_4x4, RF); + addMTLPixelFormatDesc(ASTC_5x4_LDR, ASTC_5x4, RF); + addMTLPixelFormatDescSRGB(ASTC_5x4_sRGB, ASTC_5x4, RF, ASTC_5x4_LDR); + addMTLPixelFormatDesc(ASTC_5x4_HDR, ASTC_5x4, RF); + addMTLPixelFormatDesc(ASTC_5x5_LDR, ASTC_5x5, RF); + addMTLPixelFormatDescSRGB(ASTC_5x5_sRGB, ASTC_5x5, RF, ASTC_5x5_LDR); + addMTLPixelFormatDesc(ASTC_5x5_HDR, ASTC_5x5, RF); + addMTLPixelFormatDesc(ASTC_6x5_LDR, ASTC_6x5, RF); + addMTLPixelFormatDescSRGB(ASTC_6x5_sRGB, ASTC_6x5, RF, ASTC_6x5_LDR); + addMTLPixelFormatDesc(ASTC_6x5_HDR, ASTC_6x5, RF); + addMTLPixelFormatDesc(ASTC_6x6_LDR, ASTC_6x6, RF); + addMTLPixelFormatDescSRGB(ASTC_6x6_sRGB, ASTC_6x6, RF, ASTC_6x6_LDR); + addMTLPixelFormatDesc(ASTC_6x6_HDR, ASTC_6x6, RF); + addMTLPixelFormatDesc(ASTC_8x5_LDR, ASTC_8x5, RF); + addMTLPixelFormatDescSRGB(ASTC_8x5_sRGB, ASTC_8x5, RF, ASTC_8x5_LDR); + addMTLPixelFormatDesc(ASTC_8x5_HDR, ASTC_8x5, RF); + addMTLPixelFormatDesc(ASTC_8x6_LDR, ASTC_8x6, RF); + addMTLPixelFormatDescSRGB(ASTC_8x6_sRGB, ASTC_8x6, RF, ASTC_8x6_LDR); + addMTLPixelFormatDesc(ASTC_8x6_HDR, ASTC_8x6, RF); + addMTLPixelFormatDesc(ASTC_8x8_LDR, ASTC_8x8, RF); + addMTLPixelFormatDescSRGB(ASTC_8x8_sRGB, ASTC_8x8, RF, ASTC_8x8_LDR); + addMTLPixelFormatDesc(ASTC_8x8_HDR, ASTC_8x8, RF); + addMTLPixelFormatDesc(ASTC_10x5_LDR, ASTC_10x5, RF); + addMTLPixelFormatDescSRGB(ASTC_10x5_sRGB, ASTC_10x5, RF, ASTC_10x5_LDR); + addMTLPixelFormatDesc(ASTC_10x5_HDR, ASTC_10x5, RF); + addMTLPixelFormatDesc(ASTC_10x6_LDR, ASTC_10x6, RF); + addMTLPixelFormatDescSRGB(ASTC_10x6_sRGB, ASTC_10x6, RF, ASTC_10x6_LDR); + addMTLPixelFormatDesc(ASTC_10x6_HDR, ASTC_10x6, RF); + addMTLPixelFormatDesc(ASTC_10x8_LDR, ASTC_10x8, RF); + addMTLPixelFormatDescSRGB(ASTC_10x8_sRGB, ASTC_10x8, RF, ASTC_10x8_LDR); + addMTLPixelFormatDesc(ASTC_10x8_HDR, ASTC_10x8, RF); + addMTLPixelFormatDesc(ASTC_10x10_LDR, ASTC_10x10, RF); + addMTLPixelFormatDescSRGB(ASTC_10x10_sRGB, ASTC_10x10, RF, ASTC_10x10_LDR); + addMTLPixelFormatDesc(ASTC_10x10_HDR, ASTC_10x10, RF); + addMTLPixelFormatDesc(ASTC_12x10_LDR, ASTC_12x10, RF); + addMTLPixelFormatDescSRGB(ASTC_12x10_sRGB, ASTC_12x10, RF, ASTC_12x10_LDR); + addMTLPixelFormatDesc(ASTC_12x10_HDR, ASTC_12x10, RF); + addMTLPixelFormatDesc(ASTC_12x12_LDR, ASTC_12x12, RF); + addMTLPixelFormatDescSRGB(ASTC_12x12_sRGB, ASTC_12x12, RF, ASTC_12x12_LDR); + addMTLPixelFormatDesc(ASTC_12x12_HDR, ASTC_12x12, RF); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" - addMTLPixelFormatDesc(BC1_RGBA, BC1_RGBA, RF, RF); - addMTLPixelFormatDescSRGB(BC1_RGBA_sRGB, BC1_RGBA, RF, RF, BC1_RGBA); - addMTLPixelFormatDesc(BC2_RGBA, BC2_RGBA, RF, RF); - addMTLPixelFormatDescSRGB(BC2_RGBA_sRGB, BC2_RGBA, RF, RF, BC2_RGBA); - addMTLPixelFormatDesc(BC3_RGBA, BC3_RGBA, RF, RF); - addMTLPixelFormatDescSRGB(BC3_RGBA_sRGB, BC3_RGBA, RF, RF, BC3_RGBA); - addMTLPixelFormatDesc(BC4_RUnorm, BC4_R, RF, RF); - addMTLPixelFormatDesc(BC4_RSnorm, BC4_R, RF, RF); - addMTLPixelFormatDesc(BC5_RGUnorm, BC5_RG, RF, RF); - addMTLPixelFormatDesc(BC5_RGSnorm, BC5_RG, RF, RF); - addMTLPixelFormatDesc(BC6H_RGBUfloat, BC6H_RGB, RF, RF); - addMTLPixelFormatDesc(BC6H_RGBFloat, BC6H_RGB, RF, RF); - addMTLPixelFormatDesc(BC7_RGBAUnorm, BC7_RGBA, RF, RF); - addMTLPixelFormatDescSRGB(BC7_RGBAUnorm_sRGB, BC7_RGBA, RF, RF, BC7_RGBAUnorm); + addMTLPixelFormatDesc(BC1_RGBA, BC1_RGBA, RF); + addMTLPixelFormatDescSRGB(BC1_RGBA_sRGB, BC1_RGBA, RF, BC1_RGBA); + addMTLPixelFormatDesc(BC2_RGBA, BC2_RGBA, RF); + addMTLPixelFormatDescSRGB(BC2_RGBA_sRGB, BC2_RGBA, RF, BC2_RGBA); + addMTLPixelFormatDesc(BC3_RGBA, BC3_RGBA, RF); + addMTLPixelFormatDescSRGB(BC3_RGBA_sRGB, BC3_RGBA, RF, BC3_RGBA); + addMTLPixelFormatDesc(BC4_RUnorm, BC4_R, RF); + addMTLPixelFormatDesc(BC4_RSnorm, BC4_R, RF); + addMTLPixelFormatDesc(BC5_RGUnorm, BC5_RG, RF); + addMTLPixelFormatDesc(BC5_RGSnorm, BC5_RG, RF); + addMTLPixelFormatDesc(BC6H_RGBUfloat, BC6H_RGB, RF); + addMTLPixelFormatDesc(BC6H_RGBFloat, BC6H_RGB, RF); + addMTLPixelFormatDesc(BC7_RGBAUnorm, BC7_RGBA, RF); + addMTLPixelFormatDescSRGB(BC7_RGBAUnorm_sRGB, BC7_RGBA, RF, BC7_RGBAUnorm); #pragma clang diagnostic pop - // YUV pixel formats. - addMTLPixelFormatDesc(GBGR422, None, RF, RF); - addMTLPixelFormatDesc(BGRG422, None, RF, RF); + // YUV pixel formats + addMTLPixelFormatDesc(GBGR422, None, RF); + addMTLPixelFormatDesc(BGRG422, None, RF); - // Extended range and wide color pixel formats. - addMTLPixelFormatDesc(BGRA10_XR, BGRA10_XR, None, None); - addMTLPixelFormatDescSRGB(BGRA10_XR_sRGB, BGRA10_XR, None, None, BGRA10_XR); - addMTLPixelFormatDesc(BGR10_XR, BGR10_XR, None, None); - addMTLPixelFormatDescSRGB(BGR10_XR_sRGB, BGR10_XR, None, None, BGR10_XR); - addMTLPixelFormatDesc(BGR10A2Unorm, Color32, None, None); + // Extended range and wide color pixel formats + addMTLPixelFormatDesc(BGRA10_XR, BGRA10_XR, All); + addMTLPixelFormatDescSRGB(BGRA10_XR_sRGB, BGRA10_XR, All, BGRA10_XR); + addMTLPixelFormatDesc(BGR10_XR, BGR10_XR, All); + addMTLPixelFormatDescSRGB(BGR10_XR_sRGB, BGR10_XR, All, BGR10_XR); - // Depth and stencil pixel formats. - addMTLPixelFormatDesc(Depth16Unorm, None, None, None); - addMTLPixelFormatDesc(Depth32Float, None, DRM, DRFMR); - addMTLPixelFormatDesc(Stencil8, None, DRM, DRMR); - addMTLPixelFormatDesc(Depth24Unorm_Stencil8, Depth24_Stencil8, None, None); - addMTLPixelFormatDesc(Depth32Float_Stencil8, Depth32_Stencil8, DRM, DRFMR); - addMTLPixelFormatDesc(X24_Stencil8, Depth24_Stencil8, None, DRMR); - addMTLPixelFormatDesc(X32_Stencil8, Depth32_Stencil8, DRM, DRMR); - - // When adding to this list, be sure to ensure _mtlPixelFormatCount is large enough for the format count. + // Depth and stencil pixel formats + addMTLPixelFormatDesc(Depth16Unorm, None, DRFMR); + addMTLPixelFormatDesc(Depth32Float, None, DRMR); + addMTLPixelFormatDesc(Stencil8, None, DRM); + addMTLPixelFormatDesc(Depth24Unorm_Stencil8, Depth24_Stencil8, None); + addMTLPixelFormatDesc(Depth32Float_Stencil8, Depth32_Stencil8, DRMR); + addMTLPixelFormatDesc(X24_Stencil8, Depth24_Stencil8, None); + addMTLPixelFormatDesc(X32_Stencil8, Depth32_Stencil8, DRM); } -#define addMTLVertexFormatDesc(MTL_VTX_FMT, IOS_CAPS, MACOS_CAPS) \ - CRASH_BAD_INDEX_MSG(fmtIdx, _mtlVertexFormatCount, "Attempting to describe too many MTLVertexFormats"); \ - _mtlVertexFormatDescriptions[fmtIdx++] = { .mtlVertexFormat = MTLVertexFormat##MTL_VTX_FMT, RD::DATA_FORMAT_MAX, select_platform_caps(kMTLFmtCaps##MACOS_CAPS, kMTLFmtCaps##IOS_CAPS), MTLViewClass::None, MTLPixelFormatInvalid, "MTLVertexFormat" #MTL_VTX_FMT } - -void PixelFormats::initMTLVertexFormatCapabilities() { - clear(_mtlVertexFormatDescriptions, _mtlVertexFormatCount); - - uint32_t fmtIdx = 0; - - // When adding to this list, be sure to ensure _mtlVertexFormatCount is large enough for the format count. - - // MTLVertexFormatInvalid must come first. - addMTLVertexFormatDesc(Invalid, None, None); - - addMTLVertexFormatDesc(UChar2Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Char2Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UChar2, Vertex, Vertex); - addMTLVertexFormatDesc(Char2, Vertex, Vertex); - - addMTLVertexFormatDesc(UChar3Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Char3Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UChar3, Vertex, Vertex); - addMTLVertexFormatDesc(Char3, Vertex, Vertex); - - addMTLVertexFormatDesc(UChar4Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Char4Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UChar4, Vertex, Vertex); - addMTLVertexFormatDesc(Char4, Vertex, Vertex); - - addMTLVertexFormatDesc(UInt1010102Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Int1010102Normalized, Vertex, Vertex); - - addMTLVertexFormatDesc(UShort2Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Short2Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UShort2, Vertex, Vertex); - addMTLVertexFormatDesc(Short2, Vertex, Vertex); - addMTLVertexFormatDesc(Half2, Vertex, Vertex); - - addMTLVertexFormatDesc(UShort3Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Short3Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UShort3, Vertex, Vertex); - addMTLVertexFormatDesc(Short3, Vertex, Vertex); - addMTLVertexFormatDesc(Half3, Vertex, Vertex); - - addMTLVertexFormatDesc(UShort4Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(Short4Normalized, Vertex, Vertex); - addMTLVertexFormatDesc(UShort4, Vertex, Vertex); - addMTLVertexFormatDesc(Short4, Vertex, Vertex); - addMTLVertexFormatDesc(Half4, Vertex, Vertex); - - addMTLVertexFormatDesc(UInt, Vertex, Vertex); - addMTLVertexFormatDesc(Int, Vertex, Vertex); - addMTLVertexFormatDesc(Float, Vertex, Vertex); - - addMTLVertexFormatDesc(UInt2, Vertex, Vertex); - addMTLVertexFormatDesc(Int2, Vertex, Vertex); - addMTLVertexFormatDesc(Float2, Vertex, Vertex); - - addMTLVertexFormatDesc(UInt3, Vertex, Vertex); - addMTLVertexFormatDesc(Int3, Vertex, Vertex); - addMTLVertexFormatDesc(Float3, Vertex, Vertex); - - addMTLVertexFormatDesc(UInt4, Vertex, Vertex); - addMTLVertexFormatDesc(Int4, Vertex, Vertex); - addMTLVertexFormatDesc(Float4, Vertex, Vertex); - - addMTLVertexFormatDesc(UCharNormalized, None, None); - addMTLVertexFormatDesc(CharNormalized, None, None); - addMTLVertexFormatDesc(UChar, None, None); - addMTLVertexFormatDesc(Char, None, None); - - addMTLVertexFormatDesc(UShortNormalized, None, None); - addMTLVertexFormatDesc(ShortNormalized, None, None); - addMTLVertexFormatDesc(UShort, None, None); - addMTLVertexFormatDesc(Short, None, None); - addMTLVertexFormatDesc(Half, None, None); - - addMTLVertexFormatDesc(UChar4Normalized_BGRA, None, None); - - // When adding to this list, be sure to ensure _mtlVertexFormatCount is large enough for the format count. +// If necessary, resize vector with empty elements. +void PixelFormats::addMTLVertexFormatDescImpl(MTLVertexFormat mtlVtxFmt, MTLFmtCaps vtxCap, const char *name) { + if (mtlVtxFmt >= _mtl_vertex_format_descs.size()) { + _mtl_vertex_format_descs.resize(mtlVtxFmt + 1); + } + _mtl_vertex_format_descs[mtlVtxFmt] = { .mtlVertexFormat = mtlVtxFmt, RD::DATA_FORMAT_MAX, vtxCap, MTLViewClass::None, MTLPixelFormatInvalid, name }; } -void PixelFormats::buildMTLFormatMaps() { - // Set all MTLPixelFormats and MTLVertexFormats to undefined/invalid. - clear(_mtlFormatDescIndicesByMTLPixelFormatsCore, _mtlPixelFormatCoreCount); - clear(_mtlFormatDescIndicesByMTLVertexFormats, _mtlVertexFormatCount); - - // Build lookup table for MTLPixelFormat specs. - // For most Metal format values, which are small and consecutive, use a simple lookup array. - // For outlier format values, which can be large, use a map. - for (uint32_t fmtIdx = 0; fmtIdx < _mtlPixelFormatCount; fmtIdx++) { - MTLPixelFormat fmt = _mtlPixelFormatDescriptions[fmtIdx].mtlPixelFormat; - if (fmt) { - if (fmt < _mtlPixelFormatCoreCount) { - _mtlFormatDescIndicesByMTLPixelFormatsCore[fmt] = fmtIdx; - } else { - _mtlFormatDescIndicesByMTLPixelFormatsExt[fmt] = fmtIdx; - } - } +// Check mtlVtx exists on platform, to avoid overwriting the MTLVertexFormatInvalid entry. +#define addMTLVertexFormatDesc(mtlVtx) \ + if (MTLVertexFormat##mtlVtx) { \ + addMTLVertexFormatDescImpl(MTLVertexFormat##mtlVtx, kMTLFmtCapsVertex, "MTLVertexFormat" #mtlVtx); \ } - // Build lookup table for MTLVertexFormat specs. - for (uint32_t fmtIdx = 0; fmtIdx < _mtlVertexFormatCount; fmtIdx++) { - MTLVertexFormat fmt = _mtlVertexFormatDescriptions[fmtIdx].mtlVertexFormat; - if (fmt) { - _mtlFormatDescIndicesByMTLVertexFormats[fmt] = fmtIdx; +void PixelFormats::initMTLVertexFormatCapabilities(const MetalFeatures &p_feat) { + _mtl_vertex_format_descs.resize(MTLVertexFormatHalf + 3); + // MTLVertexFormatInvalid must come first. Use addMTLVertexFormatDescImpl to avoid guard code. + addMTLVertexFormatDescImpl(MTLVertexFormatInvalid, kMTLFmtCapsNone, "MTLVertexFormatInvalid"); + + addMTLVertexFormatDesc(UChar2Normalized); + addMTLVertexFormatDesc(Char2Normalized); + addMTLVertexFormatDesc(UChar2); + addMTLVertexFormatDesc(Char2); + + addMTLVertexFormatDesc(UChar3Normalized); + addMTLVertexFormatDesc(Char3Normalized); + addMTLVertexFormatDesc(UChar3); + addMTLVertexFormatDesc(Char3); + + addMTLVertexFormatDesc(UChar4Normalized); + addMTLVertexFormatDesc(Char4Normalized); + addMTLVertexFormatDesc(UChar4); + addMTLVertexFormatDesc(Char4); + + addMTLVertexFormatDesc(UInt1010102Normalized); + addMTLVertexFormatDesc(Int1010102Normalized); + + addMTLVertexFormatDesc(UShort2Normalized); + addMTLVertexFormatDesc(Short2Normalized); + addMTLVertexFormatDesc(UShort2); + addMTLVertexFormatDesc(Short2); + addMTLVertexFormatDesc(Half2); + + addMTLVertexFormatDesc(UShort3Normalized); + addMTLVertexFormatDesc(Short3Normalized); + addMTLVertexFormatDesc(UShort3); + addMTLVertexFormatDesc(Short3); + addMTLVertexFormatDesc(Half3); + + addMTLVertexFormatDesc(UShort4Normalized); + addMTLVertexFormatDesc(Short4Normalized); + addMTLVertexFormatDesc(UShort4); + addMTLVertexFormatDesc(Short4); + addMTLVertexFormatDesc(Half4); + + addMTLVertexFormatDesc(UInt); + addMTLVertexFormatDesc(Int); + addMTLVertexFormatDesc(Float); + + addMTLVertexFormatDesc(UInt2); + addMTLVertexFormatDesc(Int2); + addMTLVertexFormatDesc(Float2); + + addMTLVertexFormatDesc(UInt3); + addMTLVertexFormatDesc(Int3); + addMTLVertexFormatDesc(Float3); + + addMTLVertexFormatDesc(UInt4); + addMTLVertexFormatDesc(Int4); + addMTLVertexFormatDesc(Float4); + + addMTLVertexFormatDesc(UCharNormalized); + addMTLVertexFormatDesc(CharNormalized); + addMTLVertexFormatDesc(UChar); + addMTLVertexFormatDesc(Char); + + addMTLVertexFormatDesc(UShortNormalized); + addMTLVertexFormatDesc(ShortNormalized); + addMTLVertexFormatDesc(UShort); + addMTLVertexFormatDesc(Short); + addMTLVertexFormatDesc(Half); + + addMTLVertexFormatDesc(UChar4Normalized_BGRA); + + if (@available(macos 14.0, ios 17.0, tvos 17.0, *)) { + if (p_feat.highestFamily >= MTLGPUFamilyApple5) { + addMTLVertexFormatDesc(FloatRG11B10); + addMTLVertexFormatDesc(FloatRGB9E5); } } } -// If the device supports the feature set, add additional capabilities to a MTLPixelFormat. -void PixelFormats::addMTLPixelFormatCapabilities(id p_device, - MTLFeatureSet p_feature_set, - MTLPixelFormat p_format, - MTLFmtCaps p_caps) { - if ([p_device supportsFeatureSet:p_feature_set]) { - flags::set(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps); +// Return a reference to the format capabilities, so the caller can manipulate them. +// Check mtlPixFmt exists on platform, to avoid overwriting the MTLPixelFormatInvalid entry. +// When returning the dummy, reset it on each access because it can be written to by caller. +MTLFmtCaps &PixelFormats::getMTLPixelFormatCapsIf(MTLPixelFormat mtlPixFmt, bool cond) { + static MTLFmtCaps dummyFmtCaps; + if (mtlPixFmt && cond) { + return getMTLPixelFormatDesc(mtlPixFmt).mtlFmtCaps; + } else { + dummyFmtCaps = kMTLFmtCapsNone; + return dummyFmtCaps; } } -// If the device supports the GPU family, add additional capabilities to a MTLPixelFormat. -void PixelFormats::addMTLPixelFormatCapabilities(id p_device, - MTLGPUFamily p_family, - MTLPixelFormat p_format, - MTLFmtCaps p_caps) { - if ([p_device supportsFamily:p_family]) { - flags::set(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps); - } -} +#define setMTLPixFmtCapsIf(cond, mtlFmt, caps) getMTLPixelFormatCapsIf(MTLPixelFormat##mtlFmt, cond) = kMTLFmtCaps##caps; +#define setMTLPixFmtCapsIfGPU(gpuFam, mtlFmt, caps) setMTLPixFmtCapsIf(gpuCaps.supports##gpuFam, mtlFmt, caps) -// Disable capability flags in the Metal pixel format. -void PixelFormats::disableMTLPixelFormatCapabilities(MTLPixelFormat p_format, - MTLFmtCaps p_caps) { - flags::clear(getMTLPixelFormatDesc(p_format).mtlFmtCaps, p_caps); -} +#define enableMTLPixFmtCapsIf(cond, mtlFmt, caps) flags::set(getMTLPixelFormatCapsIf(MTLPixelFormat##mtlFmt, cond), kMTLFmtCaps##caps); +#define enableMTLPixFmtCapsIfGPU(gpuFam, mtlFmt, caps) enableMTLPixFmtCapsIf(p_feat.highestFamily >= MTLGPUFamily##gpuFam, mtlFmt, caps) -void PixelFormats::disableAllMTLPixelFormatCapabilities(MTLPixelFormat p_format) { - getMTLPixelFormatDesc(p_format).mtlFmtCaps = kMTLFmtCapsNone; -} +#define disableMTLPixFmtCapsIf(cond, mtlFmt, caps) flags::clear(getMTLPixelFormatCapsIf(MTLPixelFormat##mtlFmt, cond), kMTLFmtCaps##caps); -// If the device supports the feature set, add additional capabilities to a MTLVertexFormat. -void PixelFormats::addMTLVertexFormatCapabilities(id p_device, - MTLFeatureSet p_feature_set, - MTLVertexFormat p_format, - MTLFmtCaps p_caps) { - if ([p_device supportsFeatureSet:p_feature_set]) { - flags::set(getMTLVertexFormatDesc(p_format).mtlFmtCaps, p_caps); - } -} +// Modifies the format capability tables based on the capabilities of the specific MTLDevice. +void PixelFormats::modifyMTLFormatCapabilities(const MetalFeatures &p_feat) { + bool noVulkanSupport = false; // Indicated supported in Metal but not Vulkan or SPIR-V. + bool notMac = !p_feat.supportsMac; + bool iosOnly1 = notMac && p_feat.highestFamily < MTLGPUFamilyApple2; + bool iosOnly2 = notMac && p_feat.highestFamily < MTLGPUFamilyApple3; + bool iosOnly6 = notMac && p_feat.highestFamily < MTLGPUFamilyApple7; + bool iosOnly8 = notMac && p_feat.highestFamily < MTLGPUFamilyApple9; -void PixelFormats::modifyMTLFormatCapabilities() { - modifyMTLFormatCapabilities(device); -} + setMTLPixFmtCapsIf(iosOnly2, A8Unorm, RF); + setMTLPixFmtCapsIf(iosOnly1, R8Unorm_sRGB, RFCMRB); + setMTLPixFmtCapsIf(iosOnly1, R8Snorm, RFWCMB); -// If the supportsBCTextureCompression query is available, use it. -bool supports_bc_texture_compression(id p_device) { -#if (TARGET_OS_OSX || TARGET_OS_IOS && __IPHONE_OS_VERSION_MAX_ALLOWED >= 160400) - if (@available(macOS 11.0, iOS 16.4, *)) { - return p_device.supportsBCTextureCompression; - } -#endif - return false; -} + setMTLPixFmtCapsIf(iosOnly1, RG8Unorm_sRGB, RFCMRB); + setMTLPixFmtCapsIf(iosOnly1, RG8Snorm, RFWCMB); -#define addFeatSetMTLPixFmtCaps(FEAT_SET, MTL_FMT, CAPS) \ - addMTLPixelFormatCapabilities(p_device, MTLFeatureSet_##FEAT_SET, MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS) + enableMTLPixFmtCapsIfGPU(Apple6, R32Uint, Atomic); + enableMTLPixFmtCapsIfGPU(Apple6, R32Sint, Atomic); -#define addFeatSetMTLVtxFmtCaps(FEAT_SET, MTL_FMT, CAPS) \ - addMTLVertexFormatCapabilities(p_device, MTLFeatureSet_##FEAT_SET, MTLVertexFormat##MTL_FMT, kMTLFmtCaps##CAPS) + setMTLPixFmtCapsIf(iosOnly8, R32Float, RWCMB); -#define addGPUMTLPixFmtCaps(GPU_FAM, MTL_FMT, CAPS) \ - addMTLPixelFormatCapabilities(p_device, MTLGPUFamily##GPU_FAM, MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS) + setMTLPixFmtCapsIf(iosOnly1, RGBA8Unorm_sRGB, RFCMRB); + setMTLPixFmtCapsIf(iosOnly1, RGBA8Snorm, RFWCMB); + setMTLPixFmtCapsIf(iosOnly1, BGRA8Unorm_sRGB, RFCMRB); -#define disableAllMTLPixFmtCaps(MTL_FMT) \ - disableAllMTLPixelFormatCapabilities(MTLPixelFormat##MTL_FMT) + setMTLPixFmtCapsIf(iosOnly2, RGB10A2Unorm, RFCMRB); + setMTLPixFmtCapsIf(iosOnly2, RGB10A2Uint, RCM); + setMTLPixFmtCapsIf(iosOnly2, RG11B10Float, RFCMRB); + setMTLPixFmtCapsIf(iosOnly2, RGB9E5Float, RFCMRB); -#define disableMTLPixFmtCaps(MTL_FMT, CAPS) \ - disableMTLPixelFormatCapabilities(MTLPixelFormat##MTL_FMT, kMTLFmtCaps##CAPS) + // Blending is actually supported for RGB9E5Float, but format channels cannot + // be individually write-enabled during blending on macOS. Disabling blending + // on macOS is the least-intrusive way to handle this in a Vulkan-friendly way. + disableMTLPixFmtCapsIf(p_feat.supportsMac, RGB9E5Float, Blend); + + // RGB9E5Float cannot be used as a render target on the simulator. + disableMTLPixFmtCapsIf(TARGET_OS_SIMULATOR, RGB9E5Float, ColorAtt); + + setMTLPixFmtCapsIf(iosOnly6, RG32Uint, RWC); + setMTLPixFmtCapsIf(iosOnly6, RG32Sint, RWC); + + // Metal supports reading both R&G into as one 64-bit atomic operation, but Vulkan and SPIR-V do not. + // Including this here so we remember to update this if support is added to Vulkan in the future. + bool atomic64 = noVulkanSupport && (p_feat.highestFamily >= MTLGPUFamilyApple9 || (p_feat.highestFamily >= MTLGPUFamilyApple8 && p_feat.supportsMac)); + enableMTLPixFmtCapsIf(atomic64, RG32Uint, Atomic); + enableMTLPixFmtCapsIf(atomic64, RG32Sint, Atomic); + + setMTLPixFmtCapsIf(iosOnly8, RG32Float, RWCMB); + setMTLPixFmtCapsIf(iosOnly6, RG32Float, RWCB); + + setMTLPixFmtCapsIf(iosOnly8, RGBA32Float, RWCM); + setMTLPixFmtCapsIf(iosOnly6, RGBA32Float, RWC); + + bool msaa32 = p_feat.supports32BitMSAA; + enableMTLPixFmtCapsIf(msaa32, R32Uint, MSAA); + enableMTLPixFmtCapsIf(msaa32, R32Sint, MSAA); + enableMTLPixFmtCapsIf(msaa32, R32Float, Resolve); + enableMTLPixFmtCapsIf(msaa32, RG32Uint, MSAA); + enableMTLPixFmtCapsIf(msaa32, RG32Sint, MSAA); + enableMTLPixFmtCapsIf(msaa32, RG32Float, Resolve); + enableMTLPixFmtCapsIf(msaa32, RGBA32Uint, MSAA); + enableMTLPixFmtCapsIf(msaa32, RGBA32Sint, MSAA); + enableMTLPixFmtCapsIf(msaa32, RGBA32Float, Resolve); + + bool floatFB = p_feat.supports32BitFloatFiltering; + enableMTLPixFmtCapsIf(floatFB, R32Float, Filter); + enableMTLPixFmtCapsIf(floatFB, RG32Float, Filter); + enableMTLPixFmtCapsIf(floatFB, RGBA32Float, Filter); + enableMTLPixFmtCapsIf(floatFB, RGBA32Float, Blend); // Undocumented by confirmed through testing. + + bool noHDR_ASTC = p_feat.highestFamily < MTLGPUFamilyApple6; + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_4x4_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_5x4_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_5x5_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_6x5_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_6x6_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_8x5_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_8x6_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_8x8_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_10x5_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_10x6_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_10x8_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_10x10_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_12x10_HDR, None); + setMTLPixFmtCapsIf(noHDR_ASTC, ASTC_12x12_HDR, None); -void PixelFormats::modifyMTLFormatCapabilities(id p_device) { - if (!supports_bc_texture_compression(p_device)) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" - disableAllMTLPixFmtCaps(BC1_RGBA); - disableAllMTLPixFmtCaps(BC1_RGBA_sRGB); - disableAllMTLPixFmtCaps(BC2_RGBA); - disableAllMTLPixFmtCaps(BC2_RGBA_sRGB); - disableAllMTLPixFmtCaps(BC3_RGBA); - disableAllMTLPixFmtCaps(BC3_RGBA_sRGB); - disableAllMTLPixFmtCaps(BC4_RUnorm); - disableAllMTLPixFmtCaps(BC4_RSnorm); - disableAllMTLPixFmtCaps(BC5_RGUnorm); - disableAllMTLPixFmtCaps(BC5_RGSnorm); - disableAllMTLPixFmtCaps(BC6H_RGBUfloat); - disableAllMTLPixFmtCaps(BC6H_RGBFloat); - disableAllMTLPixFmtCaps(BC7_RGBAUnorm); - disableAllMTLPixFmtCaps(BC7_RGBAUnorm_sRGB); + bool noBC = !p_feat.supportsBCTextureCompression; + setMTLPixFmtCapsIf(noBC, BC1_RGBA, None); + setMTLPixFmtCapsIf(noBC, BC1_RGBA_sRGB, None); + setMTLPixFmtCapsIf(noBC, BC2_RGBA, None); + setMTLPixFmtCapsIf(noBC, BC2_RGBA_sRGB, None); + setMTLPixFmtCapsIf(noBC, BC3_RGBA, None); + setMTLPixFmtCapsIf(noBC, BC3_RGBA_sRGB, None); + setMTLPixFmtCapsIf(noBC, BC4_RUnorm, None); + setMTLPixFmtCapsIf(noBC, BC4_RSnorm, None); + setMTLPixFmtCapsIf(noBC, BC5_RGUnorm, None); + setMTLPixFmtCapsIf(noBC, BC5_RGSnorm, None); + setMTLPixFmtCapsIf(noBC, BC6H_RGBUfloat, None); + setMTLPixFmtCapsIf(noBC, BC6H_RGBFloat, None); + setMTLPixFmtCapsIf(noBC, BC7_RGBAUnorm, None); + setMTLPixFmtCapsIf(noBC, BC7_RGBAUnorm_sRGB, None); #pragma clang diagnostic pop - } - if (!p_device.supports32BitMSAA) { - disableMTLPixFmtCaps(R32Uint, MSAA); - disableMTLPixFmtCaps(R32Uint, Resolve); - disableMTLPixFmtCaps(R32Sint, MSAA); - disableMTLPixFmtCaps(R32Sint, Resolve); - disableMTLPixFmtCaps(R32Float, MSAA); - disableMTLPixFmtCaps(R32Float, Resolve); - disableMTLPixFmtCaps(RG32Uint, MSAA); - disableMTLPixFmtCaps(RG32Uint, Resolve); - disableMTLPixFmtCaps(RG32Sint, MSAA); - disableMTLPixFmtCaps(RG32Sint, Resolve); - disableMTLPixFmtCaps(RG32Float, MSAA); - disableMTLPixFmtCaps(RG32Float, Resolve); - disableMTLPixFmtCaps(RGBA32Uint, MSAA); - disableMTLPixFmtCaps(RGBA32Uint, Resolve); - disableMTLPixFmtCaps(RGBA32Sint, MSAA); - disableMTLPixFmtCaps(RGBA32Sint, Resolve); - disableMTLPixFmtCaps(RGBA32Float, MSAA); - disableMTLPixFmtCaps(RGBA32Float, Resolve); - } + setMTLPixFmtCapsIf(iosOnly2, BGRA10_XR, None); + setMTLPixFmtCapsIf(iosOnly2, BGRA10_XR_sRGB, None); + setMTLPixFmtCapsIf(iosOnly2, BGR10_XR, None); + setMTLPixFmtCapsIf(iosOnly2, BGR10_XR_sRGB, None); - if (!p_device.supports32BitFloatFiltering) { - disableMTLPixFmtCaps(R32Float, Filter); - disableMTLPixFmtCaps(RG32Float, Filter); - disableMTLPixFmtCaps(RGBA32Float, Filter); - } + setMTLPixFmtCapsIf(iosOnly2, Depth16Unorm, DRFM); + setMTLPixFmtCapsIf(iosOnly2, Depth32Float, DRM); -#if TARGET_OS_OSX - addGPUMTLPixFmtCaps(Apple1, R32Uint, Atomic); - addGPUMTLPixFmtCaps(Apple1, R32Sint, Atomic); - - if (p_device.isDepth24Stencil8PixelFormatSupported) { - addGPUMTLPixFmtCaps(Apple1, Depth24Unorm_Stencil8, DRFMR); - } - - addFeatSetMTLPixFmtCaps(macOS_GPUFamily1_v2, Depth16Unorm, DRFMR); - - addFeatSetMTLPixFmtCaps(macOS_GPUFamily1_v3, BGR10A2Unorm, RFCMRB); - - addGPUMTLPixFmtCaps(Apple5, R8Unorm_sRGB, All); - - addGPUMTLPixFmtCaps(Apple5, RG8Unorm_sRGB, All); - - addGPUMTLPixFmtCaps(Apple5, B5G6R5Unorm, RFCMRB); - addGPUMTLPixFmtCaps(Apple5, A1BGR5Unorm, RFCMRB); - addGPUMTLPixFmtCaps(Apple5, ABGR4Unorm, RFCMRB); - addGPUMTLPixFmtCaps(Apple5, BGR5A1Unorm, RFCMRB); - - addGPUMTLPixFmtCaps(Apple5, RGBA8Unorm_sRGB, All); - addGPUMTLPixFmtCaps(Apple5, BGRA8Unorm_sRGB, All); - - // Blending is actually supported for this format, but format channels cannot be individually write-enabled during blending. - // Disabling blending is the least-intrusive way to handle this in a Redot-friendly way. - addGPUMTLPixFmtCaps(Apple5, RGB9E5Float, All); - disableMTLPixFmtCaps(RGB9E5Float, Blend); - - addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_2BPP, RF); - addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_2BPP_sRGB, RF); - addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_4BPP, RF); - addGPUMTLPixFmtCaps(Apple5, PVRTC_RGBA_4BPP_sRGB, RF); - - addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8, RF); - addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8_sRGB, RF); - addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8A1, RF); - addGPUMTLPixFmtCaps(Apple5, ETC2_RGB8A1_sRGB, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_RGBA8, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_RGBA8_sRGB, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_R11Unorm, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_R11Snorm, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_RG11Unorm, RF); - addGPUMTLPixFmtCaps(Apple5, EAC_RG11Snorm, RF); - - addGPUMTLPixFmtCaps(Apple5, ASTC_4x4_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_4x4_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_4x4_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_5x4_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_5x4_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_5x4_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_5x5_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_5x5_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_5x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_6x5_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_6x5_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_6x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_6x6_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_6x6_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_6x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x5_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x5_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x6_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x6_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x8_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_8x8_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x8_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x5_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x5_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x6_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x6_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x8_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x8_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x8_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x10_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_10x10_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x10_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_12x10_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_12x10_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_12x10_HDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_12x12_LDR, RF); - addGPUMTLPixFmtCaps(Apple5, ASTC_12x12_sRGB, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_12x12_HDR, RF); - - addGPUMTLPixFmtCaps(Apple5, BGRA10_XR, All); - addGPUMTLPixFmtCaps(Apple5, BGRA10_XR_sRGB, All); - addGPUMTLPixFmtCaps(Apple5, BGR10_XR, All); - addGPUMTLPixFmtCaps(Apple5, BGR10_XR_sRGB, All); - - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UCharNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, CharNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UChar, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Char, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UShortNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, ShortNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UShort, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Short, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, Half, Vertex); - addFeatSetMTLVtxFmtCaps(macOS_GPUFamily1_v3, UChar4Normalized_BGRA, Vertex); -#endif - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, R8Unorm_sRGB, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, R8Unorm_sRGB, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, R8Snorm, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, RG8Unorm_sRGB, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RG8Unorm_sRGB, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, RG8Snorm, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Uint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Uint, Atomic); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Sint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Sint, Atomic); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, R32Float, RWCMB); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, RGBA8Unorm_sRGB, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGBA8Unorm_sRGB, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, RGBA8Snorm, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v3, BGRA8Unorm_sRGB, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, BGRA8Unorm_sRGB, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB10A2Unorm, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB10A2Uint, RWCM); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RG11B10Float, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, RGB9E5Float, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Uint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Sint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RG32Float, RWCB); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Uint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Sint, RWC); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v2, RGBA32Float, RWC); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_4x4_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_4x4_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x4_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x4_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x5_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_5x5_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x5_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x5_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x6_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_6x6_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x5_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x5_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x6_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x6_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x8_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_8x8_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x5_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x5_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x6_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x6_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x8_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x8_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x10_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_10x10_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x10_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x10_sRGB, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x12_LDR, RF); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily2_v1, ASTC_12x12_sRGB, RF); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Depth32Float, DRMR); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Depth32Float_Stencil8, DRMR); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v1, Stencil8, DRMR); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGRA10_XR, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGRA10_XR_sRGB, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGR10_XR, All); - addFeatSetMTLPixFmtCaps(iOS_GPUFamily3_v2, BGR10_XR_sRGB, All); - - addFeatSetMTLPixFmtCaps(iOS_GPUFamily1_v4, BGR10A2Unorm, All); - - addGPUMTLPixFmtCaps(Apple6, ASTC_4x4_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_5x4_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_5x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_6x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_6x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_8x8_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x5_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x6_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x8_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_10x10_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_12x10_HDR, RF); - addGPUMTLPixFmtCaps(Apple6, ASTC_12x12_HDR, RF); - - addGPUMTLPixFmtCaps(Apple1, Depth16Unorm, DRFM); - addGPUMTLPixFmtCaps(Apple3, Depth16Unorm, DRFMR); - - // Vertex formats. - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UCharNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, CharNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UChar, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Char, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UShortNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, ShortNormalized, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UShort, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Short, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, Half, Vertex); - addFeatSetMTLVtxFmtCaps(iOS_GPUFamily1_v4, UChar4Normalized_BGRA, Vertex); - -// Disable for iOS simulator last. -#if TARGET_OS_SIMULATOR - if (![p_device supportsFamily:MTLGPUFamilyApple5]) { - disableAllMTLPixFmtCaps(R8Unorm_sRGB); - disableAllMTLPixFmtCaps(RG8Unorm_sRGB); - disableAllMTLPixFmtCaps(B5G6R5Unorm); - disableAllMTLPixFmtCaps(A1BGR5Unorm); - disableAllMTLPixFmtCaps(ABGR4Unorm); - disableAllMTLPixFmtCaps(BGR5A1Unorm); - - disableAllMTLPixFmtCaps(BGRA10_XR); - disableAllMTLPixFmtCaps(BGRA10_XR_sRGB); - disableAllMTLPixFmtCaps(BGR10_XR); - disableAllMTLPixFmtCaps(BGR10_XR_sRGB); - - disableAllMTLPixFmtCaps(GBGR422); - disableAllMTLPixFmtCaps(BGRG422); - - disableMTLPixFmtCaps(RGB9E5Float, ColorAtt); - - disableMTLPixFmtCaps(R8Unorm_sRGB, Write); - disableMTLPixFmtCaps(RG8Unorm_sRGB, Write); - disableMTLPixFmtCaps(RGBA8Unorm_sRGB, Write); - disableMTLPixFmtCaps(BGRA8Unorm_sRGB, Write); - disableMTLPixFmtCaps(PVRTC_RGBA_2BPP_sRGB, Write); - disableMTLPixFmtCaps(PVRTC_RGBA_4BPP_sRGB, Write); - disableMTLPixFmtCaps(ETC2_RGB8_sRGB, Write); - disableMTLPixFmtCaps(ETC2_RGB8A1_sRGB, Write); - disableMTLPixFmtCaps(EAC_RGBA8_sRGB, Write); - disableMTLPixFmtCaps(ASTC_4x4_sRGB, Write); - disableMTLPixFmtCaps(ASTC_5x4_sRGB, Write); - disableMTLPixFmtCaps(ASTC_5x5_sRGB, Write); - disableMTLPixFmtCaps(ASTC_6x5_sRGB, Write); - disableMTLPixFmtCaps(ASTC_6x6_sRGB, Write); - disableMTLPixFmtCaps(ASTC_8x5_sRGB, Write); - disableMTLPixFmtCaps(ASTC_8x6_sRGB, Write); - disableMTLPixFmtCaps(ASTC_8x8_sRGB, Write); - disableMTLPixFmtCaps(ASTC_10x5_sRGB, Write); - disableMTLPixFmtCaps(ASTC_10x6_sRGB, Write); - disableMTLPixFmtCaps(ASTC_10x8_sRGB, Write); - disableMTLPixFmtCaps(ASTC_10x10_sRGB, Write); - disableMTLPixFmtCaps(ASTC_12x10_sRGB, Write); - disableMTLPixFmtCaps(ASTC_12x12_sRGB, Write); - } -#endif -#endif + setMTLPixFmtCapsIf(!p_feat.supportsDepth24Stencil8, Depth24Unorm_Stencil8, None); + setMTLPixFmtCapsIf(iosOnly2, Depth32Float_Stencil8, DRM); } -#undef addFeatSetMTLPixFmtCaps -#undef addGPUOSMTLPixFmtCaps -#undef disableMTLPixFmtCaps -#undef disableAllMTLPixFmtCaps -#undef addFeatSetMTLVtxFmtCaps - // Populates the DataFormat lookup maps and connects Redot and Metal pixel formats to one-another. void PixelFormats::buildDFFormatMaps() { - // Iterate through the DataFormat descriptions, populate the lookup maps and back pointers, - // and validate the Metal formats for the platform and OS. - for (uint32_t fmtIdx = 0; fmtIdx < RD::DATA_FORMAT_MAX; fmtIdx++) { - DataFormatDesc &dfDesc = _dataFormatDescriptions[fmtIdx]; - DataFormat dfFmt = dfDesc.dataFormat; - if (dfFmt != RD::DATA_FORMAT_MAX) { - // Populate the back reference from the Metal formats to the Redot format. - // Validate the corresponding Metal formats for the platform, and clear them - // in the Redot format if not supported. - if (dfDesc.mtlPixelFormat) { - MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormat); - if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) { - mtlDesc.dataFormat = dfFmt; - } - if (!mtlDesc.isSupported()) { - dfDesc.mtlPixelFormat = MTLPixelFormatInvalid; - } + for (DataFormatDesc &dfDesc : _data_format_descs) { + // Populate the back reference from the Metal formats to the Godot format. + // Validate the corresponding Metal formats for the platform, and clear them + // in the Godot format if not supported. + if (dfDesc.mtlPixelFormat) { + MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormat); + if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) { + mtlDesc.dataFormat = dfDesc.dataFormat; } - if (dfDesc.mtlPixelFormatSubstitute) { - MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormatSubstitute); - if (!mtlDesc.isSupported()) { - dfDesc.mtlPixelFormatSubstitute = MTLPixelFormatInvalid; - } + if (!mtlDesc.isSupported()) { + dfDesc.mtlPixelFormat = MTLPixelFormatInvalid; } - if (dfDesc.mtlVertexFormat) { - MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormat); - if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) { - mtlDesc.dataFormat = dfFmt; - } - if (!mtlDesc.isSupported()) { - dfDesc.mtlVertexFormat = MTLVertexFormatInvalid; - } + } + if (dfDesc.mtlPixelFormatSubstitute) { + MTLFormatDesc &mtlDesc = getMTLPixelFormatDesc(dfDesc.mtlPixelFormatSubstitute); + if (!mtlDesc.isSupported()) { + dfDesc.mtlPixelFormatSubstitute = MTLPixelFormatInvalid; } - if (dfDesc.mtlVertexFormatSubstitute) { - MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormatSubstitute); - if (!mtlDesc.isSupported()) { - dfDesc.mtlVertexFormatSubstitute = MTLVertexFormatInvalid; - } + } + if (dfDesc.mtlVertexFormat) { + MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormat); + if (mtlDesc.dataFormat == RD::DATA_FORMAT_MAX) { + mtlDesc.dataFormat = dfDesc.dataFormat; + } + if (!mtlDesc.isSupported()) { + dfDesc.mtlVertexFormat = MTLVertexFormatInvalid; + } + } + if (dfDesc.mtlVertexFormatSubstitute) { + MTLFormatDesc &mtlDesc = getMTLVertexFormatDesc(dfDesc.mtlVertexFormatSubstitute); + if (!mtlDesc.isSupported()) { + dfDesc.mtlVertexFormatSubstitute = MTLVertexFormatInvalid; } } } diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h index bc01975d12..980d593b54 100644 --- a/drivers/metal/rendering_context_driver_metal.h +++ b/drivers/metal/rendering_context_driver_metal.h @@ -58,7 +58,7 @@ class MDCommandBuffer; class PixelFormats; class MDResourceCache; -class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingContextDriverMetal : public RenderingContextDriver { protected: #ifdef __OBJC__ id metal_device = nullptr; @@ -96,7 +96,7 @@ public: #endif }; - class API_AVAILABLE(macos(11.0), ios(14.0)) Surface { + class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) Surface { protected: #ifdef __OBJC__ id device; diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm index d7c0ffbdd4..2816b45ead 100644 --- a/drivers/metal/rendering_context_driver_metal.mm +++ b/drivers/metal/rendering_context_driver_metal.mm @@ -81,7 +81,7 @@ void RenderingContextDriverMetal::driver_free(RenderingDeviceDriver *p_driver) { memdelete(p_driver); } -class API_AVAILABLE(macos(11.0), ios(14.0)) SurfaceLayer : public RenderingContextDriverMetal::Surface { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) SurfaceLayer : public RenderingContextDriverMetal::Surface { CAMetalLayer *__unsafe_unretained layer = nil; LocalVector frame_buffers; LocalVector> drawables; diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h index cf07ec50a2..bc5b20c7bd 100644 --- a/drivers/metal/rendering_device_driver_metal.h +++ b/drivers/metal/rendering_device_driver_metal.h @@ -38,7 +38,6 @@ #import "servers/rendering/rendering_device_driver.h" #import -#import #import #ifdef DEBUG_ENABLED @@ -49,7 +48,7 @@ class RenderingContextDriverMetal; -class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingDeviceDriverMetal : public RenderingDeviceDriver { +class API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) RenderingDeviceDriverMetal : public RenderingDeviceDriver { friend struct ShaderCacheEntry; template @@ -108,6 +107,7 @@ public: virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) override final; virtual uint8_t *buffer_map(BufferID p_buffer) override final; virtual void buffer_unmap(BufferID p_buffer) override final; + virtual uint64_t buffer_get_device_address(BufferID p_buffer) override final; #pragma mark - Texture diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index b1f8ab60b7..50e8903dfc 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -64,6 +64,7 @@ #import #import #import +#import #import #import @@ -160,6 +161,15 @@ void RenderingDeviceDriverMetal::buffer_unmap(BufferID p_buffer) { // Nothing to do. } +uint64_t RenderingDeviceDriverMetal::buffer_get_device_address(BufferID p_buffer) { + if (@available(iOS 16.0, macOS 13.0, *)) { + id obj = rid::get(p_buffer); + return obj.gpuAddress; + } else { + return 0; + } +} + #pragma mark - Texture #pragma mark - Format Conversions @@ -301,8 +311,10 @@ RDD::TextureID RenderingDeviceDriverMetal::texture_create(const TextureFormat &p desc.usage |= MTLTextureUsageShaderWrite; } - if (p_format.usage_bits & TEXTURE_USAGE_STORAGE_ATOMIC_BIT) { - desc.usage |= MTLTextureUsageShaderWrite; + if (@available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) { + if (format_caps & kMTLFmtCapsAtomic) { + desc.usage |= MTLTextureUsageShaderAtomic; + } } bool can_be_attachment = flags::any(format_caps, (kMTLFmtCapsColorAtt | kMTLFmtCapsDSAtt)); @@ -691,7 +703,7 @@ static const MTLBlendOperation BLEND_OPERATIONS[RD::BLEND_OP_MAX] = { MTLBlendOperationMax, }; -static const API_AVAILABLE(macos(11.0), ios(14.0)) MTLSamplerAddressMode ADDRESS_MODES[RD::SAMPLER_REPEAT_MODE_MAX] = { +static const API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MTLSamplerAddressMode ADDRESS_MODES[RD::SAMPLER_REPEAT_MODE_MAX] = { MTLSamplerAddressModeRepeat, MTLSamplerAddressModeMirrorRepeat, MTLSamplerAddressModeClampToEdge, @@ -699,7 +711,7 @@ static const API_AVAILABLE(macos(11.0), ios(14.0)) MTLSamplerAddressMode ADDRESS MTLSamplerAddressModeMirrorClampToEdge, }; -static const API_AVAILABLE(macos(11.0), ios(14.0)) MTLSamplerBorderColor SAMPLER_BORDER_COLORS[RD::SAMPLER_BORDER_COLOR_MAX] = { +static const API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) MTLSamplerBorderColor SAMPLER_BORDER_COLORS[RD::SAMPLER_BORDER_COLOR_MAX] = { MTLSamplerBorderColorTransparentBlack, MTLSamplerBorderColorTransparentBlack, MTLSamplerBorderColorOpaqueBlack, @@ -734,7 +746,7 @@ RDD::SamplerID RenderingDeviceDriverMetal::sampler_create(const SamplerState &p_ desc.normalizedCoordinates = !p_state.unnormalized_uvw; if (p_state.lod_bias != 0.0) { - WARN_VERBOSE("Metal does not support LOD bias for samplers."); + WARN_PRINT_ONCE("Metal does not support LOD bias for samplers."); } id obj = [device newSamplerStateWithDescriptor:desc]; @@ -1427,7 +1439,7 @@ struct SpecializationConstantData { } }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformData { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) UniformData { RD::UniformType type = RD::UniformType::UNIFORM_TYPE_MAX; uint32_t binding = UINT32_MAX; bool writable = false; @@ -1483,7 +1495,7 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformData { } }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) UniformSetData { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) UniformSetData { uint32_t index = UINT32_MAX; LocalVector uniforms; @@ -1541,7 +1553,7 @@ struct PushConstantData { } }; -struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData { +struct API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) ShaderBinaryData { enum Flags : uint32_t { NONE = 0, NEEDS_VIEW_MASK_BUFFER = 1 << 0, @@ -2034,7 +2046,7 @@ Vector RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec msl_options.platform = CompilerMSL::Options::iOS; #endif -#if TARGET_OS_IOS +#if TARGET_OS_IPHONE msl_options.ios_use_simdgroup_functions = (*device_properties).features.simdPermute; #endif @@ -2909,7 +2921,7 @@ void RenderingDeviceDriverMetal::command_clear_color_texture(CommandBufferID p_c } } -API_AVAILABLE(macos(11.0), ios(14.0)) +API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) bool isArrayTexture(MTLTextureType p_type) { return (p_type == MTLTextureType3D || p_type == MTLTextureType2DArray || @@ -4025,6 +4037,8 @@ bool RenderingDeviceDriverMetal::has_feature(Features p_feature) { return false; case SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS: return true; + case SUPPORTS_BUFFER_DEVICE_ADDRESS: + return false; case SUPPORTS_METALFX_SPATIAL: return device_properties->features.metal_fx_spatial; case SUPPORTS_METALFX_TEMPORAL: @@ -4125,7 +4139,7 @@ Error RenderingDeviceDriverMetal::initialize(uint32_t p_device_index, uint32_t p pipeline_cache_id = "metal-driver-" + get_api_version(); device_properties = memnew(MetalDeviceProperties(device)); - pixel_formats = memnew(PixelFormats(device)); + pixel_formats = memnew(PixelFormats(device, device_properties->features)); if (device_properties->features.layeredRendering) { multiview_capabilities.is_supported = true; multiview_capabilities.max_view_count = device_properties->limits.maxViewports; diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 9cbcd62067..cce6d08a3e 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -515,6 +515,7 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() { _register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false); _register_requested_device_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false); _register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false); + _register_requested_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, false); if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { _register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true); @@ -732,6 +733,7 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() { void *next_features = nullptr; VkPhysicalDeviceVulkan12Features device_features_vk_1_2 = {}; VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_features = {}; + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR buffer_device_address_features = {}; VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features = {}; VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {}; VkPhysicalDeviceMultiviewFeatures multiview_features = {}; @@ -742,10 +744,17 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() { device_features_vk_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; device_features_vk_1_2.pNext = next_features; next_features = &device_features_vk_1_2; - } else if (enabled_device_extension_names.has(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME)) { - shader_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR; - shader_features.pNext = next_features; - next_features = &shader_features; + } else { + if (enabled_device_extension_names.has(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME)) { + shader_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR; + shader_features.pNext = next_features; + next_features = &shader_features; + } + if (enabled_device_extension_names.has(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME)) { + buffer_device_address_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR; + buffer_device_address_features.pNext = next_features; + next_features = &buffer_device_address_features; + } } if (enabled_device_extension_names.has(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) { @@ -785,11 +794,17 @@ Error RenderingDeviceDriverVulkan::_check_device_capabilities() { shader_capabilities.shader_float16_is_supported = device_features_vk_1_2.shaderFloat16; shader_capabilities.shader_int8_is_supported = device_features_vk_1_2.shaderInt8; } + if (enabled_device_extension_names.has(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME)) { + buffer_device_address_support = device_features_vk_1_2.bufferDeviceAddress; + } } else { if (enabled_device_extension_names.has(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME)) { shader_capabilities.shader_float16_is_supported = shader_features.shaderFloat16; shader_capabilities.shader_int8_is_supported = shader_features.shaderInt8; } + if (enabled_device_extension_names.has(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME)) { + buffer_device_address_support = buffer_device_address_features.bufferDeviceAddress; + } } if (enabled_device_extension_names.has(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) { @@ -973,6 +988,14 @@ Error RenderingDeviceDriverVulkan::_initialize_device(const LocalVector p_usage, MemoryAllocationType p_allocation_type) { VkBufferCreateInfo create_info = {}; @@ -1590,6 +1617,15 @@ void RenderingDeviceDriverVulkan::buffer_unmap(BufferID p_buffer) { vmaUnmapMemory(allocator, buf_info->allocation.handle); } +uint64_t RenderingDeviceDriverVulkan::buffer_get_device_address(BufferID p_buffer) { + const BufferInfo *buf_info = (const BufferInfo *)p_buffer.id; + VkBufferDeviceAddressInfo address_info = {}; + address_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + address_info.pNext = nullptr; + address_info.buffer = buf_info->vk_buffer; + return vkGetBufferDeviceAddress(vk_device, &address_info); +} + /*****************/ /**** TEXTURE ****/ /*****************/ @@ -5876,6 +5912,8 @@ bool RenderingDeviceDriverVulkan::has_feature(Features p_feature) { return vrs_capabilities.attachment_vrs_supported && physical_device_features.shaderStorageImageExtendedFormats; case SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS: return true; + case SUPPORTS_BUFFER_DEVICE_ADDRESS: + return buffer_device_address_support; default: return false; } diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h index a740a17dde..cfdd2679be 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.h +++ b/drivers/vulkan/rendering_device_driver_vulkan.h @@ -140,6 +140,7 @@ class RenderingDeviceDriverVulkan : public RenderingDeviceDriver { VRSCapabilities vrs_capabilities; ShaderCapabilities shader_capabilities; StorageBufferCapabilities storage_buffer_capabilities; + bool buffer_device_address_support = false; bool pipeline_cache_control_support = false; bool device_fault_support = false; #if defined(VK_TRACK_DEVICE_MEMORY) @@ -206,6 +207,7 @@ public: virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) override final; virtual uint8_t *buffer_map(BufferID p_buffer) override final; virtual void buffer_unmap(BufferID p_buffer) override final; + virtual uint64_t buffer_get_device_address(BufferID p_buffer) override final; /*****************/ /**** TEXTURE ****/ diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 036b011132..669355b2b9 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -125,6 +125,8 @@ const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); static bool default_output_device_changed = false; static bool default_input_device_changed = false; +static int output_reinit_countdown = 0; +static int input_reinit_countdown = 0; // Silence warning due to a COM API weirdness (GH-35194). #if defined(__GNUC__) && !defined(__clang__) @@ -136,6 +138,8 @@ class CMMNotificationClient : public IMMNotificationClient { LONG _cRef = 1; public: + ComPtr enumerator = nullptr; + CMMNotificationClient() {} virtual ~CMMNotificationClient() {} @@ -201,6 +205,9 @@ public: static CMMNotificationClient notif_client; Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_input, bool p_reinit, bool p_no_audio_client_3) { + // This function can be called recursively, so clean up before starting: + audio_device_finish(p_device); + WAVEFORMATEX *pwfex; ComPtr enumerator = nullptr; ComPtr output_device = nullptr; @@ -227,21 +234,25 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i ComPtr tmp_device = nullptr; hr = devices->Item(i, &tmp_device); - ERR_BREAK(hr != S_OK); + ERR_BREAK_MSG(hr != S_OK, "Cannot get devices item."); ComPtr props = nullptr; hr = tmp_device->OpenPropertyStore(STGM_READ, &props); - ERR_BREAK(hr != S_OK); + ERR_BREAK_MSG(hr != S_OK, "Cannot open property store."); PROPVARIANT propvar; PropVariantInit(&propvar); hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar); - ERR_BREAK(hr != S_OK); + ERR_BREAK_MSG(hr != S_OK, "Cannot get value."); if (p_device->device_name == String(propvar.pwszVal)) { hr = tmp_device->GetId(&strId); - ERR_BREAK(hr != S_OK); + if (unlikely(hr != S_OK)) { + PropVariantClear(&propvar); + ERR_PRINT("Cannot get device ID string."); + break; + } found = true; } @@ -272,9 +283,14 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); } + if (notif_client.enumerator != nullptr) { + notif_client.enumerator->UnregisterEndpointNotificationCallback(¬if_client); + notif_client.enumerator = nullptr; + } hr = enumerator->RegisterEndpointNotificationCallback(¬if_client); - - if (hr != S_OK) { + if (hr == S_OK) { + notif_client.enumerator = enumerator; + } else { ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error"); } @@ -319,6 +335,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i hr = p_device->audio_client->GetMixFormat(&pwfex); ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); + // From this point onward, CoTaskMemFree(pwfex) must be called before returning or pwfex will leak! print_verbose("WASAPI: wFormatTag = " + itos(pwfex->wFormatTag)); print_verbose("WASAPI: nChannels = " + itos(pwfex->nChannels)); @@ -342,6 +359,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i print_verbose("WASAPI: closest->cbSize = " + itos(closest->cbSize)); WARN_PRINT("WASAPI: Using closest match instead"); + CoTaskMemFree(pwfex); pwfex = closest; } } @@ -361,11 +379,13 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i p_device->format_tag = WAVE_FORMAT_IEEE_FLOAT; } else { ERR_PRINT("WASAPI: Format not supported"); + CoTaskMemFree(pwfex); ERR_FAIL_V(ERR_CANT_OPEN); } } else { if (p_device->format_tag != WAVE_FORMAT_PCM && p_device->format_tag != WAVE_FORMAT_IEEE_FLOAT) { ERR_PRINT("WASAPI: Format not supported"); + CoTaskMemFree(pwfex); ERR_FAIL_V(ERR_CANT_OPEN); } } @@ -378,10 +398,28 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8); } hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_input ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr); - ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + "."); + + if (p_reinit) { + // In case we're trying to re-initialize the device, prevent throwing this error on the console, + // otherwise if there is currently no device available this will spam the console. + if (hr != S_OK) { + print_verbose("WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + "."); + CoTaskMemFree(pwfex); + return ERR_CANT_OPEN; + } + } else { + if (unlikely(hr != S_OK)) { + CoTaskMemFree(pwfex); + ERR_FAIL_V_MSG(ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + "."); + } + } + UINT32 max_frames; hr = p_device->audio_client->GetBufferSize(&max_frames); - ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); + if (unlikely(hr != S_OK)) { + CoTaskMemFree(pwfex); + ERR_FAIL_V(ERR_CANT_OPEN); + } // Due to WASAPI Shared Mode we have no control of the buffer size if (!p_input) { @@ -455,7 +493,10 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i } else { hr = p_device->audio_client->GetService(IID_IAudioRenderClient, (void **)&p_device->render_client); } - ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN); + if (unlikely(hr != S_OK)) { + CoTaskMemFree(pwfex); + ERR_FAIL_V(ERR_CANT_OPEN); + } // Free memory CoTaskMemFree(pwfex); @@ -466,6 +507,11 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i Error AudioDriverWASAPI::init_output_device(bool p_reinit) { Error err = audio_device_init(&audio_output, false, p_reinit); if (err != OK) { + // We've tried to init the device, but have failed. Time to clean up. + Error finish_err = finish_output_device(); + if (finish_err != OK) { + ERR_PRINT("WASAPI: finish_output_device error after failed output audio_device_init"); + } return err; } @@ -506,6 +552,11 @@ Error AudioDriverWASAPI::init_output_device(bool p_reinit) { Error AudioDriverWASAPI::init_input_device(bool p_reinit) { Error err = audio_device_init(&audio_input, true, p_reinit); if (err != OK) { + // We've tried to init the device, but have failed. Time to clean up. + Error finish_err = finish_input_device(); + if (finish_err != OK) { + ERR_PRINT("WASAPI: finish_input_device error after failed input audio_device_init"); + } return err; } @@ -828,9 +879,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { } if (!ad->audio_output.audio_client) { - Error err = ad->init_output_device(true); - if (err == OK) { - ad->start(); + if (output_reinit_countdown < 1) { + Error err = ad->init_output_device(true); + if (err == OK) { + ad->start(); + } else { + output_reinit_countdown = 1000; + } + } else { + output_reinit_countdown--; } avail_frames = 0; @@ -901,9 +958,15 @@ void AudioDriverWASAPI::thread_func(void *p_udata) { } if (!ad->audio_input.audio_client) { - Error err = ad->init_input_device(true); - if (err == OK) { - ad->input_start(); + if (input_reinit_countdown < 1) { + Error err = ad->init_input_device(true); + if (err == OK) { + ad->input_start(); + } else { + input_reinit_countdown = 1000; + } + } else { + input_reinit_countdown--; } } } diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 6d6e67edc9..baf6ad07f2 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -404,13 +404,8 @@ bool FileAccessWindows::file_exists(const String &p_name) { } String filename = fix_path(p_name); - FILE *g = _wfsopen((LPCWSTR)(filename.utf16().get_data()), L"rb", _SH_DENYNO); - if (g == nullptr) { - return false; - } else { - fclose(g); - return true; - } + DWORD file_attr = GetFileAttributesW((LPCWSTR)(filename.utf16().get_data())); + return (file_attr != INVALID_FILE_ATTRIBUTES) && !(file_attr & FILE_ATTRIBUTE_DIRECTORY); } uint64_t FileAccessWindows::_get_modified_time(const String &p_file) { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index cf6bfbd323..e2f1da55b4 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -52,6 +52,7 @@ #include "scene/animation/tween.h" #include "scene/gui/check_box.h" #include "scene/gui/color_picker.h" +#include "scene/gui/flow_container.h" #include "scene/gui/grid_container.h" #include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" @@ -7608,31 +7609,34 @@ AnimationTrackEditor::AnimationTrackEditor() { track_vbox->set_h_size_flags(SIZE_EXPAND_FILL); scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); - HBoxContainer *bottom_hb = memnew(HBoxContainer); - add_child(bottom_hb); + HFlowContainer *bottom_hf = memnew(HFlowContainer); + add_child(bottom_hf); imported_anim_warning = memnew(Button); imported_anim_warning->hide(); imported_anim_warning->set_text(TTR("Imported Scene")); imported_anim_warning->set_tooltip_text(TTR("Warning: Editing imported animation")); imported_anim_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_imported_anim_warning)); - bottom_hb->add_child(imported_anim_warning); + bottom_hf->add_child(imported_anim_warning); dummy_player_warning = memnew(Button); dummy_player_warning->hide(); dummy_player_warning->set_text(TTR("Dummy Player")); dummy_player_warning->set_tooltip_text(TTR("Warning: Editing dummy AnimationPlayer")); dummy_player_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_dummy_player_warning)); - bottom_hb->add_child(dummy_player_warning); + bottom_hf->add_child(dummy_player_warning); inactive_player_warning = memnew(Button); inactive_player_warning->hide(); inactive_player_warning->set_text(TTR("Inactive Player")); inactive_player_warning->set_tooltip_text(TTR("Warning: AnimationPlayer is inactive")); inactive_player_warning->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_show_inactive_player_warning)); - bottom_hb->add_child(inactive_player_warning); + bottom_hf->add_child(inactive_player_warning); - bottom_hb->add_spacer(); + Control *spacer = memnew(Control); + spacer->set_mouse_filter(MOUSE_FILTER_PASS); + spacer->set_h_size_flags(SIZE_EXPAND_FILL); + bottom_hf->add_child(spacer); bezier_edit_icon = memnew(Button); bezier_edit_icon->set_flat(true); @@ -7641,7 +7645,7 @@ AnimationTrackEditor::AnimationTrackEditor() { bezier_edit_icon->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit)); bezier_edit_icon->set_tooltip_text(TTR("Toggle between the bezier curve editor and track editor.")); - bottom_hb->add_child(bezier_edit_icon); + bottom_hf->add_child(bezier_edit_icon); selected_filter = memnew(Button); selected_filter->set_flat(true); @@ -7649,7 +7653,7 @@ AnimationTrackEditor::AnimationTrackEditor() { selected_filter->set_toggle_mode(true); selected_filter->set_tooltip_text(TTR("Only show tracks from nodes selected in tree.")); - bottom_hb->add_child(selected_filter); + bottom_hf->add_child(selected_filter); view_group = memnew(Button); view_group->set_flat(true); @@ -7657,12 +7661,12 @@ AnimationTrackEditor::AnimationTrackEditor() { view_group->set_toggle_mode(true); view_group->set_tooltip_text(TTR("Group tracks by node or display them as plain list.")); - bottom_hb->add_child(view_group); - bottom_hb->add_child(memnew(VSeparator)); + bottom_hf->add_child(view_group); + bottom_hf->add_child(memnew(VSeparator)); snap_timeline = memnew(Button); snap_timeline->set_flat(true); - bottom_hb->add_child(snap_timeline); + bottom_hf->add_child(snap_timeline); snap_timeline->set_disabled(true); snap_timeline->set_toggle_mode(true); snap_timeline->set_pressed(false); @@ -7670,7 +7674,7 @@ AnimationTrackEditor::AnimationTrackEditor() { snap_keys = memnew(Button); snap_keys->set_flat(true); - bottom_hb->add_child(snap_keys); + bottom_hf->add_child(snap_keys); snap_keys->set_disabled(true); snap_keys->set_toggle_mode(true); snap_keys->set_pressed(true); @@ -7678,7 +7682,7 @@ AnimationTrackEditor::AnimationTrackEditor() { fps_compat = memnew(Button); fps_compat->set_flat(true); - bottom_hb->add_child(fps_compat); + bottom_hf->add_child(fps_compat); fps_compat->set_disabled(true); fps_compat->set_toggle_mode(true); fps_compat->set_pressed(true); @@ -7686,7 +7690,7 @@ AnimationTrackEditor::AnimationTrackEditor() { fps_compat->connect(SceneStringName(toggled), callable_mp(this, &AnimationTrackEditor::_update_fps_compat_mode)); nearest_fps_label = memnew(Label); - bottom_hb->add_child(nearest_fps_label); + bottom_hf->add_child(nearest_fps_label); step = memnew(EditorSpinSlider); step->set_min(0); @@ -7695,22 +7699,23 @@ AnimationTrackEditor::AnimationTrackEditor() { step->set_hide_slider(true); step->set_custom_minimum_size(Size2(100, 0) * EDSCALE); step->set_tooltip_text(TTR("Animation step value.")); - bottom_hb->add_child(step); + bottom_hf->add_child(step); step->connect(SceneStringName(value_changed), callable_mp(this, &AnimationTrackEditor::_update_step)); step->set_read_only(true); snap_mode = memnew(OptionButton); snap_mode->add_item(TTR("Seconds")); snap_mode->add_item(TTR("FPS")); - bottom_hb->add_child(snap_mode); + bottom_hf->add_child(snap_mode); snap_mode->connect(SceneStringName(item_selected), callable_mp(this, &AnimationTrackEditor::_snap_mode_changed)); snap_mode->set_disabled(true); - bottom_hb->add_child(memnew(VSeparator)); + bottom_hf->add_child(memnew(VSeparator)); + HBoxContainer *zoom_hb = memnew(HBoxContainer); zoom_icon = memnew(TextureRect); zoom_icon->set_v_size_flags(SIZE_SHRINK_CENTER); - bottom_hb->add_child(zoom_icon); + zoom_hb->add_child(zoom_icon); zoom = memnew(HSlider); zoom->set_step(0.01); zoom->set_min(0.0); @@ -7718,7 +7723,8 @@ AnimationTrackEditor::AnimationTrackEditor() { zoom->set_value(1.0); zoom->set_custom_minimum_size(Size2(200, 0) * EDSCALE); zoom->set_v_size_flags(SIZE_SHRINK_CENTER); - bottom_hb->add_child(zoom); + zoom_hb->add_child(zoom); + bottom_hf->add_child(zoom_hb); timeline->set_zoom(zoom); ED_SHORTCUT("animation_editor/auto_fit", TTRC("Fit to panel"), KeyModifierMask::ALT | Key::F); @@ -7727,14 +7733,14 @@ AnimationTrackEditor::AnimationTrackEditor() { auto_fit->set_flat(true); auto_fit->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit)); auto_fit->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); - bottom_hb->add_child(auto_fit); + bottom_hf->add_child(auto_fit); auto_fit_bezier = memnew(Button); auto_fit_bezier->set_flat(true); auto_fit_bezier->set_visible(false); auto_fit_bezier->connect(SceneStringName(pressed), callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier)); auto_fit_bezier->set_shortcut(ED_GET_SHORTCUT("animation_editor/auto_fit")); - bottom_hb->add_child(auto_fit_bezier); + bottom_hf->add_child(auto_fit_bezier); edit = memnew(MenuButton); edit->set_shortcut_context(this); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 8ea5b713ae..dbfedb9fc2 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1069,7 +1069,7 @@ Ref CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo tex = get_editor_theme_icon(SNAME("NodePath")); break; case ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE: - tex = get_editor_theme_icon(SNAME("Variant")); + tex = get_editor_theme_icon(SNAME("LocalVariable")); break; case ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT: tex = get_editor_theme_icon(SNAME("MemberConstant")); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index c61e64eb12..f08750177b 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1749,7 +1749,7 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir, if (!p_dir) { return; //none } - p_file->store_line("::" + p_dir->get_path() + "::" + String::num(p_dir->modified_time)); + p_file->store_line("::" + p_dir->get_path() + "::" + String::num_int64(p_dir->modified_time)); for (int i = 0; i < p_dir->files.size(); i++) { const EditorFileSystemDirectory::FileInfo *file_info = p_dir->files[i]; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 1b3ca2f72f..3050b282d2 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -48,6 +48,8 @@ #include "editor/editor_property_name_processor.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" +#include "editor/filesystem_dock.h" +#include "editor/gui/editor_toaster.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/gui/line_edit.h" @@ -386,6 +388,7 @@ void EditorHelp::_class_desc_select(const String &p_select) { OS::get_singleton()->shell_open(p_select); } else if (p_select.begins_with("^")) { // Copy button. DisplayServer::get_singleton()->clipboard_set(p_select.substr(1)); + EditorToaster::get_singleton()->popup_str(TTR("Code snippet copied to clipboard."), EditorToaster::SEVERITY_INFO); } } @@ -3870,6 +3873,37 @@ void EditorHelpBit::_update_labels() { _add_text_to_rt(help_data.description.replace("", comment_color.to_html()), content, this, symbol_class_name); } + if (!help_data.resource_path.is_empty()) { + if (has_prev_text) { + content->add_newline(); + content->add_newline(); + } + has_prev_text = true; + + const String ext = help_data.resource_path.get_extension(); + const bool is_dir = ext.is_empty(); + const bool is_valid = is_dir || EditorFileSystem::get_singleton()->get_valid_extensions().has(ext); + if (!is_dir && is_valid) { + content->push_meta("open-res:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER); + content->add_image(get_editor_theme_icon(SNAME("Load"))); + content->add_text(nbsp + TTR("Open")); + content->pop(); // meta + content->add_newline(); + } + + if (is_valid) { + content->push_meta("show:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER); + content->add_image(get_editor_theme_icon(SNAME("Filesystem"))); + content->add_text(nbsp + TTR("Show in FileSystem")); + content->pop(); // meta + } else { + content->push_meta("open-file:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER); + content->add_image(get_editor_theme_icon(SNAME("Filesystem"))); + content->add_text(nbsp + TTR("Open in File Manager")); + content->pop(); // meta + } + } + if (is_inside_tree()) { update_content_height(); } @@ -3951,6 +3985,17 @@ void EditorHelpBit::_meta_clicked(const String &p_select) { } else { _go_to_help(topic + ":" + symbol_class_name + ":" + link); } + } else if (p_select.begins_with("open-file:")) { + String path = ProjectSettings::get_singleton()->globalize_path(p_select.trim_prefix("open-file:")); + OS::get_singleton()->shell_show_in_file_manager(path, true); + } else if (p_select.begins_with("open-res:")) { + if (help_data.doc_type.type == "PackedScene") { + EditorNode::get_singleton()->load_scene(p_select.trim_prefix("open-res:")); + } else { + EditorNode::get_singleton()->load_resource(p_select.trim_prefix("open-res:")); + } + } else if (p_select.begins_with("show:")) { + FileSystemDock::get_singleton()->navigate_to_path(p_select.trim_prefix("show:")); } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) { OS::get_singleton()->shell_open(p_select); } else if (p_select.begins_with("^")) { // Copy button. @@ -4074,6 +4119,46 @@ void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologu help_data.doc_type.enumeration = item_data.get("enumeration", ""); help_data.doc_type.is_bitfield = item_data.get("is_bitfield", false); help_data.value = item_data.get("value", ""); + } else if (item_type == "resource") { + String path = item_name.simplify_path(); + const bool is_uid = path.begins_with("uid://"); + if (is_uid) { + if (ResourceUID::get_singleton()->has_id(ResourceUID::get_singleton()->text_to_id(path))) { + path = ResourceUID::uid_to_path(path); + } else { + path = ""; + } + } + help_data.resource_path = path; + + Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(path)) { + help_data.doc_type.type = ResourceLoader::get_resource_type(path); + if (help_data.doc_type.type.is_empty()) { + const Vector textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false); + symbol_type = textfile_ext.has(path.get_extension()) ? TTR("TextFile") : TTR("File"); + } else { + symbol_type = TTR("Resource"); + symbol_hint = SYMBOL_HINT_ASSIGNABLE; + if (is_uid) { + help_data.description = vformat("%s: [color=]%s[/color]", TTR("Path"), path); + } + } + symbol_name = path.get_file(); + } else if (!is_uid && da->dir_exists(path)) { + symbol_type = TTR("Directory"); + symbol_name = path; + } else { + help_data.resource_path = ""; + symbol_name = ""; + if (is_uid) { + symbol_type = TTR("Invalid UID"); + help_data.description = "[color=][i]" + TTR("This UID does not point to any valid Resource.") + "[/i][/color]"; + } else { + symbol_type = TTR("Invalid path"); + help_data.description = "[color=][i]" + TTR("This path does not exist.") + "[/i][/color]"; + } + } } else { ERR_FAIL_MSG("Invalid doc id: Unknown item type " + item_type.quote() + "."); } @@ -4091,7 +4176,7 @@ void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologu } } - if (help_data.description.is_empty()) { + if (help_data.description.is_empty() && item_type != "resource") { help_data.description = "[color=][i]" + TTR("No description available.") + "[/i][/color]"; } diff --git a/editor/editor_help.h b/editor/editor_help.h index 227d9e5c73..056472a689 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -279,6 +279,7 @@ class EditorHelpBit : public VBoxContainer { String value; Vector arguments; String qualifiers; + String resource_path; }; inline static HashMap doc_class_cache; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index d8c5f57158..201501c5a4 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2397,6 +2397,7 @@ void EditorInspectorArray::_setup() { Ref numbers_font; int numbers_min_w = 0; + bool unresizable = is_const || read_only; if (numbered) { numbers_font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)); @@ -2493,15 +2494,17 @@ void EditorInspectorArray::_setup() { ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); ae.hbox->add_child(ae.vbox); - ae.erase = memnew(Button); - ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove"))); - ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER); - ae.erase->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position)); - ae.hbox->add_child(ae.erase); + if (!unresizable) { + ae.erase = memnew(Button); + ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove"))); + ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER); + ae.erase->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position)); + ae.hbox->add_child(ae.erase); + } } // Hide/show the add button. - add_button->set_visible(page == max_page); + add_button->set_visible(page == max_page && !unresizable); // Add paginator if there's more than 1 page. if (max_page > 0) { @@ -2619,12 +2622,13 @@ void EditorInspectorArray::_bind_methods() { ADD_SIGNAL(MethodInfo("page_change_request")); } -void EditorInspectorArray::setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text) { +void EditorInspectorArray::setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text) { count_property = ""; mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; array_element_prefix = p_array_element_prefix; page = p_page; movable = p_movable; + is_const = p_is_const; page_length = p_page_length; numbered = p_numbered; @@ -2633,12 +2637,13 @@ void EditorInspectorArray::setup_with_move_element_function(Object *p_object, co _setup(); } -void EditorInspectorArray::setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) { +void EditorInspectorArray::setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) { count_property = p_count_property; mode = MODE_USE_COUNT_PROPERTY; array_element_prefix = p_array_element_prefix; page = p_page; movable = p_movable; + is_const = p_is_const; page_length = p_page_length; numbered = p_numbered; swap_method = p_swap_method; @@ -3474,6 +3479,7 @@ void EditorInspector::update_tree() { int page_size = 5; bool movable = true; + bool is_const = false; bool numbered = false; bool foldable = use_folding; String add_button_text = TTR("Add Element"); @@ -3485,6 +3491,8 @@ void EditorInspector::update_tree() { add_button_text = class_name_components[i].get_slice("=", 1).strip_edges(); } else if (class_name_components[i] == "static") { movable = false; + } else if (class_name_components[i] == "const") { + is_const = true; } else if (class_name_components[i] == "numbered") { numbered = true; } else if (class_name_components[i] == "unfoldable") { @@ -3511,7 +3519,7 @@ void EditorInspector::update_tree() { editor_inspector_array = memnew(EditorInspectorArray(all_read_only)); int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; - editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, numbered, page_size, add_button_text, swap_method); + editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, is_const, numbered, page_size, add_button_text, swap_method); editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix)); } } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 4146b0898c..2d3a5db377 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -401,6 +401,7 @@ class EditorInspectorArray : public EditorInspectorSection { bool read_only = false; bool movable = true; + bool is_const = false; bool numbered = false; enum MenuOptions { @@ -467,8 +468,8 @@ protected: static void _bind_methods(); public: - void setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = ""); - void setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = ""); + void setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = ""); + void setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = ""); VBoxContainer *get_vbox(int p_index); EditorInspectorArray(bool p_read_only); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 448b45871e..109b86bd4b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -343,6 +343,19 @@ void EditorNode::_update_title() { } } +void EditorNode::input(const Ref &p_event) { + // EditorNode::get_singleton()->set_process_input is set to true in ProgressDialog + // only when the progress dialog is visible. + // We need to discard all key events to disable all shortcuts while the progress + // dialog is displayed, simulating an exclusive popup. Mouse events are + // captured by a full-screen container in front of the EditorNode in ProgressDialog, + // allowing interaction with the actual dialog where a Cancel button may be visible. + Ref k = p_event; + if (k.is_valid()) { + get_tree()->get_root()->set_input_as_handled(); + } +} + void EditorNode::shortcut_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -1912,11 +1925,12 @@ void EditorNode::_save_scene(String p_file, int idx) { return; } + List>> anim_backups; + _reset_animation_mixers(scene, &anim_backups); + scene->propagate_notification(NOTIFICATION_EDITOR_PRE_SAVE); editor_data.apply_changes_in_editors(); - List>> anim_backups; - _reset_animation_mixers(scene, &anim_backups); save_default_environment(); _save_editor_states(p_file, idx); @@ -3975,7 +3989,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b return OK; } - String lpath = ResourceUID::ensure_path(p_scene); + String lpath = ProjectSettings::get_singleton()->localize_path(ResourceUID::ensure_path(p_scene)); if (!p_set_inherited) { for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { if (editor_data.get_scene_path(i) == lpath) { @@ -3993,7 +4007,6 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b } } - lpath = ProjectSettings::get_singleton()->localize_path(lpath); if (!lpath.begins_with("res://")) { show_accept(TTR("Error loading scene, it must be inside the project path. Use 'Import' to open the scene, then save it inside the project path."), TTR("OK")); opening_prev = false; @@ -5962,8 +5975,11 @@ void EditorNode::_notify_nodes_scene_reimported(Node *p_node, Array p_reimported void EditorNode::reload_scene(const String &p_path) { int scene_idx = -1; + + String lpath = ProjectSettings::get_singleton()->localize_path(p_path); + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { - if (editor_data.get_scene_path(i) == p_path) { + if (editor_data.get_scene_path(i) == lpath) { scene_idx = i; break; } @@ -7078,7 +7094,7 @@ EditorNode::EditorNode() { resource_preview = memnew(EditorResourcePreview); add_child(resource_preview); progress_dialog = memnew(ProgressDialog); - progress_dialog->set_unparent_when_invisible(true); + add_child(progress_dialog); progress_dialog->connect(SceneStringName(visibility_changed), callable_mp(this, &EditorNode::_progress_dialog_visibility_changed)); gui_base = memnew(Panel); diff --git a/editor/editor_node.h b/editor/editor_node.h index d7831f37a5..7c8349b9ee 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -594,6 +594,7 @@ private: void _exit_editor(int p_exit_code); + virtual void input(const Ref &p_event) override; virtual void shortcut_input(const Ref &p_event) override; bool has_main_screen() const { return true; } diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 5879d197c6..059fc1d988 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -48,7 +48,7 @@ String EditorRun::get_running_scene() const { return running_scene; } -Error EditorRun::run(const String &p_scene, const String &p_write_movie) { +Error EditorRun::run(const String &p_scene, const String &p_write_movie, const Vector &p_run_args) { List args; for (const String &a : Main::get_forwardable_cli_arguments(Main::CLI_SCOPE_PROJECT)) { @@ -225,6 +225,12 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { args.push_back(p_scene); } + if (!p_run_args.is_empty()) { + for (const String &run_arg : p_run_args) { + args.push_back(run_arg); + } + } + String exec = OS::get_singleton()->get_executable_path(); int instance_count = RunInstancesDialog::get_singleton()->get_instance_count(); for (int i = 0; i < instance_count; i++) { diff --git a/editor/editor_run.h b/editor/editor_run.h index 064f4c7368..55d01b95ce 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -57,7 +57,7 @@ public: Status get_status() const; String get_running_scene() const; - Error run(const String &p_scene, const String &p_write_movie = ""); + Error run(const String &p_scene, const String &p_write_movie = "", const Vector &p_run_args = Vector()); void run_native_notify() { status = STATUS_PLAY; } void stop(); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index dac9a59334..a88f9c8d8a 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -763,10 +763,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // GridMap // GridMapEditor - _initial_set("editors/grid_map/pick_distance", 5000.0); - _initial_set("editors/grid_map/palette_min_width", 230); - set_restart_if_changed("editors/grid_map/palette_min_width", true); - _initial_set("editors/grid_map/preview_size", 64); + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/grid_map/pick_distance", 5000.0, "1,8192,0.1,or_greater"); + EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "editors/grid_map/palette_min_width", 230, "100,500,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_RANGE, "editors/grid_map/preview_size", 64, "16,128,1") // 3D EDITOR_SETTING_BASIC(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d/primary_grid_color", Color(0.56, 0.56, 0.56, 0.5), "") @@ -797,6 +796,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/gridmap_grid", Color(0.8, 0.5, 0.1), "") + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_joint", Color(0.8, 0.9, 0.6), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_collision", Color(0.6, 0.8, 0.9), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_inside_collision", Color(0.9, 0.6, 0.8), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) _initial_set("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d_gizmos/gizmo_settings/bone_shape", 1, "Wire,Octahedron"); EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index b73860217a..0cbb5608c1 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -2211,7 +2211,7 @@ Vector EditorExportPlatform::gen_export_flags(BitField breakpoints; ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index e12db663c8..5f66ea1a4b 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -628,6 +628,7 @@ FindInFilesPanel::FindInFilesPanel() { _results_display->set_v_size_flags(SIZE_EXPAND_FILL); _results_display->connect(SceneStringName(item_selected), callable_mp(this, &FindInFilesPanel::_on_result_selected)); _results_display->connect("item_edited", callable_mp(this, &FindInFilesPanel::_on_item_edited)); + _results_display->connect("button_clicked", callable_mp(this, &FindInFilesPanel::_on_button_clicked)); _results_display->set_hide_root(true); _results_display->set_select_mode(Tree::SELECT_ROW); _results_display->set_allow_rmb_select(true); @@ -735,12 +736,14 @@ void FindInFilesPanel::_notification(int p_what) { void FindInFilesPanel::_on_result_found(const String &fpath, int line_number, int begin, int end, String text) { TreeItem *file_item; - HashMap::Iterator E = _file_items.find(fpath); + Ref remove_texture = get_editor_theme_icon(SNAME("Close")); + HashMap::Iterator E = _file_items.find(fpath); if (!E) { file_item = _results_display->create_item(); file_item->set_text(0, fpath); file_item->set_metadata(0, fpath); + file_item->add_button(0, remove_texture, -1, false, TTR("Remove result")); // The width of this column is restrained to checkboxes, // but that doesn't make sense for the parent items, @@ -783,6 +786,9 @@ void FindInFilesPanel::_on_result_found(const String &fpath, int line_number, in item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); item->set_checked(0, true); item->set_editable(0, true); + item->add_button(1, remove_texture, -1, false, TTR("Remove result")); + } else { + item->add_button(0, remove_texture, -1, false, TTR("Remove result")); } } @@ -825,19 +831,7 @@ void FindInFilesPanel::_on_item_edited() { } void FindInFilesPanel::_on_finished() { - String results_text; - int result_count = _result_items.size(); - int file_count = _file_items.size(); - - if (result_count == 1 && file_count == 1) { - results_text = vformat(TTR("%d match in %d file"), result_count, file_count); - } else if (result_count != 1 && file_count == 1) { - results_text = vformat(TTR("%d matches in %d file"), result_count, file_count); - } else { - results_text = vformat(TTR("%d matches in %d files"), result_count, file_count); - } - - _status_label->set_text(results_text); + update_matches_text(); update_replace_buttons(); set_progress_visible(false); _refresh_button->show(); @@ -908,6 +902,32 @@ void FindInFilesPanel::_on_replace_all_clicked() { emit_signal(SNAME(SIGNAL_FILES_MODIFIED), modified_files); } +void FindInFilesPanel::_on_button_clicked(TreeItem *p_item, int p_column, int p_id, int p_mouse_button_index) { + const String file_path = p_item->get_text(0); + + _result_items.erase(p_item); + if (_file_items.find(file_path)) { + TreeItem *file_result = _file_items.get(file_path); + int match_count = file_result->get_child_count(); + + for (int i = 0; i < match_count; i++) { + TreeItem *child_item = file_result->get_child(i); + _result_items.erase(child_item); + } + + file_result->clear_children(); + _file_items.erase(file_path); + } + + TreeItem *item_parent = p_item->get_parent(); + if (item_parent && item_parent->get_child_count() < 2) { + _file_items.erase(item_parent->get_text(0)); + get_tree()->queue_delete(item_parent); + } + get_tree()->queue_delete(p_item); + update_matches_text(); +} + // Same as get_line, but preserves line ending characters. class ConservativeGetLine { public: @@ -1008,6 +1028,22 @@ void FindInFilesPanel::update_replace_buttons() { _replace_all_button->set_disabled(disabled); } +void FindInFilesPanel::update_matches_text() { + String results_text; + int result_count = _result_items.size(); + int file_count = _file_items.size(); + + if (result_count == 1 && file_count == 1) { + results_text = vformat(TTR("%d match in %d file"), result_count, file_count); + } else if (result_count != 1 && file_count == 1) { + results_text = vformat(TTR("%d matches in %d file"), result_count, file_count); + } else { + results_text = vformat(TTR("%d matches in %d files"), result_count, file_count); + } + + _status_label->set_text(results_text); +} + void FindInFilesPanel::set_progress_visible(bool p_visible) { _progress_bar->set_self_modulate(Color(1, 1, 1, p_visible ? 1 : 0)); } diff --git a/editor/find_in_files.h b/editor/find_in_files.h index 1322152d0f..954769b265 100644 --- a/editor/find_in_files.h +++ b/editor/find_in_files.h @@ -179,6 +179,7 @@ protected: void _notification(int p_what); private: + void _on_button_clicked(TreeItem *p_item, int p_column, int p_id, int p_mouse_button_index); void _on_result_found(const String &fpath, int line_number, int begin, int end, String text); void _on_finished(); void _on_refresh_button_clicked(); @@ -198,6 +199,7 @@ private: void apply_replaces_in_file(const String &fpath, const Vector &locations, const String &new_text); void update_replace_buttons(); + void update_matches_text(); String get_replace_text(); void draw_result_text(Object *item_obj, Rect2 rect); diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index 76212d95e7..966bc264ae 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -46,8 +46,13 @@ #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/menu_button.h" #include "scene/gui/panel_container.h" +#ifndef _3D_DISABLED +#include "servers/xr_server.h" +#endif // _3D_DISABLED + EditorRunBar *EditorRunBar::singleton = nullptr; void EditorRunBar::_notification(int p_what) { @@ -164,33 +169,51 @@ void EditorRunBar::_write_movie_toggled(bool p_enabled) { } } -void EditorRunBar::_quick_run_selected(const String &p_file_path) { - play_custom_scene(p_file_path); +Vector EditorRunBar::_get_xr_mode_play_args(int p_xr_mode_id) { + Vector play_args; + if (p_xr_mode_id == 0) { + // Play in regular mode, xr mode off. + play_args.push_back("--xr-mode"); + play_args.push_back("off"); + } else if (p_xr_mode_id == 1) { + // Play in xr mode. + play_args.push_back("--xr-mode"); + play_args.push_back("on"); + } + return play_args; } -void EditorRunBar::_play_custom_pressed() { +void EditorRunBar::_quick_run_selected(const String &p_file_path, int p_id) { + play_custom_scene(p_file_path, _get_xr_mode_play_args(p_id)); +} + +void EditorRunBar::_play_custom_pressed(int p_id) { if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) { stop_playing(); - EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected)); + EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected).bind(p_id)); play_custom_scene_button->set_pressed(false); } else { + Vector play_args = _get_xr_mode_play_args(p_id); + // Reload if already running a custom scene. String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string. - play_custom_scene(last_custom_scene); + play_custom_scene(last_custom_scene, play_args); } } -void EditorRunBar::_play_current_pressed() { +void EditorRunBar::_play_current_pressed(int p_id) { + Vector play_args = _get_xr_mode_play_args(p_id); + if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) { - play_current_scene(); + play_current_scene(false, play_args); } else { // Reload if already running the current scene. - play_current_scene(true); + play_current_scene(true, play_args); } } -void EditorRunBar::_run_scene(const String &p_scene_path) { +void EditorRunBar::_run_scene(const String &p_scene_path, const Vector &p_run_args) { ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path."); if (editor_run.get_status() == EditorRun::STATUS_PLAY) { @@ -275,7 +298,7 @@ void EditorRunBar::_run_scene(const String &p_scene_path) { } EditorDebuggerNode::get_singleton()->start(); - Error error = editor_run.run(run_filename, write_movie_file); + Error error = editor_run.run(run_filename, write_movie_file, p_run_args); if (error != OK) { EditorDebuggerNode::get_singleton()->stop(); EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK")); @@ -342,7 +365,7 @@ void EditorRunBar::play_main_scene(bool p_from_native) { } } -void EditorRunBar::play_current_scene(bool p_reload) { +void EditorRunBar::play_current_scene(bool p_reload, const Vector &p_play_args) { if (Engine::get_singleton()->is_recovery_mode_hint()) { EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING); return; @@ -355,13 +378,13 @@ void EditorRunBar::play_current_scene(bool p_reload) { current_mode = RunMode::RUN_CURRENT; if (p_reload) { - _run_scene(last_current_scene); + _run_scene(last_current_scene, p_play_args); } else { - _run_scene(); + _run_scene("", p_play_args); } } -void EditorRunBar::play_custom_scene(const String &p_custom) { +void EditorRunBar::play_custom_scene(const String &p_custom, const Vector &p_play_args) { if (Engine::get_singleton()->is_recovery_mode_hint()) { EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING); return; @@ -370,7 +393,7 @@ void EditorRunBar::play_custom_scene(const String &p_custom) { stop_playing(); current_mode = RunMode::RUN_CUSTOM; - _run_scene(p_custom); + _run_scene(p_custom, p_play_args); } void EditorRunBar::stop_playing() { @@ -565,25 +588,55 @@ EditorRunBar::EditorRunBar() { main_hbox->add_child(run_native); run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native)); - play_scene_button = memnew(Button); + bool add_play_xr_mode_options = false; +#ifndef _3D_DISABLED + if (OS::get_singleton()->has_feature("xr_editor") && + (XRServer::get_xr_mode() == XRServer::XRMODE_ON || + (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT && GLOBAL_GET("xr/openxr/enabled")))) { + // If this is the XR editor and openxr is enabled, we turn the `play_scene_button` and + // `play_custom_scene_button` into MenuButtons to provide the option to start a scene in + // either regular mode or XR mode. + add_play_xr_mode_options = true; + } +#endif // _3D_DISABLED + + if (add_play_xr_mode_options) { + MenuButton *menu_button = memnew(MenuButton); + PopupMenu *popup = menu_button->get_popup(); + popup->add_item(TTRC("Run Scene in Regular Mode"), 0); + popup->add_item(TTRC("Run Scene in XR Mode"), 1); + popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_current_pressed)); + play_scene_button = menu_button; + } else { + play_scene_button = memnew(Button); + play_scene_button->set_toggle_mode(true); + play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed).bind(-1)); + } main_hbox->add_child(play_scene_button); play_scene_button->set_theme_type_variation("RunBarButton"); - play_scene_button->set_toggle_mode(true); play_scene_button->set_focus_mode(Control::FOCUS_NONE); play_scene_button->set_tooltip_text(TTRC("Run the currently edited scene.")); - play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed)); ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTRC("Run Current Scene"), Key::F6); ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R); play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene")); - play_custom_scene_button = memnew(Button); + if (add_play_xr_mode_options) { + MenuButton *menu_button = memnew(MenuButton); + PopupMenu *popup = menu_button->get_popup(); + popup->add_item(TTRC("Run in Regular Mode"), 0); + popup->add_item(TTRC("Run in XR Mode"), 1); + popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed)); + play_custom_scene_button = menu_button; + } else { + play_custom_scene_button = memnew(Button); + play_custom_scene_button->set_toggle_mode(true); + play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed).bind(-1)); + } main_hbox->add_child(play_custom_scene_button); play_custom_scene_button->set_theme_type_variation("RunBarButton"); - play_custom_scene_button->set_toggle_mode(true); play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE); play_custom_scene_button->set_tooltip_text(TTRC("Run a specific scene.")); - play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed)); ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTRC("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5); ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R); diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h index ba4d507510..d8bdbbf454 100644 --- a/editor/gui/editor_run_bar.h +++ b/editor/gui/editor_run_bar.h @@ -86,16 +86,19 @@ class EditorRunBar : public MarginContainer { void _update_play_buttons(); void _write_movie_toggled(bool p_enabled); - void _quick_run_selected(const String &p_file_path); + void _quick_run_selected(const String &p_file_path, int p_id = -1); - void _play_current_pressed(); - void _play_custom_pressed(); + void _play_current_pressed(int p_id = -1); + void _play_custom_pressed(int p_id = -1); - void _run_scene(const String &p_scene_path = ""); + void _run_scene(const String &p_scene_path = "", const Vector &p_run_args = Vector()); void _run_native(const Ref &p_preset); void _profiler_autostart_indicator_pressed(); +private: + static Vector _get_xr_mode_play_args(int p_xr_mode_id); + protected: void _notification(int p_what); static void _bind_methods(); @@ -107,8 +110,8 @@ public: void recovery_mode_reload_project(); void play_main_scene(bool p_from_native = false); - void play_current_scene(bool p_reload = false); - void play_custom_scene(const String &p_custom); + void play_current_scene(bool p_reload = false, const Vector &p_play_args = Vector()); + void play_custom_scene(const String &p_custom, const Vector &p_play_args = Vector()); void stop_playing(); bool is_playing() const; diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index ef50abe4aa..3dd76b5e67 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -1441,9 +1441,32 @@ void SceneTreeEditor::rename_node(Node *p_node, const String &p_name, TreeItem * item = _find(tree->get_root(), p_node->get_path()); } ERR_FAIL_NULL(item); - String new_name = p_name.validate_node_name(); + bool check_for_unique_name_token = !p_name.is_empty() && p_name[0] == '%'; + String substr_name = p_name; - if (new_name != p_name) { + if (check_for_unique_name_token) { + substr_name = p_name.substr(1); + + // No need to do anything else with this if already unique. + if (p_node->is_unique_name_in_owner()) { + check_for_unique_name_token = false; + // Do not set scene root as unique. + } else if (get_tree()->get_edited_scene_root() == p_node) { + check_for_unique_name_token = false; + String text = TTR("Root nodes cannot be accessed as unique names in their own scene. Instantiate in another scene and set as unique name there."); + if (error->is_visible()) { + error->set_text(error->get_text() + "\n\n" + text); + } else { + error->set_text(text); + error->popup_centered(); + } + } + } + + String new_name = substr_name.validate_node_name(); + + // If p_name only has "%" at the beginning and no other invalid characters, do not error. + if (new_name != substr_name) { String text = TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::get_invalid_node_name_characters(); if (error->is_visible()) { if (!error->get_meta("invalid_character", false)) { @@ -1484,12 +1507,16 @@ void SceneTreeEditor::rename_node(Node *p_node, const String &p_name, TreeItem * new_name = p_node->get_parent()->prevalidate_child_name(p_node, new_name); if (new_name == p_node->get_name()) { item->set_text(0, new_name); - return; + // If setting name as unique, check for existing unique node below first. + if (!check_for_unique_name_token) { + return; + } } // We previously made sure name is not the same as current name // so that it won't complain about already used unique name when not changing name. - if (p_node->is_unique_name_in_owner() && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name)) { + if ((check_for_unique_name_token || p_node->is_unique_name_in_owner()) && get_tree()->get_edited_scene_root()->get_node_or_null("%" + new_name)) { + check_for_unique_name_token = false; String text = vformat(TTR("A node with the unique name %s already exists in this scene."), new_name); if (error->is_visible()) { if (!error->get_meta("same_unique_name", false)) { @@ -1503,17 +1530,43 @@ void SceneTreeEditor::rename_node(Node *p_node, const String &p_name, TreeItem * error->popup_centered(); } item->set_text(0, p_node->get_name()); + if (p_node->is_unique_name_in_owner()) { + return; + } + } + + // If same name and check_for_unique_name_token is still true, now set as unique. + // This is separate from final action so "Rename Node" is not added to undo history. + if (new_name == p_node->get_name()) { + if (check_for_unique_name_token) { + if (!is_scene_tree_dock) { + p_node->set_unique_name_in_owner(true); + } else { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Enable Scene Unique Name(s)")); + undo_redo->add_undo_method(p_node, "set_unique_name_in_owner", false); + undo_redo->add_do_method(p_node, "set_unique_name_in_owner", true); + undo_redo->commit_action(); + } + } return; } if (!is_scene_tree_dock) { p_node->set_name(new_name); + if (check_for_unique_name_token) { + p_node->set_unique_name_in_owner(true); + } item->set_metadata(0, p_node->get_path()); emit_signal(SNAME("node_renamed")); } else { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Rename Node"), UndoRedo::MERGE_DISABLE, p_node); + if (check_for_unique_name_token) { + undo_redo->add_undo_method(p_node, "set_unique_name_in_owner", false); + } + emit_signal(SNAME("node_prerename"), p_node, new_name); undo_redo->add_undo_method(p_node, "set_name", p_node->get_name()); @@ -1525,6 +1578,10 @@ void SceneTreeEditor::rename_node(Node *p_node, const String &p_name, TreeItem * undo_redo->add_do_method(item, "set_metadata", 0, p_node->get_path()); undo_redo->add_do_method(item, "set_text", 0, new_name); + if (check_for_unique_name_token) { + undo_redo->add_do_method(p_node, "set_unique_name_in_owner", true); + } + undo_redo->commit_action(); } } diff --git a/editor/icons/LocalVariable.svg b/editor/icons/LocalVariable.svg new file mode 100644 index 0000000000..194a34d9d4 --- /dev/null +++ b/editor/icons/LocalVariable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollision3D.svg b/editor/icons/SpringBoneCollision3D.svg new file mode 100644 index 0000000000..564c44a411 --- /dev/null +++ b/editor/icons/SpringBoneCollision3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionCapsule3D.svg b/editor/icons/SpringBoneCollisionCapsule3D.svg new file mode 100644 index 0000000000..3c433562d8 --- /dev/null +++ b/editor/icons/SpringBoneCollisionCapsule3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionPlane3D.svg b/editor/icons/SpringBoneCollisionPlane3D.svg new file mode 100644 index 0000000000..7ccb674ab1 --- /dev/null +++ b/editor/icons/SpringBoneCollisionPlane3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionSphere3D.svg b/editor/icons/SpringBoneCollisionSphere3D.svg new file mode 100644 index 0000000000..7d39ee952f --- /dev/null +++ b/editor/icons/SpringBoneCollisionSphere3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneSimulator3D.svg b/editor/icons/SpringBoneSimulator3D.svg new file mode 100644 index 0000000000..eeb37314c3 --- /dev/null +++ b/editor/icons/SpringBoneSimulator3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/TexturePreviewChannels.svg b/editor/icons/TexturePreviewChannels.svg new file mode 100644 index 0000000000..e70fd998c7 --- /dev/null +++ b/editor/icons/TexturePreviewChannels.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/import/3d/editor_import_collada.cpp b/editor/import/3d/editor_import_collada.cpp index 144acfbdaa..cc623fe378 100644 --- a/editor/import/3d/editor_import_collada.cpp +++ b/editor/import/3d/editor_import_collada.cpp @@ -1798,10 +1798,6 @@ void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) { /*************************************** SCENE ***********************************/ /*********************************************************************************/ -uint32_t EditorSceneFormatImporterCollada::get_import_flags() const { - return IMPORT_SCENE | IMPORT_ANIMATION; -} - void EditorSceneFormatImporterCollada::get_extensions(List *r_extensions) const { r_extensions->push_back("dae"); } diff --git a/editor/import/3d/editor_import_collada.h b/editor/import/3d/editor_import_collada.h index 60ccb94441..ac54c295f7 100644 --- a/editor/import/3d/editor_import_collada.h +++ b/editor/import/3d/editor_import_collada.h @@ -39,7 +39,6 @@ class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter); public: - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps = nullptr, Error *r_err = nullptr) override; diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 2845fed12c..2a48203f81 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -40,10 +40,6 @@ #include "scene/resources/mesh.h" #include "scene/resources/surface_tool.h" -uint32_t EditorOBJImporter::get_import_flags() const { - return IMPORT_SCENE; -} - static Error _parse_material_library(const String &p_path, HashMap> &material_map, List *r_missing_deps) { Ref f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open MTL file '%s', it may not exist or not be readable.", p_path)); diff --git a/editor/import/3d/resource_importer_obj.h b/editor/import/3d/resource_importer_obj.h index f2226d2dff..6cead4eff1 100644 --- a/editor/import/3d/resource_importer_obj.h +++ b/editor/import/3d/resource_importer_obj.h @@ -39,7 +39,6 @@ class EditorOBJImporter : public EditorSceneFormatImporter { GDCLASS(EditorOBJImporter, EditorSceneFormatImporter); public: - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr) override; diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index ca79fc41fd..d8b954b34a 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -59,15 +59,6 @@ #include "scene/resources/packed_scene.h" #include "scene/resources/resource_format_text.h" -uint32_t EditorSceneFormatImporter::get_import_flags() const { - uint32_t ret; - if (GDVIRTUAL_CALL(_get_import_flags, ret)) { - return ret; - } - - ERR_FAIL_V(0); -} - void EditorSceneFormatImporter::get_extensions(List *r_extensions) const { Vector arr; if (GDVIRTUAL_CALL(_get_extensions, arr)) { @@ -93,8 +84,20 @@ Node *EditorSceneFormatImporter::import_scene(const String &p_path, uint32_t p_f ERR_FAIL_V(nullptr); } +void EditorSceneFormatImporter::add_import_option(const String &p_name, const Variant &p_default_value) { + ERR_FAIL_NULL_MSG(current_option_list, "add_import_option() can only be called from get_import_options()."); + add_import_option_advanced(p_default_value.get_type(), p_name, p_default_value); +} + +void EditorSceneFormatImporter::add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) { + ERR_FAIL_NULL_MSG(current_option_list, "add_import_option_advanced() can only be called from get_import_options()."); + current_option_list->push_back(ResourceImporter::ImportOption(PropertyInfo(p_type, p_name, p_hint, p_hint_string, p_usage_flags), p_default_value)); +} + void EditorSceneFormatImporter::get_import_options(const String &p_path, List *r_options) { + current_option_list = r_options; GDVIRTUAL_CALL(_get_import_options, p_path); + current_option_list = nullptr; } Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap &p_options) { @@ -105,7 +108,9 @@ Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, c } void EditorSceneFormatImporter::_bind_methods() { - GDVIRTUAL_BIND(_get_import_flags); + ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorSceneFormatImporter::add_import_option); + ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorSceneFormatImporter::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT)); + GDVIRTUAL_BIND(_get_extensions); GDVIRTUAL_BIND(_import_scene, "path", "flags", "options"); GDVIRTUAL_BIND(_get_import_options, "path"); @@ -2014,29 +2019,29 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p decomposition_default.instantiate(); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/advanced", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/precision", PROPERTY_HINT_RANGE, "1,10,1"), 5)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/max_concavity", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_max_concavity())); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/symmetry_planes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_symmetry_planes_clipping_bias())); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/revolution_axes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_revolution_axes_clipping_bias())); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/min_volume_per_convex_hull", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_min_volume_per_convex_hull())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/resolution", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_resolution())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_num_vertices_per_convex_hull", PROPERTY_HINT_RANGE, "5,512,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_max_num_vertices_per_convex_hull())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/plane_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_plane_downsampling())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/convexhull_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_convex_hull_downsampling())); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/normalize_mesh", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_normalize_mesh())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/mode", PROPERTY_HINT_ENUM, "Voxel,Tetrahedron", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), static_cast(decomposition_default->get_mode()))); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/convexhull_approximation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_convex_hull_approximation())); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_convex_hulls", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_max_convex_hulls())); - r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/project_hull_vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), decomposition_default->get_project_hull_vertices())); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/max_concavity", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT), decomposition_default->get_max_concavity())); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/symmetry_planes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT), decomposition_default->get_symmetry_planes_clipping_bias())); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/revolution_axes_clipping_bias", PROPERTY_HINT_RANGE, "0.0,1.0,0.001", PROPERTY_USAGE_DEFAULT), decomposition_default->get_revolution_axes_clipping_bias())); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "decomposition/min_volume_per_convex_hull", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), decomposition_default->get_min_volume_per_convex_hull())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/resolution", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), decomposition_default->get_resolution())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_num_vertices_per_convex_hull", PROPERTY_HINT_RANGE, "5,512,1", PROPERTY_USAGE_DEFAULT), decomposition_default->get_max_num_vertices_per_convex_hull())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/plane_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT), decomposition_default->get_plane_downsampling())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/convexhull_downsampling", PROPERTY_HINT_RANGE, "1,16,1", PROPERTY_USAGE_DEFAULT), decomposition_default->get_convex_hull_downsampling())); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/normalize_mesh", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), decomposition_default->get_normalize_mesh())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/mode", PROPERTY_HINT_ENUM, "Voxel,Tetrahedron", PROPERTY_USAGE_DEFAULT), static_cast(decomposition_default->get_mode()))); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/convexhull_approximation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), decomposition_default->get_convex_hull_approximation())); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "decomposition/max_convex_hulls", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT), decomposition_default->get_max_convex_hulls())); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "decomposition/project_hull_vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), decomposition_default->get_project_hull_vertices())); // Primitives: Box, Sphere, Cylinder, Capsule. - r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3(2.0, 2.0, 2.0))); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/height", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/radius", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 1.0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3())); - r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Vector3())); + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), Vector3(2.0, 2.0, 2.0))); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/height", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), 1.0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "primitive/radius", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), 1.0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), Vector3())); + r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "primitive/rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), Vector3())); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/occluder", PROPERTY_HINT_ENUM, "Disabled,Mesh + Occluder,Occluder Only", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); - r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "occluder/simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0.1f)); + r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "occluder/simplification_distance", PROPERTY_HINT_RANGE, "0.0,2.0,0.01", PROPERTY_USAGE_DEFAULT), 0.1f)); } break; case INTERNAL_IMPORT_CATEGORY_MESH: { r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false)); @@ -3268,10 +3273,6 @@ void ResourceImporterScene::get_scene_importer_extensions(List *p_extens /////////////////////////////////////// -uint32_t EditorSceneFormatImporterESCN::get_import_flags() const { - return IMPORT_SCENE; -} - void EditorSceneFormatImporterESCN::get_extensions(List *r_extensions) const { r_extensions->push_back("escn"); } diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h index 9f4d394ae6..f054a4e4a6 100644 --- a/editor/import/3d/resource_importer_scene.h +++ b/editor/import/3d/resource_importer_scene.h @@ -52,13 +52,14 @@ class Material; class EditorSceneFormatImporter : public RefCounted { GDCLASS(EditorSceneFormatImporter, RefCounted); + List *current_option_list = nullptr; + protected: static void _bind_methods(); Node *import_scene_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options); Ref import_animation_wrapper(const String &p_path, uint32_t p_flags, const Dictionary &p_options); - GDVIRTUAL0RC(uint32_t, _get_import_flags) GDVIRTUAL0RC(Vector, _get_extensions) GDVIRTUAL3R(Object *, _import_scene, String, uint32_t, Dictionary) GDVIRTUAL1(_get_import_options, String) @@ -75,7 +76,8 @@ public: IMPORT_FORCE_DISABLE_MESH_COMPRESSION = 64, }; - virtual uint32_t get_import_flags() const; + void add_import_option(const String &p_name, const Variant &p_default_value); + void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT); virtual void get_extensions(List *r_extensions) const; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr); virtual void get_import_options(const String &p_path, List *r_options); @@ -320,7 +322,6 @@ class EditorSceneFormatImporterESCN : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterESCN, EditorSceneFormatImporter); public: - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, List *r_missing_deps, Error *r_err = nullptr) override; }; diff --git a/editor/plugins/color_channel_selector.cpp b/editor/plugins/color_channel_selector.cpp new file mode 100644 index 0000000000..690f825b36 --- /dev/null +++ b/editor/plugins/color_channel_selector.cpp @@ -0,0 +1,148 @@ +/**************************************************************************/ +/* color_channel_selector.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "color_channel_selector.h" + +#include "editor/themes/editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" + +ColorChannelSelector::ColorChannelSelector() { + toggle_button = memnew(Button); + toggle_button->set_flat(true); + toggle_button->set_toggle_mode(true); + toggle_button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_toggled)); + toggle_button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); + add_child(toggle_button); + + panel = memnew(PanelContainer); + panel->hide(); + + HBoxContainer *container = memnew(HBoxContainer); + container->add_theme_constant_override("separation", 0); + + create_button(0, "R", container); + create_button(1, "G", container); + create_button(2, "B", container); + create_button(3, "A", container); + + // Use a bit of transparency to be less distracting. + set_modulate(Color(1, 1, 1, 0.7)); + + panel->add_child(container); + + add_child(panel); +} + +void ColorChannelSelector::_notification(int p_what) { + if (p_what == NOTIFICATION_THEME_CHANGED) { + // PanelContainer's background is invisible in the editor. We need a background. + // And we need this in turn because buttons don't look good without background (for example, hover is transparent). + Ref bg_style = get_theme_stylebox(SceneStringName(panel), "TabContainer"); + ERR_FAIL_COND(bg_style.is_null()); + bg_style = bg_style->duplicate(); + // The default content margin makes the widget become a bit too large. It should be like mini-toolbar. + const float editor_scale = EditorScale::get_scale(); + bg_style->set_content_margin(SIDE_LEFT, 1.0f * editor_scale); + bg_style->set_content_margin(SIDE_RIGHT, 1.0f * editor_scale); + bg_style->set_content_margin(SIDE_TOP, 1.0f * editor_scale); + bg_style->set_content_margin(SIDE_BOTTOM, 1.0f * editor_scale); + panel->add_theme_style_override(SceneStringName(panel), bg_style); + + Ref icon = get_editor_theme_icon(SNAME("TexturePreviewChannels")); + toggle_button->set_button_icon(icon); + } +} + +void ColorChannelSelector::set_available_channels_mask(uint32_t p_mask) { + for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) { + const bool available = (p_mask & (1u << i)) != 0; + Button *button = channel_buttons[i]; + button->set_visible(available); + } +} + +void ColorChannelSelector::on_channel_button_toggled(bool p_unused_pressed) { + emit_signal("selected_channels_changed"); +} + +uint32_t ColorChannelSelector::get_selected_channels_mask() const { + uint32_t mask = 0; + for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) { + Button *button = channel_buttons[i]; + if (button->is_visible() && channel_buttons[i]->is_pressed()) { + mask |= (1 << i); + } + } + return mask; +} + +// Helper +Vector4 ColorChannelSelector::get_selected_channel_factors() const { + Vector4 channel_factors; + const uint32_t mask = get_selected_channels_mask(); + for (unsigned int i = 0; i < 4; ++i) { + if ((mask & (1 << i)) != 0) { + channel_factors[i] = 1; + } + } + return channel_factors; +} + +void ColorChannelSelector::create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent) { + ERR_FAIL_COND(p_channel_index >= CHANNEL_COUNT); + ERR_FAIL_COND(channel_buttons[p_channel_index] != nullptr); + Button *button = memnew(Button); + button->set_text(p_text); + button->set_toggle_mode(true); + button->set_pressed(true); + + // Don't show focus, it stands out too much and remains visible which can be confusing. + button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); + + // Make it look similar to toolbar buttons. + button->set_theme_type_variation(SceneStringName(FlatButton)); + + button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_channel_button_toggled)); + p_parent->add_child(button); + channel_buttons[p_channel_index] = button; +} + +void ColorChannelSelector::on_toggled(bool p_pressed) { + panel->set_visible(p_pressed); +} + +void ColorChannelSelector::_bind_methods() { + ADD_SIGNAL(MethodInfo("selected_channels_changed")); +} diff --git a/editor/plugins/color_channel_selector.h b/editor/plugins/color_channel_selector.h new file mode 100644 index 0000000000..a15edcbb32 --- /dev/null +++ b/editor/plugins/color_channel_selector.h @@ -0,0 +1,67 @@ +/**************************************************************************/ +/* color_channel_selector.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef COLOR_CHANNEL_SELECTOR_H +#define COLOR_CHANNEL_SELECTOR_H + +#include "scene/gui/box_container.h" + +class PanelContainer; +class Button; + +class ColorChannelSelector : public HBoxContainer { + GDCLASS(ColorChannelSelector, HBoxContainer); + + static const unsigned int CHANNEL_COUNT = 4; + +public: + ColorChannelSelector(); + + void set_available_channels_mask(uint32_t p_mask); + uint32_t get_selected_channels_mask() const; + Vector4 get_selected_channel_factors() const; + +private: + void _notification(int p_what); + + void on_channel_button_toggled(bool p_unused_pressed); + void create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent); + void on_toggled(bool p_pressed); + + static void _bind_methods(); + + Button *channel_buttons[CHANNEL_COUNT] = {}; + PanelContainer *panel = nullptr; + Button *toggle_button = nullptr; +}; + +#endif // COLOR_CHANNEL_SELECTOR_H diff --git a/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp index 32a5eae280..b4dfb28eab 100644 --- a/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/navigation_link_3d_gizmo_plugin.cpp @@ -72,59 +72,103 @@ void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); - // Draw line between the points. + // Number of points in an octant. So there ill be 8 * points_in_octant points in total. + // Correspond to the smoothness of the circle. + const uint32_t points_in_octant = 4; + real_t inc = (Math_PI / (4 * points_in_octant)); + Vector lines; - lines.append(start_position); - lines.append(end_position); + // points_in_octant * 8 * 2 per circle * 2 circles. 2 for the start-end. 4 for the arrow, and another 4 if bidirectionnal. + lines.resize(points_in_octant * 8 * 2 * 2 + 2 + 4 + (link->is_bidirectional() ? 4 : 0)); + uint32_t index = 0; + Vector3 *lines_ptrw = lines.ptrw(); + // Draw line between the points. + lines_ptrw[index++] = start_position; + lines_ptrw[index++] = end_position; + real_t search_radius_squared = search_radius * search_radius; - // Draw start position search radius - for (int i = 0; i < 30; i++) { - // Create a circle - const float ra = Math::deg_to_rad((float)(i * 12)); - const float rb = Math::deg_to_rad((float)((i + 1) * 12)); - const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; - const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + // Draw circles at start and end positions in one go. + real_t r = 0; + Vector2 a = Vector2(search_radius, 0); + for (uint32_t i = 0; i < points_in_octant; i++) { + r += inc; + real_t x = Math::cos(r) * search_radius; + real_t y = Math::sqrt(search_radius_squared - (x * x)); - // Draw axis-aligned circle + // Draw axis-aligned circle. switch (up_axis) { case Vector3::AXIS_X: - lines.append(start_position + Vector3(0, a.x, a.y)); - lines.append(start_position + Vector3(0, b.x, b.y)); +#define PUSH_OCTANT(_position, a, b) \ + lines_ptrw[index++] = _position + Vector3(0, a.x, a.y); \ + lines_ptrw[index++] = _position + Vector3(0, x, y); \ + lines_ptrw[index++] = _position + Vector3(0, -a.x, a.y); \ + lines_ptrw[index++] = _position + Vector3(0, x, y); \ + lines_ptrw[index++] = _position + Vector3(0, a.x, -a.y); \ + lines_ptrw[index++] = _position + Vector3(0, x, -y); \ + lines_ptrw[index++] = _position + Vector3(0, -a.x, -a.y); \ + lines_ptrw[index++] = _position + Vector3(0, x, y); \ + lines_ptrw[index++] = _position + Vector3(0, a.y, a.x); \ + lines_ptrw[index++] = _position + Vector3(0, y, x); \ + lines_ptrw[index++] = _position + Vector3(0, -a.y, a.x); \ + lines_ptrw[index++] = _position + Vector3(0, -y, x); \ + lines_ptrw[index++] = _position + Vector3(0, a.y, -a.x); \ + lines_ptrw[index++] = _position + Vector3(0, y, -x); \ + lines_ptrw[index++] = _position + Vector3(0, -a.y, -a.x); \ + lines_ptrw[index++] = _position + Vector3(0, -y, -x); + + PUSH_OCTANT(start_position, a, b) + PUSH_OCTANT(end_position, a, b) +#undef PUSH_OCTANT break; case Vector3::AXIS_Y: - lines.append(start_position + Vector3(a.x, 0, a.y)); - lines.append(start_position + Vector3(b.x, 0, b.y)); +#define PUSH_OCTANT(_position, a, b) \ + lines_ptrw[index++] = _position + Vector3(a.x, 0, a.y); \ + lines_ptrw[index++] = _position + Vector3(x, 0, y); \ + lines_ptrw[index++] = _position + Vector3(-a.x, 0, a.y); \ + lines_ptrw[index++] = _position + Vector3(-x, 0, y); \ + lines_ptrw[index++] = _position + Vector3(a.x, 0, -a.y); \ + lines_ptrw[index++] = _position + Vector3(x, 0, -y); \ + lines_ptrw[index++] = _position + Vector3(-a.x, 0, -a.y); \ + lines_ptrw[index++] = _position + Vector3(-x, 0, -y); \ + lines_ptrw[index++] = _position + Vector3(a.y, 0, a.x); \ + lines_ptrw[index++] = _position + Vector3(y, 0, x); \ + lines_ptrw[index++] = _position + Vector3(-a.y, 0, a.x); \ + lines_ptrw[index++] = _position + Vector3(-y, 0, x); \ + lines_ptrw[index++] = _position + Vector3(a.y, 0, -a.x); \ + lines_ptrw[index++] = _position + Vector3(y, 0, -x); \ + lines_ptrw[index++] = _position + Vector3(-a.y, 0, -a.x); \ + lines_ptrw[index++] = _position + Vector3(-y, 0, -x); + + PUSH_OCTANT(start_position, a, b) + PUSH_OCTANT(end_position, a, b) +#undef PUSH_OCTANT break; case Vector3::AXIS_Z: - lines.append(start_position + Vector3(a.x, a.y, 0)); - lines.append(start_position + Vector3(b.x, b.y, 0)); - break; - } - } - - // Draw end position search radius - for (int i = 0; i < 30; i++) { - // Create a circle - const float ra = Math::deg_to_rad((float)(i * 12)); - const float rb = Math::deg_to_rad((float)((i + 1) * 12)); - const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; - const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; - - // Draw axis-aligned circle - switch (up_axis) { - case Vector3::AXIS_X: - lines.append(end_position + Vector3(0, a.x, a.y)); - lines.append(end_position + Vector3(0, b.x, b.y)); - break; - case Vector3::AXIS_Y: - lines.append(end_position + Vector3(a.x, 0, a.y)); - lines.append(end_position + Vector3(b.x, 0, b.y)); - break; - case Vector3::AXIS_Z: - lines.append(end_position + Vector3(a.x, a.y, 0)); - lines.append(end_position + Vector3(b.x, b.y, 0)); +#define PUSH_OCTANT(_position, a, b) \ + lines_ptrw[index++] = _position + Vector3(a.x, a.y, 0); \ + lines_ptrw[index++] = _position + Vector3(x, y, 0); \ + lines_ptrw[index++] = _position + Vector3(-a.x, a.y, 0); \ + lines_ptrw[index++] = _position + Vector3(-x, y, 0); \ + lines_ptrw[index++] = _position + Vector3(a.x, -a.y, 0); \ + lines_ptrw[index++] = _position + Vector3(x, -y, 0); \ + lines_ptrw[index++] = _position + Vector3(-a.x, -a.y, 0); \ + lines_ptrw[index++] = _position + Vector3(-x, -y, 0); \ + lines_ptrw[index++] = _position + Vector3(a.y, a.x, 0); \ + lines_ptrw[index++] = _position + Vector3(y, x, 0); \ + lines_ptrw[index++] = _position + Vector3(-a.y, a.x, 0); \ + lines_ptrw[index++] = _position + Vector3(-y, x, 0); \ + lines_ptrw[index++] = _position + Vector3(a.y, -a.x, 0); \ + lines_ptrw[index++] = _position + Vector3(y, -x, 0); \ + lines_ptrw[index++] = _position + Vector3(-a.y, -a.x, 0); \ + lines_ptrw[index++] = _position + Vector3(-y, -x, 0); + + PUSH_OCTANT(start_position, a, b) + PUSH_OCTANT(end_position, a, b) +#undef PUSH_OCTANT break; } + a.x = x; + a.y = y; } const Vector3 link_segment = end_position - start_position; @@ -135,33 +179,30 @@ void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Vector3 anchor = start_position + (link_segment * 0.75); Vector3 direction = start_position.direction_to(end_position); Vector3 arrow_dir = direction.cross(up); - lines.push_back(anchor); - lines.push_back(anchor + (arrow_dir - direction) * arror_len); + lines_ptrw[index++] = anchor; + lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len; arrow_dir = -direction.cross(up); - lines.push_back(anchor); - lines.push_back(anchor + (arrow_dir - direction) * arror_len); + lines_ptrw[index++] = anchor; + lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len; } if (link->is_bidirectional()) { Vector3 anchor = start_position + (link_segment * 0.25); Vector3 direction = end_position.direction_to(start_position); Vector3 arrow_dir = direction.cross(up); - lines.push_back(anchor); - lines.push_back(anchor + (arrow_dir - direction) * arror_len); + lines_ptrw[index++] = anchor; + lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len; arrow_dir = -direction.cross(up); - lines.push_back(anchor); - lines.push_back(anchor + (arrow_dir - direction) * arror_len); + lines_ptrw[index++] = anchor; + lines_ptrw[index++] = anchor + (arrow_dir - direction) * arror_len; } p_gizmo->add_lines(lines, link->is_enabled() ? link_material : link_material_disabled); p_gizmo->add_collision_segments(lines); - Vector handles; - handles.append(start_position); - handles.append(end_position); - p_gizmo->add_handles(handles, handles_material); + p_gizmo->add_handles(Vector({ start_position, end_position }), handles_material); } String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { diff --git a/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp new file mode 100644 index 0000000000..0e00a1a8b3 --- /dev/null +++ b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp @@ -0,0 +1,426 @@ +/**************************************************************************/ +/* spring_bone_3d_gizmo_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_3d_gizmo_plugin.h" + +#include "editor/editor_settings.h" +#include "scene/3d/spring_bone_collision_capsule_3d.h" +#include "scene/3d/spring_bone_collision_plane_3d.h" +#include "scene/3d/spring_bone_collision_sphere_3d.h" + +// SpringBoneSimulator3D + +SpringBoneSimulator3DGizmoPlugin::SelectionMaterials SpringBoneSimulator3DGizmoPlugin::selection_materials; + +SpringBoneSimulator3DGizmoPlugin::SpringBoneSimulator3DGizmoPlugin() { + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref sh; + sh.instantiate(); + sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, POSITION.w, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selection_materials.selected_mat->set_shader(sh); +} + +SpringBoneSimulator3DGizmoPlugin::~SpringBoneSimulator3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + +bool SpringBoneSimulator3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) != nullptr; +} + +String SpringBoneSimulator3DGizmoPlugin::get_gizmo_name() const { + return "SpringBoneSimulator3D"; +} + +int SpringBoneSimulator3DGizmoPlugin::get_priority() const { + return -1; +} + +void SpringBoneSimulator3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SpringBoneSimulator3D *simulator = Object::cast_to(p_gizmo->get_node_3d()); + p_gizmo->clear(); + + if (!simulator->get_setting_count()) { + return; + } + + Skeleton3D *skeleton = simulator->get_skeleton(); + if (!skeleton) { + return; + } + + Ref mesh = get_joints_mesh(skeleton, simulator, p_gizmo->is_selected()); + Transform3D skel_tr = simulator->get_global_transform().inverse() * skeleton->get_global_transform(); + p_gizmo->add_mesh(mesh, Ref(), skel_tr, skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); +} + +Ref SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected) { + Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_joint"); + + Ref surface_tool; + surface_tool.instantiate(); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); + } else { + selection_materials.unselected_mat->set_albedo(bone_color); + surface_tool->set_material(selection_materials.unselected_mat); + } + + LocalVector bones; + LocalVector weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + for (int i = 0; i < p_simulator->get_setting_count(); i++) { + int current_bone = -1; + int prev_bone = -1; + int joint_end = p_simulator->get_joint_count(i) - 1; + for (int j = 0; j <= joint_end; j++) { + current_bone = p_simulator->get_joint_bone(i, j); + if (j > 0) { + Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone); + Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone); + draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color); + draw_sphere(surface_tool, global_pose.basis, global_pose.origin, p_simulator->get_joint_radius(i, j - 1), bone_color); + } + if (j == joint_end && p_simulator->is_end_bone_extended(i) && p_simulator->get_end_bone_length(i) > 0) { + Vector3 axis = p_simulator->get_end_bone_axis(current_bone, p_simulator->get_end_bone_direction(i)); + if (axis.is_zero_approx()) { + continue; + } + bones[0] = current_bone; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone); + axis = global_pose.xform(axis * p_simulator->get_end_bone_length(i)); + draw_line(surface_tool, global_pose.origin, axis, bone_color); + draw_sphere(surface_tool, global_pose.basis, axis, p_simulator->get_joint_radius(i, j), bone_color); + } else { + bones[0] = current_bone; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + } + prev_bone = current_bone; + } + } + + return surface_tool->commit(); +} + +void SpringBoneSimulator3DGizmoPlugin::draw_sphere(Ref &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const float SPPI = Math_TAU / (float)STEP; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * (i % STEP)))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * (i % STEP)))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * (i % STEP)))); + } +} + +void SpringBoneSimulator3DGizmoPlugin::draw_line(Ref &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_begin_pos); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_end_pos); +} + +// SpringBoneCollision3D + +SpringBoneCollision3DGizmoPlugin::SelectionMaterials SpringBoneCollision3DGizmoPlugin::selection_materials; + +SpringBoneCollision3DGizmoPlugin::SpringBoneCollision3DGizmoPlugin() { + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref sh; + sh.instantiate(); + sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, POSITION.w, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selection_materials.selected_mat->set_shader(sh); +} + +SpringBoneCollision3DGizmoPlugin::~SpringBoneCollision3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + +bool SpringBoneCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) != nullptr; +} + +String SpringBoneCollision3DGizmoPlugin::get_gizmo_name() const { + return "SpringBoneCollision3D"; +} + +int SpringBoneCollision3DGizmoPlugin::get_priority() const { + return -1; +} + +void SpringBoneCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SpringBoneCollision3D *collision = Object::cast_to(p_gizmo->get_node_3d()); + p_gizmo->clear(); + + Ref mesh = get_collision_mesh(collision, p_gizmo->is_selected()); + p_gizmo->add_mesh(mesh); +} + +Ref SpringBoneCollision3DGizmoPlugin::get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected) { + Color collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_collision"); + Color inside_collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_inside_collision"); + + Ref surface_tool; + surface_tool.instantiate(); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); + } else { + selection_materials.unselected_mat->set_albedo(collision_color); + surface_tool->set_material(selection_materials.unselected_mat); + } + + SpringBoneCollisionSphere3D *sphere = Object::cast_to(p_collision); + if (sphere) { + draw_sphere(surface_tool, sphere->get_radius(), sphere->is_inside() ? inside_collision_color : collision_color); + return surface_tool->commit(); + } + + SpringBoneCollisionCapsule3D *capsule = Object::cast_to(p_collision); + if (capsule) { + draw_capsule(surface_tool, capsule->get_radius(), capsule->get_height(), capsule->is_inside() ? inside_collision_color : collision_color); + return surface_tool->commit(); + } + + SpringBoneCollisionPlane3D *plane = Object::cast_to(p_collision); + if (plane) { + draw_plane(surface_tool, collision_color); + return surface_tool->commit(); + } + + return surface_tool->commit(); +} + +void SpringBoneCollision3DGizmoPlugin::draw_sphere(Ref &p_surface_tool, float p_radius, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const float SPPI = Math_TAU / (float)STEP; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } +} + +void SpringBoneCollision3DGizmoPlugin::draw_capsule(Ref &p_surface_tool, float p_radius, float p_height, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const int HALF_STEP = 8; + static const float SPPI = Math_TAU / (float)STEP; + static const float HALF_PI = Math_PI * 0.5; + + Vector3 top = VECTOR3_UP * (p_height * 0.5 - p_radius); + Vector3 bottom = -top; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } + LocalVector directions; + directions.resize(4); + directions[0] = VECTOR3_RIGHT; + directions[1] = -VECTOR3_RIGHT; + directions[2] = VECTOR3_FORWARD; + directions[3] = -VECTOR3_FORWARD; + for (int i = 0; i < 4; i++) { + Vector3 dir = directions[i] * p_radius; + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + dir); + p_surface_tool->add_vertex(bottom + dir); + } +} + +void SpringBoneCollision3DGizmoPlugin::draw_plane(Ref &p_surface_tool, const Color &p_color) { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const float HALF_PI = Math_PI * 0.5; + static const float ARROW_LENGTH = 0.3; + static const float ARROW_HALF_WIDTH = 0.05; + static const float ARROW_TOP_HALF_WIDTH = 0.1; + static const float ARROW_TOP = 0.5; + static const float RECT_SIZE = 1.0; + static const int RECT_STEP_COUNT = 9; + static const float RECT_HALF_SIZE = RECT_SIZE * 0.5; + static const float RECT_STEP = RECT_SIZE / (float)RECT_STEP_COUNT; + + p_surface_tool->set_color(p_color); + + // Draw arrow of the normal. + LocalVector arrow; + arrow.resize(7); + arrow[0] = Vector3(0, ARROW_TOP, 0); + arrow[1] = Vector3(-ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[2] = Vector3(-ARROW_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[3] = Vector3(-ARROW_HALF_WIDTH, 0, 0); + arrow[4] = Vector3(ARROW_HALF_WIDTH, 0, 0); + arrow[5] = Vector3(ARROW_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[6] = Vector3(ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0); + for (int i = 0; i < 2; i++) { + Basis ma(VECTOR3_UP, HALF_PI * i); + for (uint32_t j = 0; j < arrow.size(); j++) { + Vector3 v1 = arrow[j]; + Vector3 v2 = arrow[(j + 1) % arrow.size()]; + p_surface_tool->add_vertex(ma.xform(v1)); + p_surface_tool->add_vertex(ma.xform(v2)); + } + } + + // Draw dashed line of the rect. + for (int i = 0; i < 4; i++) { + Basis ma(VECTOR3_UP, HALF_PI * i); + for (int j = 0; j < RECT_STEP_COUNT; j++) { + if (j % 2 == 1) { + continue; + } + Vector3 v1 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * j); + Vector3 v2 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * (j + 1)); + p_surface_tool->add_vertex(ma.xform(v1)); + p_surface_tool->add_vertex(ma.xform(v2)); + } + } +} diff --git a/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h new file mode 100644 index 0000000000..592b5893f6 --- /dev/null +++ b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h @@ -0,0 +1,91 @@ +/**************************************************************************/ +/* spring_bone_3d_gizmo_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_3D_GIZMO_PLUGIN_H +#define SPRING_BONE_3D_GIZMO_PLUGIN_H + +#include "editor/plugins/editor_plugin.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/spring_bone_collision_3d.h" +#include "scene/3d/spring_bone_simulator_3d.h" +#include "scene/resources/surface_tool.h" + +class SpringBoneSimulator3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SpringBoneSimulator3DGizmoPlugin, EditorNode3DGizmoPlugin); + + struct SelectionMaterials { + Ref unselected_mat; + Ref selected_mat; + }; + static SelectionMaterials selection_materials; + +public: + static Ref get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected); + static void draw_sphere(Ref &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color); + static void draw_line(Ref &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color); + + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + SpringBoneSimulator3DGizmoPlugin(); + ~SpringBoneSimulator3DGizmoPlugin(); +}; + +class SpringBoneCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SpringBoneCollision3DGizmoPlugin, EditorNode3DGizmoPlugin); + + struct SelectionMaterials { + Ref unselected_mat; + Ref selected_mat; + }; + static SelectionMaterials selection_materials; + +public: + static Ref get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected); + static void draw_sphere(Ref &p_surface_tool, float p_radius, const Color &p_color); + static void draw_capsule(Ref &p_surface_tool, float p_radius, float p_height, const Color &p_color); + static void draw_plane(Ref &p_surface_tool, const Color &p_color); + + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + SpringBoneCollision3DGizmoPlugin(); + ~SpringBoneCollision3DGizmoPlugin(); +}; + +#endif // SPRING_BONE_3D_GIZMO_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 7b52992d4e..b0704be97a 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -76,6 +76,7 @@ #include "editor/plugins/gizmos/shape_cast_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/soft_body_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.h" +#include "editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/sprite_base_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/vehicle_body_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h" @@ -8574,6 +8575,8 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(ShapeCast3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(SpringArm3DGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(SpringBoneCollision3DGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(SpringBoneSimulator3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(VehicleWheel3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(VisibleOnScreenNotifier3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(GPUParticles3DGizmoPlugin))); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 8bde091ed2..5194dfeb2a 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -988,6 +988,15 @@ void ScriptEditor::_copy_script_path() { } } +void ScriptEditor::_copy_script_uid() { + ScriptEditorBase *se = _get_current_editor(); + if (se) { + Ref scr = se->get_edited_resource(); + ResourceUID::ID uid = ResourceLoader::get_resource_uid(scr->get_path()); + DisplayServer::get_singleton()->clipboard_set(ResourceUID::get_singleton()->id_to_text(uid)); + } +} + void ScriptEditor::_close_other_tabs() { int current_idx = tab_container->get_current_tab(); for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) { @@ -1563,6 +1572,9 @@ void ScriptEditor::_menu_option(int p_option) { case FILE_COPY_PATH: { _copy_script_path(); } break; + case FILE_COPY_UID: { + _copy_script_uid(); + } break; case SHOW_IN_FILE_SYSTEM: { const Ref scr = current->get_edited_resource(); String path = scr->get_path(); @@ -1708,17 +1720,19 @@ bool ScriptEditor::_has_script_tab() const { void ScriptEditor::_prepare_file_menu() { PopupMenu *menu = file_menu->get_popup(); - const bool current_is_doc = _get_current_editor() == nullptr; + ScriptEditorBase *editor = _get_current_editor(); + const Ref res = editor ? editor->get_edited_resource() : Ref(); menu->set_item_disabled(menu->get_item_index(FILE_REOPEN_CLOSED), previous_scripts.is_empty()); - menu->set_item_disabled(menu->get_item_index(FILE_SAVE), current_is_doc); - menu->set_item_disabled(menu->get_item_index(FILE_SAVE_AS), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE), res.is_null()); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE_AS), res.is_null()); menu->set_item_disabled(menu->get_item_index(FILE_SAVE_ALL), !_has_script_tab()); - menu->set_item_disabled(menu->get_item_index(FILE_TOOL_RELOAD_SOFT), current_is_doc); - menu->set_item_disabled(menu->get_item_index(FILE_COPY_PATH), current_is_doc); - menu->set_item_disabled(menu->get_item_index(SHOW_IN_FILE_SYSTEM), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_TOOL_RELOAD_SOFT), res.is_null()); + menu->set_item_disabled(menu->get_item_index(FILE_COPY_PATH), res.is_null() || res->get_path().is_empty()); + menu->set_item_disabled(menu->get_item_index(FILE_COPY_UID), res.is_null() || ResourceLoader::get_resource_uid(res->get_path()) == ResourceUID::INVALID_ID); + menu->set_item_disabled(menu->get_item_index(SHOW_IN_FILE_SYSTEM), res.is_null()); menu->set_item_disabled(menu->get_item_index(WINDOW_PREV), history_pos <= 0); menu->set_item_disabled(menu->get_item_index(WINDOW_NEXT), history_pos >= history.size() - 1); @@ -1728,7 +1742,7 @@ void ScriptEditor::_prepare_file_menu() { menu->set_item_disabled(menu->get_item_index(CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1); menu->set_item_disabled(menu->get_item_index(CLOSE_DOCS), !_has_docs_tab()); - menu->set_item_disabled(menu->get_item_index(FILE_RUN), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_RUN), res.is_null()); } void ScriptEditor::_file_menu_closed() { @@ -3406,6 +3420,9 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_separator(); } context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_path"), FILE_COPY_PATH); + context_menu->set_item_disabled(-1, se->get_edited_resource()->get_path().is_empty()); + context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/copy_uid"), FILE_COPY_UID); + context_menu->set_item_disabled(-1, ResourceLoader::get_resource_uid(se->get_edited_resource()->get_path()) == ResourceUID::INVALID_ID); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/show_in_file_system"), SHOW_IN_FILE_SYSTEM); context_menu->add_separator(); } @@ -4250,6 +4267,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reload_script_soft", TTRC("Soft Reload Tool Script"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::R), FILE_TOOL_RELOAD_SOFT); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_path", TTRC("Copy Script Path")), FILE_COPY_PATH); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/copy_uid", TTRC("Copy Script UID")), FILE_COPY_UID); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/show_in_file_system", TTRC("Show in FileSystem")), SHOW_IN_FILE_SYSTEM); file_menu->get_popup()->add_separator(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 7d90e042c7..99af5c0987 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -255,6 +255,7 @@ class ScriptEditor : public PanelContainer { TOGGLE_SCRIPTS_PANEL, SHOW_IN_FILE_SYSTEM, FILE_COPY_PATH, + FILE_COPY_UID, FILE_TOOL_RELOAD_SOFT, SEARCH_IN_FILES, REPLACE_IN_FILES, @@ -401,6 +402,7 @@ class ScriptEditor : public PanelContainer { void _queue_close_tabs(); void _copy_script_path(); + void _copy_script_uid(); void _ask_close_current_unsaved_tab(ScriptEditorBase *current); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c6be5ce992..2a1588bd38 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -759,7 +759,7 @@ void ScriptTextEditor::_update_bookmark_list() { line = line.substr(0, 50); } - bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - `" + line + "`"); + bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - `" + line + "`"); bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -914,7 +914,7 @@ void ScriptTextEditor::_update_breakpoint_list() { line = line.substr(0, 50); } - breakpoints_menu->add_item(String::num((int)breakpoint_list[i] + 1) + " - `" + line + "`"); + breakpoints_menu->add_item(String::num_int64(breakpoint_list[i] + 1) + " - `" + line + "`"); breakpoints_menu->set_item_metadata(-1, breakpoint_list[i]); } } @@ -1104,6 +1104,11 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { } void ScriptTextEditor::_show_symbol_tooltip(const String &p_symbol, int p_row, int p_column) { + if (p_symbol.begins_with("res://") || p_symbol.begins_with("uid://")) { + EditorHelpBitTooltip::show_tooltip(code_editor->get_text_editor(), "resource||" + p_symbol); + return; + } + Node *base = get_tree()->get_edited_scene_root(); if (base) { base = _find_node_for_script(base, base, script); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 399e17e16e..9c27f0c597 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -40,6 +40,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/gui/editor_file_dialog.h" #include "editor/scene_tree_dock.h" @@ -1311,10 +1312,39 @@ void SpriteFramesEditor::_frame_list_gui_input(const Ref &p_event) { _zoom_out(); // Don't scroll down after zooming out. accept_event(); + } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { + Point2 pos = mb->get_position(); + right_clicked_frame = frame_list->get_item_at_position(pos, true); + if (right_clicked_frame != -1) { + if (!menu) { + menu = memnew(PopupMenu); + add_child(menu); + menu->connect(SceneStringName(id_pressed), callable_mp(this, &SpriteFramesEditor::_menu_selected)); + menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTRC("Show in FileSystem")); + } + + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->popup(); + } } } } +void SpriteFramesEditor::_menu_selected(int p_index) { + switch (p_index) { + case MENU_SHOW_IN_FILESYSTEM: { + String path = frames->get_frame_texture(edited_anim, right_clicked_frame)->get_path(); + // Check if the file is an atlas resource, if it is find the source texture. + Ref at = frames->get_frame_texture(edited_anim, right_clicked_frame); + while (at.is_valid() && at->get_atlas().is_valid()) { + path = at->get_atlas()->get_path(); + at = at->get_atlas(); + } + FileSystemDock::get_singleton()->navigate_to_path(path); + } break; + } +} + void SpriteFramesEditor::_frame_list_item_selected(int p_index, bool p_selected) { if (updating) { return; diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 1e60cb1361..82da5692a5 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -89,6 +89,12 @@ class SpriteFramesEditor : public HSplitContainer { FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT, }; + enum { + MENU_SHOW_IN_FILESYSTEM, + }; + + int right_clicked_frame = -1; + bool read_only = false; Ref autoplay_icon; @@ -139,6 +145,8 @@ class SpriteFramesEditor : public HSplitContainer { AcceptDialog *dialog = nullptr; + PopupMenu *menu = nullptr; + StringName edited_anim; ConfirmationDialog *delete_dialog = nullptr; @@ -221,6 +229,8 @@ class SpriteFramesEditor : public HSplitContainer { void _frame_list_gui_input(const Ref &p_event); void _frame_list_item_selected(int p_index, bool p_selected); + void _menu_selected(int p_index); + void _zoom_in(); void _zoom_out(); void _zoom_reset(); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index a3efc507da..a78d7f8941 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -231,7 +231,7 @@ void TextEditor::_update_bookmark_list() { line = line.substr(0, 50); } - bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); + bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - \"" + line + "\""); bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index 873195a7bd..d24a7ba4cf 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -1077,7 +1077,7 @@ void TextShaderEditor::_update_bookmark_list() { line = line.substr(0, 50); } - bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); + bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - \"" + line + "\""); bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index a3927807be..92e85300bb 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "texture_3d_editor_plugin.h" #include "editor/editor_string_names.h" +#include "editor/plugins/color_channel_selector.h" #include "editor/themes/editor_scale.h" #include "scene/gui/label.h" @@ -46,8 +47,29 @@ constexpr const char *texture_3d_shader = R"( uniform sampler3D tex; uniform float layer; + uniform vec4 u_channel_factors = vec4(1.0); + + vec4 filter_preview_colors(vec4 input_color, vec4 factors) { + // Filter RGB. + vec4 output_color = input_color * vec4(factors.rgb, input_color.a); + + // Remove transparency when alpha is not enabled. + output_color.a = mix(1.0, output_color.a, factors.a); + + // Switch to opaque grayscale when visualizing only one channel. + float csum = factors.r + factors.g + factors.b + factors.a; + float single = clamp(2.0 - csum, 0.0, 1.0); + for (int i = 0; i < 4; i++) { + float c = input_color[i]; + output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single); + } + + return output_color; + } + void fragment() { COLOR = textureLod(tex, vec3(UV, layer), 0.0); + COLOR = filter_preview_colors(COLOR, u_channel_factors); } )"; @@ -96,6 +118,8 @@ void Texture3DEditor::_update_material(bool p_texture_changed) { if (p_texture_changed) { material->set_shader_parameter("tex", texture->get_rid()); } + + material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors()); } void Texture3DEditor::_make_shaders() { @@ -140,30 +164,44 @@ void Texture3DEditor::_update_gui() { layer->set_max(texture->get_depth() - 1); - const String format = Image::get_format_name(texture->get_format()); + const Image::Format format = texture->get_format(); + const String format_name = Image::get_format_name(format); if (texture->has_mipmaps()) { - const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format()); - const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_depth(); + const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format); + const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_depth(); info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"), texture->get_width(), texture->get_height(), texture->get_depth(), - format, + format_name, mip_count, String::humanize_size(memory))); } else { - const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_depth(); + const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_depth(); info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"), texture->get_width(), texture->get_height(), texture->get_depth(), - format, + format_name, String::humanize_size(memory))); } + + const uint32_t components_mask = Image::get_format_component_mask(format); + if (is_power_of_2(components_mask)) { + // Only one channel available, no point in showing a channel selector. + channel_selector->hide(); + } else { + channel_selector->show(); + channel_selector->set_available_channels_mask(components_mask); + } +} + +void Texture3DEditor::on_selected_channels_changed() { + _update_material(false); } void Texture3DEditor::edit(Ref p_texture) { @@ -217,6 +255,11 @@ Texture3DEditor::Texture3DEditor() { add_child(layer); + channel_selector = memnew(ColorChannelSelector); + channel_selector->connect("selected_channels_changed", callable_mp(this, &Texture3DEditor::on_selected_channels_changed)); + channel_selector->set_anchors_preset(Control::PRESET_TOP_LEFT); + add_child(channel_selector); + info = memnew(Label); info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1)); info->add_theme_color_override("font_shadow_color", Color(0, 0, 0)); diff --git a/editor/plugins/texture_3d_editor_plugin.h b/editor/plugins/texture_3d_editor_plugin.h index e2a10ca1dd..582772cb5b 100644 --- a/editor/plugins/texture_3d_editor_plugin.h +++ b/editor/plugins/texture_3d_editor_plugin.h @@ -39,6 +39,8 @@ #include "scene/resources/shader.h" #include "scene/resources/texture.h" +class ColorChannelSelector; + class Texture3DEditor : public Control { GDCLASS(Texture3DEditor, Control); @@ -51,6 +53,8 @@ class Texture3DEditor : public Control { Control *texture_rect = nullptr; + ColorChannelSelector *channel_selector = nullptr; + bool setting = false; void _make_shaders(); @@ -69,6 +73,8 @@ class Texture3DEditor : public Control { void _update_material(bool p_texture_changed); void _update_gui(); + void on_selected_channels_changed(); + protected: void _notification(int p_what); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 938d994a38..b35031271b 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "texture_editor_plugin.h" #include "editor/editor_string_names.h" +#include "editor/plugins/color_channel_selector.h" #include "editor/themes/editor_scale.h" #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/color_rect.h" @@ -43,6 +44,36 @@ #include "scene/resources/compressed_texture.h" #include "scene/resources/image_texture.h" #include "scene/resources/portable_compressed_texture.h" +#include "scene/resources/style_box_flat.h" + +constexpr const char *texture_2d_shader = R"( +shader_type canvas_item; +render_mode blend_mix; + +uniform vec4 u_channel_factors = vec4(1.0); + +vec4 filter_preview_colors(vec4 input_color, vec4 factors) { + // Filter RGB. + vec4 output_color = input_color * vec4(factors.rgb, input_color.a); + + // Remove transparency when alpha is not enabled. + output_color.a = mix(1.0, output_color.a, factors.a); + + // Switch to opaque grayscale when visualizing only one channel. + float csum = factors.r + factors.g + factors.b + factors.a; + float single = clamp(2.0 - csum, 0.0, 1.0); + for (int i = 0; i < 4; i++) { + float c = input_color[i]; + output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single); + } + + return output_color; +} + +void fragment() { + COLOR = filter_preview_colors(texture(TEXTURE, UV), u_channel_factors); +} +)"; TextureRect *TexturePreview::get_texture_display() { return texture_display; @@ -74,8 +105,8 @@ void TexturePreview::_notification(int p_what) { void TexturePreview::_draw_outline() { const float outline_width = Math::round(EDSCALE); - const Rect2 outline_rect = Rect2(Vector2(), texture_display->get_size()).grow(outline_width * 0.5); - texture_display->draw_rect(outline_rect, cached_outline_color, false, outline_width); + const Rect2 outline_rect = Rect2(Vector2(), outline_overlay->get_size()).grow(outline_width * 0.5); + outline_overlay->draw_rect(outline_rect, cached_outline_color, false, outline_width); } void TexturePreview::_update_texture_display_ratio() { @@ -84,25 +115,49 @@ void TexturePreview::_update_texture_display_ratio() { } } -void TexturePreview::_update_metadata_label_text() { - const Ref texture = texture_display->get_texture(); - - String format; - if (Object::cast_to(*texture)) { - format = Image::get_format_name(Object::cast_to(*texture)->get_format()); - } else if (Object::cast_to(*texture)) { - format = Image::get_format_name(Object::cast_to(*texture)->get_format()); - } else { - format = texture->get_class(); +static Image::Format get_texture_2d_format(const Ref &p_texture) { + const Ref image_texture = p_texture; + if (image_texture.is_valid()) { + return image_texture->get_format(); } - const Ref image = texture->get_image(); + const Ref compressed_texture = p_texture; + if (compressed_texture.is_valid()) { + return compressed_texture->get_format(); + } + + // AtlasTexture? + + // Unknown + return Image::FORMAT_MAX; +} + +static int get_texture_mipmaps_count(const Ref &p_texture) { + ERR_FAIL_COND_V(p_texture.is_null(), -1); + // We are having to download the image only to get its mipmaps count. It would be nice if we didn't have to. + Ref image = p_texture->get_image(); if (image.is_valid()) { - const int mipmaps = image->get_mipmap_count(); + return image->get_mipmap_count(); + } + return -1; +} + +void TexturePreview::_update_metadata_label_text() { + const Ref texture = texture_display->get_texture(); + ERR_FAIL_COND(texture.is_null()); + + const Image::Format format = get_texture_2d_format(texture.ptr()); + + const String format_name = format != Image::FORMAT_MAX ? Image::get_format_name(format) : texture->get_class(); + + const Vector2i resolution = texture->get_size(); + const int mipmaps = get_texture_mipmaps_count(texture); + + if (format != Image::FORMAT_MAX) { // Avoid signed integer overflow that could occur with huge texture sizes by casting everything to uint64_t. - uint64_t memory = uint64_t(image->get_width()) * uint64_t(image->get_height()) * uint64_t(Image::get_format_pixel_size(image->get_format())); + uint64_t memory = uint64_t(resolution.x) * uint64_t(resolution.y) * uint64_t(Image::get_format_pixel_size(format)); // Handle VRAM-compressed formats that are stored with 4 bpp. - memory >>= Image::get_format_pixel_rshift(image->get_format()); + memory >>= Image::get_format_pixel_rshift(format); float mipmaps_multiplier = 1.0; float mipmap_increase = 0.25; @@ -119,7 +174,7 @@ void TexturePreview::_update_metadata_label_text() { vformat(String::utf8("%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"), texture->get_width(), texture->get_height(), - format, + format_name, mipmaps, String::humanize_size(memory))); } else { @@ -129,7 +184,7 @@ void TexturePreview::_update_metadata_label_text() { vformat(String::utf8("%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"), texture->get_width(), texture->get_height(), - format, + format_name, String::humanize_size(memory))); } } else { @@ -137,10 +192,14 @@ void TexturePreview::_update_metadata_label_text() { vformat(String::utf8("%d×%d %s"), texture->get_width(), texture->get_height(), - format)); + format_name)); } } +void TexturePreview::on_selected_channels_changed() { + material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors()); +} + TexturePreview::TexturePreview(Ref p_texture, bool p_show_metadata) { set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE); @@ -165,19 +224,48 @@ TexturePreview::TexturePreview(Ref p_texture, bool p_show_metadata) { checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); centering_container->add_child(checkerboard); + { + Ref shader; + shader.instantiate(); + shader->set_code(texture_2d_shader); + + material.instantiate(); + material->set_shader(shader); + material->set_shader_parameter("u_channel_factors", Vector4(1, 1, 1, 1)); + } + texture_display = memnew(TextureRect); texture_display->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS); texture_display->set_texture(p_texture); texture_display->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + texture_display->set_material(material); centering_container->add_child(texture_display); - texture_display->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline)); + // Creating a separate control so it is not affected by the filtering shader. + outline_overlay = memnew(Control); + centering_container->add_child(outline_overlay); + + outline_overlay->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline)); if (p_texture.is_valid()) { _update_texture_display_ratio(); p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_texture_display_ratio)); } + // Null can be passed by `Camera3DPreview` (which immediately after sets a texture anyways). + const Image::Format format = p_texture.is_valid() ? get_texture_2d_format(p_texture.ptr()) : Image::FORMAT_MAX; + const uint32_t components_mask = format != Image::FORMAT_MAX ? Image::get_format_component_mask(format) : 0xf; + + // Add color channel selector at the bottom left if more than 1 channel is available. + if (p_show_metadata && !is_power_of_2(components_mask)) { + channel_selector = memnew(ColorChannelSelector); + channel_selector->connect("selected_channels_changed", callable_mp(this, &TexturePreview::on_selected_channels_changed)); + channel_selector->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); + channel_selector->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + channel_selector->set_available_channels_mask(components_mask); + add_child(channel_selector); + } + if (p_show_metadata) { metadata_label = memnew(Label); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 34b8946976..dd2136ee07 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -41,6 +41,8 @@ class AspectRatioContainer; class ColorRect; class TextureRect; +class ShaderMaterial; +class ColorChannelSelector; class TexturePreview : public MarginContainer { GDCLASS(TexturePreview, MarginContainer); @@ -49,10 +51,14 @@ private: TextureRect *texture_display = nullptr; MarginContainer *margin_container = nullptr; + Control *outline_overlay = nullptr; AspectRatioContainer *centering_container = nullptr; ColorRect *bg_rect = nullptr; TextureRect *checkerboard = nullptr; Label *metadata_label = nullptr; + Ref material; + + ColorChannelSelector *channel_selector = nullptr; Color cached_outline_color; @@ -63,6 +69,8 @@ protected: void _notification(int p_what); void _update_texture_display_ratio(); + void on_selected_channels_changed(); + public: TextureRect *get_texture_display(); TexturePreview(Ref p_texture, bool p_show_metadata); diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 247c472d9c..3e7e5ea24e 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "texture_layered_editor_plugin.h" #include "editor/editor_string_names.h" +#include "editor/plugins/color_channel_selector.h" #include "editor/themes/editor_scale.h" #include "scene/gui/label.h" @@ -45,9 +46,29 @@ constexpr const char *array_2d_shader = R"( uniform sampler2DArray tex; uniform float layer; + uniform vec4 u_channel_factors = vec4(1.0); + + vec4 filter_preview_colors(vec4 input_color, vec4 factors) { + // Filter RGB. + vec4 output_color = input_color * vec4(factors.rgb, input_color.a); + + // Remove transparency when alpha is not enabled. + output_color.a = mix(1.0, output_color.a, factors.a); + + // Switch to opaque grayscale when visualizing only one channel. + float csum = factors.r + factors.g + factors.b + factors.a; + float single = clamp(2.0 - csum, 0.0, 1.0); + for (int i = 0; i < 4; i++) { + float c = input_color[i]; + output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single); + } + + return output_color; + } void fragment() { COLOR = textureLod(tex, vec3(UV, layer), 0.0); + COLOR = filter_preview_colors(COLOR, u_channel_factors); } )"; @@ -60,9 +81,30 @@ constexpr const char *cubemap_shader = R"( uniform vec3 normal; uniform mat3 rot; + uniform vec4 u_channel_factors = vec4(1.0); + + vec4 filter_preview_colors(vec4 input_color, vec4 factors) { + // Filter RGB. + vec4 output_color = input_color * vec4(factors.rgb, input_color.a); + + // Remove transparency when alpha is not enabled. + output_color.a = mix(1.0, output_color.a, factors.a); + + // Switch to opaque grayscale when visualizing only one channel. + float csum = factors.r + factors.g + factors.b + factors.a; + float single = clamp(2.0 - csum, 0.0, 1.0); + for (int i = 0; i < 4; i++) { + float c = input_color[i]; + output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single); + } + + return output_color; + } + void fragment() { vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z)); COLOR = textureLod(tex, n, 0.0); + COLOR = filter_preview_colors(COLOR, u_channel_factors); } )"; @@ -75,9 +117,30 @@ constexpr const char *cubemap_array_shader = R"( uniform mat3 rot; uniform float layer; + uniform vec4 u_channel_factors = vec4(1.0); + + vec4 filter_preview_colors(vec4 input_color, vec4 factors) { + // Filter RGB. + vec4 output_color = input_color * vec4(factors.rgb, input_color.a); + + // Remove transparency when alpha is not enabled. + output_color.a = mix(1.0, output_color.a, factors.a); + + // Switch to opaque grayscale when visualizing only one channel. + float csum = factors.r + factors.g + factors.b + factors.a; + float single = clamp(2.0 - csum, 0.0, 1.0); + for (int i = 0; i < 4; i++) { + float c = input_color[i]; + output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single); + } + + return output_color; + } + void fragment() { vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z)); COLOR = textureLod(tex, vec4(n, layer), 0.0); + COLOR = filter_preview_colors(COLOR, u_channel_factors); } )"; @@ -104,7 +167,8 @@ void TextureLayeredEditor::_update_gui() { _texture_rect_update_area(); - const String format = Image::get_format_name(texture->get_format()); + const Image::Format format = texture->get_format(); + const String format_name = Image::get_format_name(format); String texture_info; switch (texture->get_layered_type()) { @@ -115,7 +179,7 @@ void TextureLayeredEditor::_update_gui() { texture->get_width(), texture->get_height(), texture->get_layers(), - format); + format_name); } break; case TextureLayered::LAYERED_TYPE_CUBEMAP: { @@ -124,7 +188,7 @@ void TextureLayeredEditor::_update_gui() { texture_info = vformat(String::utf8("%d×%d %s\n"), texture->get_width(), texture->get_height(), - format); + format_name); } break; case TextureLayered::LAYERED_TYPE_CUBEMAP_ARRAY: { @@ -134,7 +198,7 @@ void TextureLayeredEditor::_update_gui() { texture->get_width(), texture->get_height(), texture->get_layers() / 6, - format); + format_name); } break; @@ -143,21 +207,30 @@ void TextureLayeredEditor::_update_gui() { } if (texture->has_mipmaps()) { - const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format()); - const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_layers(); + const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format); + const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_layers(); texture_info += vformat(TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"), mip_count, String::humanize_size(memory)); } else { - const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_layers(); + const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_layers(); texture_info += vformat(TTR("No Mipmaps") + "\n" + TTR("Memory: %s"), String::humanize_size(memory)); } info->set_text(texture_info); + + const uint32_t components_mask = Image::get_format_component_mask(format); + if (is_power_of_2(components_mask)) { + // Only one channel available, no point in showing a channel selector. + channel_selector->hide(); + } else { + channel_selector->show(); + channel_selector->set_available_channels_mask(components_mask); + } } void TextureLayeredEditor::_notification(int p_what) { @@ -214,6 +287,15 @@ void TextureLayeredEditor::_update_material(bool p_texture_changed) { if (p_texture_changed) { materials[texture->get_layered_type()]->set_shader_parameter("tex", texture->get_rid()); } + + const Vector4 channel_factors = channel_selector->get_selected_channel_factors(); + for (unsigned int i = 0; i < 3; ++i) { + materials[i]->set_shader_parameter("u_channel_factors", channel_factors); + } +} + +void TextureLayeredEditor::on_selected_channels_changed() { + _update_material(false); } void TextureLayeredEditor::_make_shaders() { @@ -311,6 +393,11 @@ TextureLayeredEditor::TextureLayeredEditor() { add_child(layer); + channel_selector = memnew(ColorChannelSelector); + channel_selector->connect("selected_channels_changed", callable_mp(this, &TextureLayeredEditor::on_selected_channels_changed)); + channel_selector->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT); + add_child(channel_selector); + info = memnew(Label); info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1)); info->add_theme_color_override("font_shadow_color", Color(0, 0, 0)); diff --git a/editor/plugins/texture_layered_editor_plugin.h b/editor/plugins/texture_layered_editor_plugin.h index 20c0db0aed..781d023da3 100644 --- a/editor/plugins/texture_layered_editor_plugin.h +++ b/editor/plugins/texture_layered_editor_plugin.h @@ -39,6 +39,8 @@ #include "scene/resources/shader.h" #include "scene/resources/texture.h" +class ColorChannelSelector; + class TextureLayeredEditor : public Control { GDCLASS(TextureLayeredEditor, Control); @@ -55,6 +57,8 @@ class TextureLayeredEditor : public Control { bool setting = false; + ColorChannelSelector *channel_selector = nullptr; + void _make_shaders(); void _update_material(bool p_texture_changed); @@ -71,6 +75,8 @@ class TextureLayeredEditor : public Control { void _update_gui(); + void on_selected_channels_changed(); + protected: void _notification(int p_what); virtual void gui_input(const Ref &p_event) override; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index f9a7fc564e..54d335eeba 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -46,6 +46,7 @@ #include "editor/gui/editor_bottom_panel.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" +#include "scene/gui/flow_container.h" #include "scene/gui/separator.h" #define CHECK_PLUGIN_INITIALIZED() \ @@ -1302,7 +1303,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { version_commit_dock->add_child(memnew(HSeparator)); - HBoxContainer *menu_bar = memnew(HBoxContainer); + HFlowContainer *menu_bar = memnew(HFlowContainer); menu_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL); menu_bar->set_v_size_flags(Control::SIZE_FILL); version_commit_dock->add_child(menu_bar); @@ -1310,6 +1311,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { branch_select = memnew(OptionButton); branch_select->set_tooltip_text(TTR("Branches")); branch_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_select->set_clip_text(true); branch_select->connect(SceneStringName(item_selected), callable_mp(this, &VersionControlEditorPlugin::_branch_item_selected)); branch_select->connect(SceneStringName(pressed), callable_mp(this, &VersionControlEditorPlugin::_refresh_branch_list)); menu_bar->add_child(branch_select); @@ -1355,6 +1357,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { remote_select = memnew(OptionButton); remote_select->set_tooltip_text(TTR("Remotes")); remote_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_select->set_clip_text(true); remote_select->connect(SceneStringName(item_selected), callable_mp(this, &VersionControlEditorPlugin::_remote_selected)); remote_select->connect(SceneStringName(pressed), callable_mp(this, &VersionControlEditorPlugin::_refresh_remote_list)); menu_bar->add_child(remote_select); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index efa05c811e..39cf55e806 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -5906,24 +5906,49 @@ void VisualShaderEditor::_varying_create() { add_varying_dialog->hide(); } -void VisualShaderEditor::_varying_name_changed(const String &p_name) { - if (!p_name.is_valid_ascii_identifier()) { - varying_error_label->show(); - varying_error_label->set_text(TTR("Invalid name for varying.")); - add_varying_dialog->get_ok_button()->set_disabled(true); - return; +void VisualShaderEditor::_varying_validate() { + bool has_error = false; + String error; + String varname = varying_name->get_text(); + + if (!varname.is_valid_ascii_identifier()) { + error += TTR("Invalid name for varying."); + has_error = true; + } else if (visual_shader->has_varying(varname)) { + error += TTR("Varying with that name is already exist."); + has_error = true; } - if (visual_shader->has_varying(p_name)) { - varying_error_label->show(); - varying_error_label->set_text(TTR("Varying with that name is already exist.")); - add_varying_dialog->get_ok_button()->set_disabled(true); - return; + + if (varying_type->get_selected() == 6 && varying_mode->get_selected() == VisualShader::VaryingMode::VARYING_MODE_VERTEX_TO_FRAG_LIGHT) { + if (has_error) { + error += "\n"; + } + error += vformat(TTR("Boolean type cannot be used with `%s` varying mode."), "Vertex -> [Fragment, Light]"); + has_error = true; } - if (varying_error_label->is_visible()) { + + if (has_error) { + varying_error_label->show(); + varying_error_label->set_text(error); + add_varying_dialog->get_ok_button()->set_disabled(true); + } else { varying_error_label->hide(); - add_varying_dialog->set_size(Size2(add_varying_dialog->get_size().x, 0)); + varying_error_label->set_text(""); + add_varying_dialog->get_ok_button()->set_disabled(false); } - add_varying_dialog->get_ok_button()->set_disabled(false); + add_varying_dialog->reset_size(); +} + +void VisualShaderEditor::_varying_type_changed(int p_index) { + _varying_validate(); +} + +void VisualShaderEditor::_varying_mode_changed(int p_index) { + _varying_validate(); +} + +void VisualShaderEditor::_varying_name_changed(const String &p_name) { + _varying_validate(); } void VisualShaderEditor::_varying_deleted() { @@ -6796,6 +6821,7 @@ VisualShaderEditor::VisualShaderEditor() { varying_type->add_item("Vector4"); varying_type->add_item("Boolean"); varying_type->add_item("Transform"); + varying_type->connect(SceneStringName(item_selected), callable_mp(this, &VisualShaderEditor::_varying_type_changed)); varying_name = memnew(LineEdit); hb->add_child(varying_name); @@ -6808,6 +6834,7 @@ VisualShaderEditor::VisualShaderEditor() { hb->add_child(varying_mode); varying_mode->add_item("Vertex -> [Fragment, Light]"); varying_mode->add_item("Fragment -> Light"); + varying_mode->connect(SceneStringName(item_selected), callable_mp(this, &VisualShaderEditor::_varying_mode_changed)); varying_error_label = memnew(Label); vb->add_child(varying_error_label); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index d27ade9620..fea3432c00 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -597,6 +597,9 @@ class VisualShaderEditor : public ShaderEditor { void _member_cancel(); void _varying_create(); + void _varying_validate(); + void _varying_type_changed(int p_index); + void _varying_mode_changed(int p_index); void _varying_name_changed(const String &p_name); void _varying_deleted(); void _varying_selected(); diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index c57ec09aff..27dbc02d56 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -37,6 +37,8 @@ #include "editor/editor_node.h" #include "editor/themes/editor_scale.h" #include "main/main.h" +#include "scene/gui/panel_container.h" +#include "scene/main/window.h" #include "servers/display_server.h" void BackgroundProgress::_add_task(const String &p_task, const String &p_label, int p_steps) { @@ -128,6 +130,21 @@ void BackgroundProgress::end_task(const String &p_task) { ProgressDialog *ProgressDialog::singleton = nullptr; +void ProgressDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + Ref style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu")); + main_border_size = style->get_minimum_size(); + main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT)); + main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT)); + main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP)); + main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM)); + + center_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), "PopupPanel")); + } break; + } +} + void ProgressDialog::_update_ui() { // Run main loop for two frames. if (is_inside_tree()) { @@ -137,33 +154,33 @@ void ProgressDialog::_update_ui() { } void ProgressDialog::_popup() { + // Activate processing of all inputs in EditorNode, and the EditorNode::input method + // will discard every key input. + EditorNode::get_singleton()->set_process_input(true); + // Disable all other windows to prevent interaction with them. + for (Window *w : host_windows) { + w->set_process_mode(PROCESS_MODE_DISABLED); + } + Size2 ms = main->get_combined_minimum_size(); ms.width = MAX(500 * EDSCALE, ms.width); + ms += main_border_size; - Ref style = main->get_theme_stylebox(SceneStringName(panel), SNAME("PopupMenu")); - ms += style->get_minimum_size(); + center_panel->set_custom_minimum_size(ms); - main->set_offset(SIDE_LEFT, style->get_margin(SIDE_LEFT)); - main->set_offset(SIDE_RIGHT, -style->get_margin(SIDE_RIGHT)); - main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP)); - main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM)); - - if (is_inside_tree()) { - Rect2i adjust = _popup_adjust_rect(); - if (adjust != Rect2i()) { - set_position(adjust.position); - set_size(adjust.size); - } - } else { - for (Window *window : host_windows) { - if (window->has_focus()) { - popup_exclusive_centered(window, ms); - return; - } - } - // No host window found, use main window. - EditorInterface::get_singleton()->popup_dialog_centered(this, ms); + Window *current_window = Window::get_from_id(DisplayServer::get_singleton()->get_focused_window()); + if (!current_window) { + current_window = get_tree()->get_root(); } + + reparent(current_window); + + // Ensures that events are properly released before the dialog blocks input. + bool window_is_input_disabled = current_window->is_input_disabled(); + current_window->set_disable_input(!window_is_input_disabled); + current_window->set_disable_input(window_is_input_disabled); + + show(); } void ProgressDialog::add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) { @@ -233,6 +250,10 @@ void ProgressDialog::end_task(const String &p_task) { if (tasks.is_empty()) { hide(); + EditorNode::get_singleton()->set_process_input(false); + for (Window *w : host_windows) { + w->set_process_mode(PROCESS_MODE_INHERIT); + } } else { _popup(); } @@ -243,17 +264,31 @@ void ProgressDialog::add_host_window(Window *p_window) { host_windows.push_back(p_window); } +void ProgressDialog::remove_host_window(Window *p_window) { + ERR_FAIL_NULL(p_window); + host_windows.erase(p_window); +} + void ProgressDialog::_cancel_pressed() { canceled = true; } ProgressDialog::ProgressDialog() { - main = memnew(VBoxContainer); - add_child(main); - main->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - set_exclusive(true); - set_flag(Window::FLAG_POPUP, false); + // We want to cover the entire screen to prevent the user from interacting with the Editor. + set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + // Be sure it's the top most component. + set_z_index(RS::CANVAS_ITEM_Z_MAX); singleton = this; + hide(); + + center_panel = memnew(PanelContainer); + add_child(center_panel); + center_panel->set_h_size_flags(SIZE_SHRINK_BEGIN); + center_panel->set_v_size_flags(SIZE_SHRINK_BEGIN); + + main = memnew(VBoxContainer); + center_panel->add_child(main); + cancel_hb = memnew(HBoxContainer); main->add_child(cancel_hb); cancel_hb->hide(); diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h index d4987e2474..d24638a1df 100644 --- a/editor/progress_dialog.h +++ b/editor/progress_dialog.h @@ -35,8 +35,8 @@ #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/center_container.h" #include "scene/gui/label.h" -#include "scene/gui/popup.h" #include "scene/gui/progress_bar.h" class BackgroundProgress : public HBoxContainer { @@ -66,8 +66,10 @@ public: BackgroundProgress() {} }; -class ProgressDialog : public PopupPanel { - GDCLASS(ProgressDialog, PopupPanel); +class PanelContainer; + +class ProgressDialog : public CenterContainer { + GDCLASS(ProgressDialog, CenterContainer); struct Task { String task; VBoxContainer *vb = nullptr; @@ -79,10 +81,13 @@ class ProgressDialog : public PopupPanel { Button *cancel = nullptr; HashMap tasks; + PanelContainer *center_panel = nullptr; VBoxContainer *main = nullptr; LocalVector host_windows; + Size2 main_border_size; + static ProgressDialog *singleton; void _popup(); @@ -91,6 +96,9 @@ class ProgressDialog : public PopupPanel { void _update_ui(); bool canceled = false; +protected: + void _notification(int p_what); + public: static ProgressDialog *get_singleton() { return singleton; } void add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel = false); @@ -98,6 +106,7 @@ public: void end_task(const String &p_task); void add_host_window(Window *p_window); + void remove_host_window(Window *p_window); ProgressDialog(); }; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index dba7200bb7..c5e653c5b6 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -151,7 +151,7 @@ void ProjectManager::_build_icon_type_cache(Ref p_theme) { // Main layout. -void ProjectManager::_update_size_limits() { +void ProjectManager::_update_size_limits(bool p_custom_res) { const Size2 minimum_size = Size2(720, 450) * EDSCALE; const Size2 default_size = Size2(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT) * EDSCALE; @@ -161,20 +161,21 @@ void ProjectManager::_update_size_limits() { // Calling Window methods this early doesn't sync properties with DS. w->set_min_size(minimum_size); DisplayServer::get_singleton()->window_set_min_size(minimum_size); - if (DisplayServer::get_singleton()->window_get_size() == default_size) { + if (!p_custom_res) { // Only set window size if it currently matches the default, which is defined in `main/main.cpp`. // This allows CLI arguments to override the window size. w->set_size(default_size); DisplayServer::get_singleton()->window_set_size(default_size); } } + Size2 real_size = DisplayServer::get_singleton()->window_get_size(); Rect2i screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(DisplayServer::get_singleton()->window_get_current_screen()); if (screen_rect.size != Vector2i()) { // Center the window on the screen. Vector2i window_position; - window_position.x = screen_rect.position.x + (screen_rect.size.x - default_size.x) / 2; - window_position.y = screen_rect.position.y + (screen_rect.size.y - default_size.y) / 2; + window_position.x = screen_rect.position.x + (screen_rect.size.x - real_size.x) / 2; + window_position.y = screen_rect.position.y + (screen_rect.size.y - real_size.y) / 2; DisplayServer::get_singleton()->window_set_position(window_position); // Limit popup menus to prevent unusably long lists. @@ -614,7 +615,13 @@ void ProjectManager::_open_selected_projects_check_warnings() { } void ProjectManager::_open_selected_projects_check_recovery_mode() { - ProjectList::Item project = project_list->get_selected_projects()[0]; + Vector selected_projects = project_list->get_selected_projects(); + + if (selected_projects.is_empty()) { + return; + } + + const ProjectList::Item &project = selected_projects[0]; if (project.missing) { return; } @@ -1154,7 +1161,7 @@ void ProjectManager::_titlebar_resized() { // Object methods. -ProjectManager::ProjectManager() { +ProjectManager::ProjectManager(bool p_custom_res) { singleton = this; set_translation_domain("godot.editor"); @@ -1742,7 +1749,7 @@ ProjectManager::ProjectManager() { title_bar->connect(SceneStringName(item_rect_changed), callable_mp(this, &ProjectManager::_titlebar_resized)); } - _update_size_limits(); + _update_size_limits(p_custom_res); } ProjectManager::~ProjectManager() { diff --git a/editor/project_manager.h b/editor/project_manager.h index bfe1899725..ae3784634b 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -72,7 +72,7 @@ class ProjectManager : public Control { Ref theme; - void _update_size_limits(); + void _update_size_limits(bool p_custom_res); void _update_theme(bool p_skip_creation = false); void _titlebar_resized(); @@ -264,7 +264,7 @@ public: void add_new_tag(const String &p_tag); - ProjectManager(); + ProjectManager(bool p_custom_res); ~ProjectManager(); }; diff --git a/editor/project_manager/quick_settings_dialog.cpp b/editor/project_manager/quick_settings_dialog.cpp index 3ebbf76653..0b537c9750 100644 --- a/editor/project_manager/quick_settings_dialog.cpp +++ b/editor/project_manager/quick_settings_dialog.cpp @@ -99,6 +99,7 @@ void QuickSettingsDialog::_update_current_values() { if (current_theme == theme_value) { theme_option_button->set_text(current_theme); theme_option_button->select(i); + theme_option_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); custom_theme_label->set_visible(current_theme == "Custom"); } diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 8e7705ff4a..5d8212567c 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -338,6 +338,7 @@ WindowWrapper::WindowWrapper() { } window = memnew(Window); + window_id = window->get_instance_id(); window->set_wrap_controls(true); add_child(window); @@ -356,6 +357,12 @@ WindowWrapper::WindowWrapper() { ProgressDialog::get_singleton()->add_host_window(window); } +WindowWrapper::~WindowWrapper() { + if (ObjectDB::get_instance(window_id)) { + ProgressDialog::get_singleton()->remove_host_window(window); + } +} + // ScreenSelect void ScreenSelect::_build_advanced_menu() { diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h index 76922e9d45..ccb61c7090 100644 --- a/editor/window_wrapper.h +++ b/editor/window_wrapper.h @@ -46,6 +46,7 @@ class WindowWrapper : public MarginContainer { Control *wrapped_control = nullptr; MarginContainer *margins = nullptr; Window *window = nullptr; + ObjectID window_id; Panel *window_background = nullptr; @@ -86,6 +87,7 @@ public: void grab_window_focus(); WindowWrapper(); + ~WindowWrapper(); }; class ScreenSelect : public Button { diff --git a/main/main.cpp b/main/main.cpp index 9536dcebc4..1ccf2d9a4a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -232,6 +232,8 @@ static bool init_use_custom_pos = false; static bool init_use_custom_screen = false; static Vector2 init_custom_pos; static int64_t init_embed_parent_window_id = 0; +static bool use_custom_res = true; +static bool force_res = false; // Debug @@ -1021,8 +1023,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph String remotefs_pass; Vector breakpoints; - bool use_custom_res = true; - bool force_res = false; bool delta_smoothing_override = false; String default_renderer = ""; @@ -4320,7 +4320,7 @@ int Main::start() { translation_server->get_editor_domain()->set_pseudolocalization_enabled(true); } - ProjectManager *pmanager = memnew(ProjectManager); + ProjectManager *pmanager = memnew(ProjectManager(force_res || use_custom_res)); ProgressDialog *progress_dialog = memnew(ProgressDialog); pmanager->add_child(progress_dialog); diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index 212ed38afc..447751bf48 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 054F8BE52D38852F00B81423 /* MetalFX.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 1F1575721F582BE20003B888 /* dylibs in Resources */ = {isa = PBXBuildFile; fileRef = 1F1575711F582BE20003B888 /* dylibs */; }; DEADBEEF2F582BE20003B888 /* $binary.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEADBEEF1F582BE20003B888 /* $binary.xcframework */; }; $modules_buildfile @@ -42,6 +43,7 @@ 1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = ""; }; 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = ""; }; 9039D3BD24C093AC0020482C /* MoltenVK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MoltenVK; path = MoltenVK.xcframework; sourceTree = ""; }; + 054F8BE52D38852F00B81423 /* MetalFX.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalFX.framework; path = System/Library/Frameworks/MetalFX.framework; sourceTree = SDKROOT; }; D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; }; D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = ""; }; @@ -60,6 +62,7 @@ buildActionMask = 2147483647; files = ( 9039D3BE24C093AC0020482C /* MoltenVK.xcframework in Frameworks */, + 054F8BE62D38852F00B81423 /* MetalFX.framework in Frameworks */, DEADBEEF2F582BE20003B888 /* $binary.xcframework */, $modules_buildphase $additional_pbx_frameworks_build @@ -94,6 +97,7 @@ isa = PBXGroup; children = ( 9039D3BD24C093AC0020482C /* MoltenVK.xcframework */, + 054F8BE52D38852F00B81423 /* MetalFX.framework */, DEADBEEF1F582BE20003B888 /* $binary.xcframework */, $modules_buildgrp $additional_pbx_frameworks_refs diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 5a27302a12..7b5889f464 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -217,6 +217,13 @@ Validate extension JSON: Error: Field 'classes/Control/properties/offset_top': t Property type changed to float to match the actual internal API and documentation. +GH-99455 +-------- +Validate extension JSON: Error: Field 'classes/RenderingServer/methods/multimesh_allocate_data/arguments': size changed value in new API, from 5 to 6. + +Optional argument added to allow setting indirect draw mode on Multimesh. Compatibility method registered. + + GH-100129 --------- Validate extension JSON: Error: Field 'classes/NavigationServer2D/methods/query_path': is_const changed value in new API, from true to false. @@ -268,3 +275,37 @@ GH-98441 Validate extension JSON: Error: Field 'global_enums/KeyModifierMask/values/KEY_MODIFIER_MASK': value changed value in new API, from 5.32677e+08 to 2130706432. Key modifier mask value corrected. API change documented for compatibility. + + +GH-92089 +-------- +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/CPUParticles2D/methods/restart': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/CPUParticles3D/methods/restart': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/GPUParticles2D/methods/restart': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/GPUParticles3D/methods/restart': arguments + +Added an optional keep_seed parameter to restart particles, to avoid modifying the seed to do particle seeking. + + +GH-101482 +--------- +Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/set_table_column_expand/arguments': size changed value in new API, from 3 to 4. + +Added optional "shrink" argument. Compatibility method registered. + + +GH-100062 +-------- +Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/index_buffer_create/arguments': size changed value in new API, from 4 to 5. +Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/uniform_buffer_create/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/vertex_buffer_create/arguments': size changed value in new API, from 3 to 4. + +Optional argument added. Compatibility methods registered. + + +GH-101531 +--------- +Validate extension JSON: API was removed: classes/EditorSceneFormatImporter/methods/_get_import_flags + +This virtual method, and the internal public `get_import_flags`, were never used by the engine, since it was open sourced. +So we're removing it despite the compat breakage as there's no way for users to rely on this affecting engine behavior. diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub index a872e1cd03..e6ecb2bc83 100644 --- a/modules/etcpak/SCsub +++ b/modules/etcpak/SCsub @@ -12,6 +12,7 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/etcpak/" thirdparty_sources = [ + "DecodeRGB.cpp", "Dither.cpp", "ProcessDxtc.cpp", "ProcessRGB.cpp", diff --git a/modules/etcpak/config.py b/modules/etcpak/config.py index eb565b85b9..d22f9454ed 100644 --- a/modules/etcpak/config.py +++ b/modules/etcpak/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return env.editor_build + return True def configure(env): diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 7e6381946a..1858bc7716 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -32,6 +32,8 @@ #include "image_compress_etcpak.h" +#ifdef TOOLS_ENABLED + #include "core/os/os.h" #include "core/string/print_string.h" @@ -305,3 +307,4 @@ void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) { print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } +#endif // TOOLS_ENABLED diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index 93c9e86eb0..8d433b12a5 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -33,6 +33,8 @@ #ifndef IMAGE_COMPRESS_ETCPAK_H #define IMAGE_COMPRESS_ETCPAK_H +#ifdef TOOLS_ENABLED + #include "core/io/image.h" enum class EtcpakType { @@ -55,4 +57,6 @@ void _compress_bc(Image *r_img, Image::UsedChannels p_channels); void _compress_etcpak(EtcpakType p_compress_type, Image *r_img); +#endif // TOOLS_ENABLED + #endif // IMAGE_COMPRESS_ETCPAK_H diff --git a/modules/etcpak/image_decompress_etcpak.cpp b/modules/etcpak/image_decompress_etcpak.cpp new file mode 100644 index 0000000000..a459ed4efb --- /dev/null +++ b/modules/etcpak/image_decompress_etcpak.cpp @@ -0,0 +1,199 @@ +/**************************************************************************/ +/* image_decompress_etcpak.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "image_decompress_etcpak.h" + +#include "core/os/os.h" +#include "core/string/print_string.h" + +#include + +#define ETCPAK_R_BLOCK_SIZE 8 +#define ETCPAK_RG_BLOCK_SIZE 16 +#define ETCPAK_RGB_BLOCK_SIZE 8 +#define ETCPAK_RGBA_BLOCK_SIZE 16 + +static void decompress_image(EtcpakFormat format, const void *src, void *dst, const uint64_t width, const uint64_t height) { + const uint8_t *src_blocks = reinterpret_cast(src); + uint8_t *dec_blocks = reinterpret_cast(dst); + +#define DECOMPRESS_LOOP(m_func, m_block_size, m_color_bytesize) \ + for (uint64_t y = 0; y < height; y += 4) { \ + for (uint64_t x = 0; x < width; x += 4) { \ + m_func(&src_blocks[src_pos], &dec_blocks[dst_pos], width); \ + src_pos += m_block_size; \ + dst_pos += 4 * m_color_bytesize; \ + } \ + dst_pos += 3 * width * m_color_bytesize; \ + } + +#define DECOMPRESS_LOOP_SAFE(m_func, m_block_size, m_color_bytesize, m_output) \ + for (uint64_t y = 0; y < height; y += 4) { \ + for (uint64_t x = 0; x < width; x += 4) { \ + const uint32_t yblock = MIN(height - y, 4ul); \ + const uint32_t xblock = MIN(width - x, 4ul); \ + \ + const bool incomplete = yblock < 4 && xblock < 4; \ + uint8_t *dec_out = incomplete ? m_output : &dec_blocks[y * 4 * width + x * m_color_bytesize]; \ + \ + m_func(&src_blocks[src_pos], dec_out, incomplete ? 4 : width); \ + src_pos += m_block_size; \ + \ + if (incomplete) { \ + for (uint32_t cy = 0; cy < yblock; cy++) { \ + for (uint32_t cx = 0; cx < xblock; cx++) { \ + memcpy(&dec_blocks[(y + cy) * 4 * width + (x + cx) * m_color_bytesize], &m_output[cy * 4 + cx * m_color_bytesize], m_color_bytesize); \ + } \ + } \ + } \ + } \ + } + + if (width % 4 != 0 || height % 4 != 0) { + uint64_t src_pos = 0; + + uint8_t rgba8_output[4 * 4 * 4]; + + switch (format) { + case Etcpak_R: { + DECOMPRESS_LOOP_SAFE(DecodeRBlock, ETCPAK_R_BLOCK_SIZE, 4, rgba8_output) + } break; + case Etcpak_RG: { + DECOMPRESS_LOOP_SAFE(DecodeRGBlock, ETCPAK_RG_BLOCK_SIZE, 4, rgba8_output) + } break; + case Etcpak_RGB: { + DECOMPRESS_LOOP_SAFE(DecodeRGBBlock, ETCPAK_RGB_BLOCK_SIZE, 4, rgba8_output) + } break; + case Etcpak_RGBA: { + DECOMPRESS_LOOP_SAFE(DecodeRGBABlock, ETCPAK_RGBA_BLOCK_SIZE, 4, rgba8_output) + } break; + } + + } else { + uint64_t src_pos = 0, dst_pos = 0; + + switch (format) { + case Etcpak_R: { + DECOMPRESS_LOOP(DecodeRBlock, ETCPAK_R_BLOCK_SIZE, 4) + } break; + case Etcpak_RG: { + DECOMPRESS_LOOP(DecodeRGBlock, ETCPAK_RG_BLOCK_SIZE, 4) + } break; + case Etcpak_RGB: { + DECOMPRESS_LOOP(DecodeRGBBlock, ETCPAK_RGB_BLOCK_SIZE, 4) + } break; + case Etcpak_RGBA: { + DECOMPRESS_LOOP(DecodeRGBABlock, ETCPAK_RGBA_BLOCK_SIZE, 4) + } break; + } + } + +#undef DECOMPRESS_LOOP +#undef DECOMPRESS_LOOP_SAFE +} + +void _decompress_etc(Image *p_image) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + + int width = p_image->get_width(); + int height = p_image->get_height(); + + // Compressed images' dimensions should be padded to the upper multiple of 4. + // If they aren't, they need to be realigned (the actual data is correctly padded though). + if (width % 4 != 0 || height % 4 != 0) { + int new_width = width + (4 - (width % 4)); + int new_height = height + (4 - (height % 4)); + + print_verbose(vformat("Compressed image (%s) has dimensions are not multiples of 4 (%dx%d), aligning to (%dx%d)", p_image->get_path(), width, height, new_width, new_height)); + + width = new_width; + height = new_height; + } + + Image::Format source_format = p_image->get_format(); + Image::Format target_format = Image::FORMAT_RGBA8; + + EtcpakFormat etcpak_format = Etcpak_R; + + switch (source_format) { + case Image::FORMAT_ETC: + case Image::FORMAT_ETC2_RGB8: + etcpak_format = Etcpak_RGB; + break; + + case Image::FORMAT_ETC2_RGBA8: + case Image::FORMAT_ETC2_RA_AS_RG: + etcpak_format = Etcpak_RGBA; + break; + + case Image::FORMAT_ETC2_R11: + etcpak_format = Etcpak_R; + break; + + case Image::FORMAT_ETC2_RG11: + etcpak_format = Etcpak_RG; + break; + + default: + ERR_FAIL_MSG(vformat("etcpak: Can't decompress image %s with an unknown format: %s.", p_image->get_path(), Image::get_format_name(source_format))); + break; + } + + int mm_count = p_image->get_mipmap_count(); + int64_t target_size = Image::get_image_data_size(width, height, target_format, p_image->has_mipmaps()); + + // Decompressed data. + Vector data; + data.resize(target_size); + uint8_t *wb = data.ptrw(); + + // Source data. + const uint8_t *rb = p_image->ptr(); + + // Decompress mipmaps. + for (int i = 0; i <= mm_count; i++) { + int mipmap_w = 0, mipmap_h = 0; + int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, source_format, i, mipmap_w, mipmap_h); + int64_t dst_ofs = Image::get_image_mipmap_offset(width, height, target_format, i); + decompress_image(etcpak_format, rb + src_ofs, wb + dst_ofs, mipmap_w, mipmap_h); + } + + p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); + + // Swap channels if the format is using a channel swizzle. + if (source_format == Image::FORMAT_ETC2_RA_AS_RG) { + p_image->convert_ra_rgba8_to_rg(); + } + + print_verbose(vformat("etcpak: Decompression of %dx%d %s image %s with %d mipmaps took %d ms.", + p_image->get_width(), p_image->get_height(), Image::get_format_name(source_format), p_image->get_path(), p_image->get_mipmap_count(), OS::get_singleton()->get_ticks_msec() - start_time)); +} diff --git a/modules/etcpak/image_decompress_etcpak.h b/modules/etcpak/image_decompress_etcpak.h new file mode 100644 index 0000000000..a31df87ea3 --- /dev/null +++ b/modules/etcpak/image_decompress_etcpak.h @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* image_decompress_etcpak.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef IMAGE_DECOMPRESS_ETCPAK_H +#define IMAGE_DECOMPRESS_ETCPAK_H + +#include "core/io/image.h" + +enum EtcpakFormat { + Etcpak_R, + Etcpak_RG, + Etcpak_RGB, + Etcpak_RGBA, +}; + +void _decompress_etc(Image *p_image); + +#endif // IMAGE_DECOMPRESS_ETCPAK_H diff --git a/modules/etcpak/register_types.cpp b/modules/etcpak/register_types.cpp index e9444d9609..0ab22016b2 100644 --- a/modules/etcpak/register_types.cpp +++ b/modules/etcpak/register_types.cpp @@ -33,15 +33,21 @@ #include "register_types.h" #include "image_compress_etcpak.h" +#include "image_decompress_etcpak.h" void initialize_etcpak_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } +#ifdef TOOLS_ENABLED Image::_image_compress_etc1_func = _compress_etc1; Image::_image_compress_etc2_func = _compress_etc2; Image::_image_compress_bc_func = _compress_bc; +#endif + + Image::_image_decompress_etc1 = _decompress_etc; + Image::_image_decompress_etc2 = _decompress_etc; } void uninitialize_etcpak_module(ModuleInitializationLevel p_level) { diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index 6c8d1b6019..e7a0f246c6 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -40,10 +40,6 @@ #include "modules/gltf/gltf_document.h" -uint32_t EditorSceneFormatImporterFBX2GLTF::get_import_flags() const { - return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; -} - void EditorSceneFormatImporterFBX2GLTF::get_extensions(List *r_extensions) const { r_extensions->push_back("fbx"); } diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h index 64903c4286..def3fc1c07 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h @@ -44,7 +44,6 @@ class EditorSceneFormatImporterFBX2GLTF : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterFBX2GLTF, EditorSceneFormatImporter); public: - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index c9fc8442a1..a5a74afe7b 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -39,10 +39,6 @@ #include "core/config/project_settings.h" -uint32_t EditorSceneFormatImporterUFBX::get_import_flags() const { - return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; -} - void EditorSceneFormatImporterUFBX::get_extensions(List *r_extensions) const { r_extensions->push_back("fbx"); } diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h index 401aad21c5..dd08679661 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.h +++ b/modules/fbx/editor/editor_scene_importer_ufbx.h @@ -48,7 +48,6 @@ public: FBX_IMPORTER_UFBX, FBX_IMPORTER_FBX2GLTF, }; - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig deleted file mode 100644 index b380846f86..0000000000 --- a/modules/gdscript/.editorconfig +++ /dev/null @@ -1,3 +0,0 @@ -[*.gd] -indent_size = 4 -trim_trailing_whitespace = true diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index a40e3d31b2..a9aa93b1a5 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1927,7 +1927,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found && base.value.get_type() != Variant::NIL) { + if (!found) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } diff --git a/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.cfg b/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.cfg new file mode 100644 index 0000000000..e8f996aa03 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + ; String + {"display": "begins_with(…)"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.gd b/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.gd new file mode 100644 index 0000000000..e271b37eeb --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/infer_return_type_without_value.gd @@ -0,0 +1,9 @@ +class B: + func to_str(b: int): + return str(b) + +var a: B + +func _ready(): + a.to_str(10).➡ + pass diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 9e3b17a93d..5253ebb593 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -104,10 +104,6 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino return true; } -uint32_t EditorSceneFormatImporterBlend::get_import_flags() const { - return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; -} - void EditorSceneFormatImporterBlend::get_extensions(List *r_extensions) const { r_extensions->push_back("blend"); } diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index a9d379bea2..7bce63e15a 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -69,7 +69,6 @@ public: BLEND_MODIFIERS_ALL }; - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 2c8406b371..a800eaab15 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -37,10 +37,6 @@ #include "../gltf_defines.h" #include "../gltf_document.h" -uint32_t EditorSceneFormatImporterGLTF::get_import_flags() const { - return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; -} - void EditorSceneFormatImporterGLTF::get_extensions(List *r_extensions) const { r_extensions->push_back("gltf"); r_extensions->push_back("glb"); diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index ba126d0647..f5cf2b16fe 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -44,7 +44,6 @@ class EditorSceneFormatImporterGLTF : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterGLTF, EditorSceneFormatImporter); public: - virtual uint32_t get_import_flags() const override; virtual void get_extensions(List *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap &p_options, diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index bfb47293c2..b82eacf5fc 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -223,21 +223,27 @@ Ref GLTFLight::from_dictionary(const Dictionary p_dictionary) { Dictionary GLTFLight::to_dictionary() const { Dictionary d; - Array color_array; - color_array.resize(3); - color_array[0] = color.r; - color_array[1] = color.g; - color_array[2] = color.b; - d["color"] = color_array; - d["type"] = light_type; + if (color != Color(1.0f, 1.0f, 1.0f)) { + Array color_array; + color_array.resize(3); + color_array[0] = color.r; + color_array[1] = color.g; + color_array[2] = color.b; + d["color"] = color_array; + } + if (intensity != 1.0f) { + d["intensity"] = intensity; + } + if (light_type != "directional" && range != INFINITY) { + d["range"] = range; + } if (light_type == "spot") { Dictionary spot_dict; spot_dict["innerConeAngle"] = inner_cone_angle; spot_dict["outerConeAngle"] = outer_cone_angle; d["spot"] = spot_dict; } - d["intensity"] = intensity; - d["range"] = range; + d["type"] = light_type; return d; } diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index d769afd23b..1ff4bec8a2 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1256,6 +1256,9 @@ void GridMapEditor::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { indicator_mat->set_albedo(EDITOR_GET("editors/3d_gizmos/gizmo_colors/gridmap_grid")); + + // Take Preview Size changes into account. + update_palette(); } break; } } diff --git a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml index 5353dc7376..e554eaeb5a 100644 --- a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml +++ b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml @@ -4,7 +4,7 @@ Stream that can be fitted with sub-streams, which will be played in-sync. - This is a stream that can be fitted with sub-streams, which will be played in-sync. The streams being at exactly the same time when play is pressed, and will end when the last of them ends. If one of the sub-streams loops, then playback will continue. + This is a stream that can be fitted with sub-streams, which will be played in-sync. The streams begin at exactly the same time when play is pressed, and will end when the last of them ends. If one of the sub-streams loops, then playback will continue. diff --git a/modules/jolt_physics/objects/jolt_area_3d.cpp b/modules/jolt_physics/objects/jolt_area_3d.cpp index 238e34bcae..ec7e9e4165 100644 --- a/modules/jolt_physics/objects/jolt_area_3d.cpp +++ b/modules/jolt_physics/objects/jolt_area_3d.cpp @@ -82,7 +82,7 @@ bool JoltArea3D::_has_pending_events() const { } void JoltArea3D::_add_to_space() { - jolt_shape = build_shape(); + jolt_shape = build_shapes(true); JPH::CollisionGroup::GroupID group_id = 0; JPH::CollisionGroup::SubGroupID sub_group_id = 0; @@ -99,7 +99,7 @@ void JoltArea3D::_add_to_space() { jolt_settings->mCollideKinematicVsNonDynamic = true; } - jolt_settings->SetShape(build_shape()); + jolt_settings->SetShape(jolt_shape); const JPH::BodyID new_jolt_id = space->add_rigid_body(*this, *jolt_settings); if (new_jolt_id.IsInvalid()) { diff --git a/modules/jolt_physics/objects/jolt_body_3d.cpp b/modules/jolt_physics/objects/jolt_body_3d.cpp index 949ef2cdd9..380e32baa4 100644 --- a/modules/jolt_physics/objects/jolt_body_3d.cpp +++ b/modules/jolt_physics/objects/jolt_body_3d.cpp @@ -116,7 +116,7 @@ JPH::EMotionType JoltBody3D::_get_motion_type() const { } void JoltBody3D::_add_to_space() { - jolt_shape = build_shape(); + jolt_shape = build_shapes(true); JPH::CollisionGroup::GroupID group_id = 0; JPH::CollisionGroup::SubGroupID sub_group_id = 0; @@ -479,8 +479,8 @@ void JoltBody3D::_mode_changed() { wake_up(); } -void JoltBody3D::_shapes_built() { - JoltShapedObject3D::_shapes_built(); +void JoltBody3D::_shapes_committed() { + JoltShapedObject3D::_shapes_committed(); _update_mass_properties(); _update_joint_constraints(); diff --git a/modules/jolt_physics/objects/jolt_body_3d.h b/modules/jolt_physics/objects/jolt_body_3d.h index 69fd799c5f..9dcd24fd9d 100644 --- a/modules/jolt_physics/objects/jolt_body_3d.h +++ b/modules/jolt_physics/objects/jolt_body_3d.h @@ -141,7 +141,7 @@ private: void _exit_all_areas(); void _mode_changed(); - virtual void _shapes_built() override; + virtual void _shapes_committed() override; virtual void _space_changing() override; virtual void _space_changed() override; void _areas_changed(); diff --git a/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp b/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp index 78c08f5b6c..b9d2400fa4 100644 --- a/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp +++ b/modules/jolt_physics/objects/jolt_shaped_object_3d.cpp @@ -38,6 +38,7 @@ #include "../spaces/jolt_space_3d.h" #include "Jolt/Physics/Collision/Shape/EmptyShape.h" +#include "Jolt/Physics/Collision/Shape/MutableCompoundShape.h" #include "Jolt/Physics/Collision/Shape/StaticCompoundShape.h" bool JoltShapedObject3D::_is_big() const { @@ -45,7 +46,7 @@ bool JoltShapedObject3D::_is_big() const { return get_aabb().get_longest_axis_size() >= 1000.0f; } -JPH::ShapeRefC JoltShapedObject3D::_try_build_shape() { +JPH::ShapeRefC JoltShapedObject3D::_try_build_shape(bool p_optimize_compound) { int built_shapes = 0; for (JoltShapeInstance3D &shape : shapes) { @@ -58,7 +59,7 @@ JPH::ShapeRefC JoltShapedObject3D::_try_build_shape() { return nullptr; } - JPH::ShapeRefC result = built_shapes == 1 ? _try_build_single_shape() : _try_build_compound_shape(); + JPH::ShapeRefC result = built_shapes == 1 ? _try_build_single_shape() : _try_build_compound_shape(p_optimize_compound); if (unlikely(result == nullptr)) { return nullptr; } @@ -108,8 +109,12 @@ JPH::ShapeRefC JoltShapedObject3D::_try_build_single_shape() { return nullptr; } -JPH::ShapeRefC JoltShapedObject3D::_try_build_compound_shape() { - JPH::StaticCompoundShapeSettings compound_shape_settings; +JPH::ShapeRefC JoltShapedObject3D::_try_build_compound_shape(bool p_optimize) { + JPH::StaticCompoundShapeSettings static_compound_shape_settings; + JPH::MutableCompoundShapeSettings mutable_compound_shape_settings; + JPH::CompoundShapeSettings *compound_shape_settings = p_optimize ? static_cast(&static_compound_shape_settings) : static_cast(&mutable_compound_shape_settings); + + compound_shape_settings->mSubShapes.reserve((size_t)shapes.size()); for (int shape_index = 0; shape_index < (int)shapes.size(); ++shape_index) { const JoltShapeInstance3D &sub_shape = shapes[shape_index]; @@ -124,27 +129,41 @@ JPH::ShapeRefC JoltShapedObject3D::_try_build_compound_shape() { const Transform3D sub_shape_transform = sub_shape.get_transform_unscaled(); if (sub_shape_scale != Vector3(1, 1, 1)) { - JOLT_ENSURE_SCALE_VALID(jolt_sub_shape, sub_shape_scale, vformat("Failed to correctly scale shape at index %d in body '%s'.", shape_index, to_string())); + JOLT_ENSURE_SCALE_VALID(jolt_sub_shape, sub_shape_scale, vformat("Failed to correctly scale shape at index %d for body '%s'.", shape_index, to_string())); jolt_sub_shape = JoltShape3D::with_scale(jolt_sub_shape, sub_shape_scale); } - compound_shape_settings.AddShape(to_jolt(sub_shape_transform.origin), to_jolt(sub_shape_transform.basis), jolt_sub_shape); + compound_shape_settings->AddShape(to_jolt(sub_shape_transform.origin), to_jolt(sub_shape_transform.basis), jolt_sub_shape); } - const JPH::ShapeSettings::ShapeResult shape_result = compound_shape_settings.Create(); - ERR_FAIL_COND_V_MSG(shape_result.HasError(), nullptr, vformat("Failed to create compound shape with sub-shape count '%d'. It returned the following error: '%s'.", (int)compound_shape_settings.mSubShapes.size(), to_godot(shape_result.GetError()))); + const JPH::ShapeSettings::ShapeResult shape_result = p_optimize ? static_compound_shape_settings.Create(space->get_temp_allocator()) : mutable_compound_shape_settings.Create(); + ERR_FAIL_COND_V_MSG(shape_result.HasError(), nullptr, vformat("Failed to create compound shape for body '%s'. It returned the following error: '%s'.", to_string(), to_godot(shape_result.GetError()))); return shape_result.Get(); } +void JoltShapedObject3D::_enqueue_needs_optimization() { + if (!needs_optimization_element.in_list()) { + space->enqueue_needs_optimization(&needs_optimization_element); + } +} + +void JoltShapedObject3D::_dequeue_needs_optimization() { + if (needs_optimization_element.in_list()) { + space->dequeue_needs_optimization(&needs_optimization_element); + } +} + void JoltShapedObject3D::_shapes_changed() { - _update_shape(); + commit_shapes(false); _update_object_layer(); } void JoltShapedObject3D::_space_changing() { JoltObject3D::_space_changing(); + _dequeue_needs_optimization(); + if (space != nullptr) { const JoltWritableBody3D body = space->write_body(jolt_id); ERR_FAIL_COND(body.is_invalid()); @@ -153,30 +172,9 @@ void JoltShapedObject3D::_space_changing() { } } -void JoltShapedObject3D::_update_shape() { - if (!in_space()) { - _shapes_built(); - return; - } - - const JoltWritableBody3D body = space->write_body(jolt_id); - ERR_FAIL_COND(body.is_invalid()); - - JPH::ShapeRefC new_shape = build_shape(); - if (new_shape == jolt_shape) { - return; - } - - previous_jolt_shape = jolt_shape; - jolt_shape = new_shape; - - space->get_body_iface().SetShape(jolt_id, jolt_shape, false, JPH::EActivation::DontActivate); - - _shapes_built(); -} - JoltShapedObject3D::JoltShapedObject3D(ObjectType p_object_type) : - JoltObject3D(p_object_type) { + JoltObject3D(p_object_type), + needs_optimization_element(this) { jolt_settings->mAllowSleeping = true; jolt_settings->mFriction = 1.0f; jolt_settings->mRestitution = 0.0f; @@ -288,8 +286,8 @@ AABB JoltShapedObject3D::get_aabb() const { return get_transform_scaled().xform(result); } -JPH::ShapeRefC JoltShapedObject3D::build_shape() { - JPH::ShapeRefC new_shape = _try_build_shape(); +JPH::ShapeRefC JoltShapedObject3D::build_shapes(bool p_optimize_compound) { + JPH::ShapeRefC new_shape = _try_build_shape(p_optimize_compound); if (new_shape == nullptr) { if (has_custom_center_of_mass()) { @@ -302,6 +300,34 @@ JPH::ShapeRefC JoltShapedObject3D::build_shape() { return new_shape; } +void JoltShapedObject3D::commit_shapes(bool p_optimize_compound) { + if (!in_space()) { + _shapes_committed(); + return; + } + + const JoltWritableBody3D body = space->write_body(jolt_id); + ERR_FAIL_COND(body.is_invalid()); + + JPH::ShapeRefC new_shape = build_shapes(p_optimize_compound); + if (new_shape == jolt_shape) { + return; + } + + previous_jolt_shape = jolt_shape; + jolt_shape = new_shape; + + space->get_body_iface().SetShape(jolt_id, jolt_shape, false, JPH::EActivation::DontActivate); + + if (!p_optimize_compound && jolt_shape->GetType() == JPH::EShapeType::Compound) { + _enqueue_needs_optimization(); + } else { + _dequeue_needs_optimization(); + } + + _shapes_committed(); +} + void JoltShapedObject3D::add_shape(JoltShape3D *p_shape, Transform3D p_transform, bool p_disabled) { JOLT_ENSURE_SCALE_NOT_ZERO(p_transform, vformat("An invalid transform was passed when adding shape at index %d to physics body '%s'.", shapes.size(), to_string())); diff --git a/modules/jolt_physics/objects/jolt_shaped_object_3d.h b/modules/jolt_physics/objects/jolt_shaped_object_3d.h index 298a60173c..7c02e33747 100644 --- a/modules/jolt_physics/objects/jolt_shaped_object_3d.h +++ b/modules/jolt_physics/objects/jolt_shaped_object_3d.h @@ -35,6 +35,8 @@ #include "jolt_object_3d.h" +#include "core/templates/self_list.h" + #include "Jolt/Jolt.h" #include "Jolt/Physics/Body/Body.h" @@ -44,6 +46,8 @@ class JoltShapedObject3D : public JoltObject3D { friend class JoltShape3D; protected: + SelfList needs_optimization_element; + Vector3 scale = Vector3(1, 1, 1); JPH::ShapeRefC jolt_shape; @@ -55,16 +59,17 @@ protected: bool _is_big() const; - JPH::ShapeRefC _try_build_shape(); + JPH::ShapeRefC _try_build_shape(bool p_optimize_compound); JPH::ShapeRefC _try_build_single_shape(); - JPH::ShapeRefC _try_build_compound_shape(); + JPH::ShapeRefC _try_build_compound_shape(bool p_optimize); + + void _enqueue_needs_optimization(); + void _dequeue_needs_optimization(); virtual void _shapes_changed(); - virtual void _shapes_built() {} + virtual void _shapes_committed() {} virtual void _space_changing() override; - void _update_shape(); - public: explicit JoltShapedObject3D(ObjectType p_object_type); virtual ~JoltShapedObject3D() override; @@ -88,7 +93,9 @@ public: virtual bool has_custom_center_of_mass() const = 0; virtual Vector3 get_center_of_mass_custom() const = 0; - JPH::ShapeRefC build_shape(); + JPH::ShapeRefC build_shapes(bool p_optimize_compound); + + void commit_shapes(bool p_optimize_compound); const JPH::Shape *get_jolt_shape() const { return jolt_shape; } const JPH::Shape *get_previous_jolt_shape() const { return previous_jolt_shape; } diff --git a/modules/jolt_physics/spaces/jolt_space_3d.cpp b/modules/jolt_physics/spaces/jolt_space_3d.cpp index 8d811bcc0f..69102b859d 100644 --- a/modules/jolt_physics/spaces/jolt_space_3d.cpp +++ b/modules/jolt_physics/spaces/jolt_space_3d.cpp @@ -65,6 +65,12 @@ constexpr double DEFAULT_SOLVER_ITERATIONS = 8; } // namespace void JoltSpace3D::_pre_step(float p_step) { + while (needs_optimization_list.first()) { + JoltShapedObject3D *object = needs_optimization_list.first()->self(); + needs_optimization_list.remove(needs_optimization_list.first()); + object->commit_shapes(true); + } + contact_listener->pre_step(); const JPH::BodyLockInterface &lock_iface = get_lock_iface(); @@ -429,6 +435,18 @@ void JoltSpace3D::dequeue_call_queries(SelfList *p_area) { } } +void JoltSpace3D::enqueue_needs_optimization(SelfList *p_object) { + if (!p_object->in_list()) { + needs_optimization_list.add(p_object); + } +} + +void JoltSpace3D::dequeue_needs_optimization(SelfList *p_object) { + if (p_object->in_list()) { + needs_optimization_list.remove(p_object); + } +} + void JoltSpace3D::add_joint(JPH::Constraint *p_jolt_ref) { physics_system->AddConstraint(p_jolt_ref); } diff --git a/modules/jolt_physics/spaces/jolt_space_3d.h b/modules/jolt_physics/spaces/jolt_space_3d.h index 0543e7e176..f90604196c 100644 --- a/modules/jolt_physics/spaces/jolt_space_3d.h +++ b/modules/jolt_physics/spaces/jolt_space_3d.h @@ -56,10 +56,12 @@ class JoltJoint3D; class JoltLayers; class JoltObject3D; class JoltPhysicsDirectSpaceState3D; +class JoltShapedObject3D; class JoltSpace3D { SelfList::List body_call_queries_list; SelfList::List area_call_queries_list; + SelfList::List needs_optimization_list; RID rid; @@ -102,6 +104,8 @@ public: JPH::PhysicsSystem &get_physics_system() const { return *physics_system; } + JPH::TempAllocator &get_temp_allocator() const { return *temp_allocator; } + JPH::BodyInterface &get_body_iface(); const JPH::BodyInterface &get_body_iface() const; const JPH::BodyLockInterface &get_lock_iface() const; @@ -140,6 +144,9 @@ public: void dequeue_call_queries(SelfList *p_body); void dequeue_call_queries(SelfList *p_area); + void enqueue_needs_optimization(SelfList *p_object); + void dequeue_needs_optimization(SelfList *p_object); + void add_joint(JPH::Constraint *p_jolt_ref); void add_joint(JoltJoint3D *p_joint); void remove_joint(JPH::Constraint *p_jolt_ref); diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp index 4f738c50ee..4559d0b85c 100644 --- a/modules/jsonrpc/jsonrpc.cpp +++ b/modules/jsonrpc/jsonrpc.cpp @@ -124,6 +124,11 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem Variant id; if (dict.has("id")) { id = dict["id"]; + + // Account for implementations that discern between int and float on the json serialization level, by using an int if there is a .0 fraction. See #100914 + if (id.get_type() == Variant::FLOAT && id.operator float() == (float)(id.operator int())) { + id = id.operator int(); + } } if (object == nullptr || !object->has_method(method)) { diff --git a/modules/ktx/texture_loader_ktx.cpp b/modules/ktx/texture_loader_ktx.cpp index 31a7343322..163ae340be 100644 --- a/modules/ktx/texture_loader_ktx.cpp +++ b/modules/ktx/texture_loader_ktx.cpp @@ -210,32 +210,33 @@ static Ref load_from_file_access(Ref f, Error *r_error) { case GL_COMPRESSED_RGBA_BPTC_UNORM: format = Image::FORMAT_BPTC_RGBA; break; -#if 0 // TODO: ETC compression is bogus. case GL_ETC1_RGB8_OES: format = Image::FORMAT_ETC; break; case GL_COMPRESSED_R11_EAC: format = Image::FORMAT_ETC2_R11; break; - case GL_COMPRESSED_SIGNED_R11_EAC: + // Decompression is not supported for this format. + /*case GL_COMPRESSED_SIGNED_R11_EAC: format = Image::FORMAT_ETC2_R11S; - break; + break;*/ case GL_COMPRESSED_RG11_EAC: format = Image::FORMAT_ETC2_RG11; break; - case GL_COMPRESSED_SIGNED_RG11_EAC: + // Decompression is not supported for this format. + /*case GL_COMPRESSED_SIGNED_RG11_EAC: format = Image::FORMAT_ETC2_RG11S; - break; + break;*/ case GL_COMPRESSED_RGB8_ETC2: format = Image::FORMAT_ETC2_RGB8; break; case GL_COMPRESSED_RGBA8_ETC2_EAC: format = Image::FORMAT_ETC2_RGBA8; break; - case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + // Decompression is not supported for this format. + /*case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: format = Image::FORMAT_ETC2_RGB8A1; - break; -#endif + break;*/ case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: format = Image::FORMAT_ASTC_4x4; break; @@ -408,29 +409,30 @@ static Ref load_from_file_access(Ref f, Error *r_error) { case VK_FORMAT_BC7_UNORM_BLOCK: format = Image::FORMAT_BPTC_RGBA; break; -#if 0 // TODO: ETC compression is bogus. case VK_FORMAT_EAC_R11_UNORM_BLOCK: format = Image::FORMAT_ETC2_R11; break; - case VK_FORMAT_EAC_R11_SNORM_BLOCK: + // Decompression is not supported for this format. + /*case VK_FORMAT_EAC_R11_SNORM_BLOCK: format = Image::FORMAT_ETC2_R11S; - break; + break;*/ case VK_FORMAT_EAC_R11G11_UNORM_BLOCK: format = Image::FORMAT_ETC2_RG11; break; - case VK_FORMAT_EAC_R11G11_SNORM_BLOCK: + // Decompression is not supported for this format. + /*case VK_FORMAT_EAC_R11G11_SNORM_BLOCK: format = Image::FORMAT_ETC2_RG11S; - break; + break;*/ case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: format = Image::FORMAT_ETC2_RGB8; break; case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: format = Image::FORMAT_ETC2_RGBA8; break; - case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: + // Decompression is not supported for this format. + /*case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: format = Image::FORMAT_ETC2_RGB8A1; - break; -#endif + break;*/ case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: format = Image::FORMAT_ASTC_4x4; break; diff --git a/modules/mono/.editorconfig b/modules/mono/.editorconfig index fcd10461ad..91df69b732 100644 --- a/modules/mono/.editorconfig +++ b/modules/mono/.editorconfig @@ -2,21 +2,14 @@ end_of_line = crlf charset = utf-8-bom -[*.sln] -indent_style = tab - [*.{csproj,props,targets,nuspec,resx}] indent_style = space indent_size = 2 [*.cs] indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true -max_line_length = 120 -csharp_indent_case_contents_when_block = false +csharp_indent_case_contents_when_block = false csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp index 4c5e905343..e53de774cc 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.cpp +++ b/modules/navigation/2d/godot_navigation_server_2d.cpp @@ -160,6 +160,13 @@ static Ref poly_to_mesh(Ref d) { } } +static Rect2 aabb_to_rect2(AABB aabb) { + Rect2 rect2; + rect2.position = Vector2(aabb.position.x, aabb.position.z); + rect2.size = Vector2(aabb.size.x, aabb.size.z); + return rect2; +} + void GodotNavigationServer2D::init() { #ifdef CLIPPER2_ENABLED navmesh_generator_2d = memnew(NavMeshGenerator2D); @@ -334,6 +341,11 @@ Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t return v3_to_v2(result); } +Rect2 GodotNavigationServer2D::region_get_bounds(RID p_region) const { + AABB bounds = NavigationServer3D::get_singleton()->region_get_bounds(p_region); + return aabb_to_rect2(bounds); +} + RID FORWARD_0(link_create); void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h index d6049bb54f..ef79f349c5 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.h +++ b/modules/navigation/2d/godot_navigation_server_2d.h @@ -107,6 +107,7 @@ public: virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override; virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override; + virtual Rect2 region_get_bounds(RID p_region) const override; virtual RID link_create() override; diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index f2599c8546..0b91d50488 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -597,6 +597,13 @@ Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t return region->get_random_point(p_navigation_layers, p_uniformly); } +AABB GodotNavigationServer3D::region_get_bounds(RID p_region) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_NULL_V(region, AABB()); + + return region->get_bounds(); +} + RID GodotNavigationServer3D::link_create() { MutexLock lock(operations_mutex); diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h index 2b12de0b89..fa65509a03 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.h +++ b/modules/navigation/3d/godot_navigation_server_3d.h @@ -189,6 +189,7 @@ public: virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override; virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override; virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override; + virtual AABB region_get_bounds(RID p_region) const override; virtual RID link_create() override; COMMAND_2(link_set_map, RID, p_link, RID, p_map); diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index b0ee44267b..6492bb46f5 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -17,6 +17,13 @@ https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html + + + + + Returns the corresponding [code]XrAction[/code] OpenXR handle for the given action RID. + + @@ -36,6 +43,14 @@ Marks the end of a debug label region. Removes the latest debug label region added by calling [method begin_debug_label_region]. + + + + + + Returns the [RID] corresponding to an [code]Action[/code] of a matching name, optionally limited to a specified action set. + + diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index 2ad8e90819..00cfe98ca8 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -230,33 +230,32 @@ bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in } #ifdef ANDROID_ENABLED - XrSwapchainImageOpenGLESKHR *images = (XrSwapchainImageOpenGLESKHR *)memalloc(sizeof(XrSwapchainImageOpenGLESKHR) * swapchain_length); + LocalVector images; #else - XrSwapchainImageOpenGLKHR *images = (XrSwapchainImageOpenGLKHR *)memalloc(sizeof(XrSwapchainImageOpenGLKHR) * swapchain_length); + LocalVector images; #endif - ERR_FAIL_NULL_V_MSG(images, false, "OpenXR Couldn't allocate memory for swap chain image"); + images.resize(swapchain_length); - for (uint64_t i = 0; i < swapchain_length; i++) { #ifdef ANDROID_ENABLED - images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + for (XrSwapchainImageOpenGLESKHR &image : images) { + image.type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; #else - images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR; + for (XrSwapchainImageOpenGLKHR &image : images) { + image.type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR; #endif - images[i].next = nullptr; - images[i].image = 0; + image.next = nullptr; + image.image = 0; } - result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images); + result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images.ptr()); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get swapchaim images [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); - memfree(images); return false; } SwapchainGraphicsData *data = memnew(SwapchainGraphicsData); if (data == nullptr) { print_line("OpenXR: Failed to allocate memory for swapchain data"); - memfree(images); return false; } *r_swapchain_graphics_data = data; @@ -281,8 +280,6 @@ bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in data->texture_rids = texture_rids; - memfree(images); - return true; } diff --git a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp index 3a408adf5c..e1e7b93130 100644 --- a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp @@ -241,7 +241,7 @@ void OpenXRVulkanExtension::get_usable_depth_formats(Vector &p_usable_s } bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { - XrSwapchainImageVulkanKHR *images = nullptr; + LocalVector images; RenderingServer *rendering_server = RenderingServer::get_singleton(); ERR_FAIL_NULL_V(rendering_server, false); @@ -255,27 +255,23 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in return false; } - images = (XrSwapchainImageVulkanKHR *)memalloc(sizeof(XrSwapchainImageVulkanKHR) * swapchain_length); - ERR_FAIL_NULL_V_MSG(images, false, "OpenXR Couldn't allocate memory for swap chain image"); + images.resize(swapchain_length); - for (uint64_t i = 0; i < swapchain_length; i++) { - images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; - images[i].next = nullptr; - images[i].image = VK_NULL_HANDLE; + for (XrSwapchainImageVulkanKHR &image : images) { + image.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; + image.next = nullptr; + image.image = VK_NULL_HANDLE; } - result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images); + result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images.ptr()); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get swapchaim images [", OpenXRAPI::get_singleton()->get_error_string(result), "]"); - memfree(images); return false; } - // SwapchainGraphicsData *data = (SwapchainGraphicsData *)memalloc(sizeof(SwapchainGraphicsData)); SwapchainGraphicsData *data = memnew(SwapchainGraphicsData); if (data == nullptr) { print_line("OpenXR: Failed to allocate memory for swapchain data"); - memfree(images); return false; } *r_swapchain_graphics_data = data; @@ -359,13 +355,13 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in Vector texture_rids; // create Godot texture objects for each entry in our swapchain - for (uint64_t i = 0; i < swapchain_length; i++) { + for (const XrSwapchainImageVulkanKHR &swapchain_image : images) { RID image_rid = rendering_device->texture_create_from_extension( p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY, format, samples, usage_flags, - (uint64_t)images[i].image, + (uint64_t)swapchain_image.image, p_width, p_height, 1, @@ -376,8 +372,6 @@ bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in data->texture_rids = texture_rids; - memfree(images); - return true; } diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index dc7860796f..362164da3c 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -360,27 +360,27 @@ void OpenXRAPI::insert_debug_label(const String &p_label_name) { bool OpenXRAPI::load_layer_properties() { // This queries additional layers that are available and can be initialized when we create our OpenXR instance - if (layer_properties != nullptr) { + if (!layer_properties.is_empty()) { // already retrieved this return true; } // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + uint32_t num_layer_properties = 0; XrResult result = xrEnumerateApiLayerProperties(0, &num_layer_properties, nullptr); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of api layer properties"); - layer_properties = (XrApiLayerProperties *)memalloc(sizeof(XrApiLayerProperties) * num_layer_properties); - ERR_FAIL_NULL_V(layer_properties, false); - for (uint32_t i = 0; i < num_layer_properties; i++) { - layer_properties[i].type = XR_TYPE_API_LAYER_PROPERTIES; - layer_properties[i].next = nullptr; + layer_properties.resize(num_layer_properties); + for (XrApiLayerProperties &layer : layer_properties) { + layer.type = XR_TYPE_API_LAYER_PROPERTIES; + layer.next = nullptr; } - result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties); + result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties.ptr()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties"); - for (uint32_t i = 0; i < num_layer_properties; i++) { - print_verbose(String("OpenXR: Found OpenXR layer ") + layer_properties[i].layerName); + for (const XrApiLayerProperties &layer : layer_properties) { + print_verbose(vformat("OpenXR: Found OpenXR layer %s.", layer.layerName)); } return true; @@ -389,36 +389,36 @@ bool OpenXRAPI::load_layer_properties() { bool OpenXRAPI::load_supported_extensions() { // This queries supported extensions that are available and can be initialized when we create our OpenXR instance - if (supported_extensions != nullptr) { + if (!supported_extensions.is_empty()) { // already retrieved this return true; } // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + uint32_t num_supported_extensions = 0; XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &num_supported_extensions, nullptr); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of extension properties"); - supported_extensions = (XrExtensionProperties *)memalloc(sizeof(XrExtensionProperties) * num_supported_extensions); - ERR_FAIL_NULL_V(supported_extensions, false); + supported_extensions.resize(num_supported_extensions); // set our types - for (uint32_t i = 0; i < num_supported_extensions; i++) { - supported_extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES; - supported_extensions[i].next = nullptr; + for (XrExtensionProperties &extension : supported_extensions) { + extension.type = XR_TYPE_EXTENSION_PROPERTIES; + extension.next = nullptr; } - result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions); + result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions.ptr()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties"); - for (uint32_t i = 0; i < num_supported_extensions; i++) { - print_verbose(String("OpenXR: Found OpenXR extension ") + supported_extensions[i].extensionName); + for (const XrExtensionProperties &extension : supported_extensions) { + print_verbose(vformat("OpenXR: Found OpenXR extension %s.", extension.extensionName)); } return true; } bool OpenXRAPI::is_extension_supported(const String &p_extension) const { - for (uint32_t i = 0; i < num_supported_extensions; i++) { - if (supported_extensions[i].extensionName == p_extension) { + for (const XrExtensionProperties &extension : supported_extensions) { + if (extension.extensionName == p_extension) { return true; } } @@ -714,32 +714,28 @@ bool OpenXRAPI::load_supported_view_configuration_types() { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); - if (supported_view_configuration_types != nullptr) { - // free previous results - memfree(supported_view_configuration_types); - supported_view_configuration_types = nullptr; - } + supported_view_configuration_types.clear(); + uint32_t num_view_configuration_types = 0; XrResult result = xrEnumerateViewConfigurations(instance, system_id, 0, &num_view_configuration_types, nullptr); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); return false; } - supported_view_configuration_types = (XrViewConfigurationType *)memalloc(sizeof(XrViewConfigurationType) * num_view_configuration_types); - ERR_FAIL_NULL_V(supported_view_configuration_types, false); + supported_view_configuration_types.resize(num_view_configuration_types); - result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types); + result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types.ptr()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations"); ERR_FAIL_COND_V_MSG(num_view_configuration_types == 0, false, "OpenXR: Failed to enumerateview configurations"); // JIC there should be at least 1! - for (uint32_t i = 0; i < num_view_configuration_types; i++) { - print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); + for (const XrViewConfigurationType &view_configuration_type : supported_view_configuration_types) { + print_verbose(vformat("OpenXR: Found supported view configuration %s.", OpenXRUtil::get_view_configuration_name(view_configuration_type))); } // Check value we loaded at startup... if (!is_view_configuration_supported(view_configuration)) { - print_verbose(String("OpenXR: ") + OpenXRUtil::get_view_configuration_name(view_configuration) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[0])); + print_verbose(vformat("OpenXR: %s isn't supported, defaulting to %s.", OpenXRUtil::get_view_configuration_name(view_configuration), OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[0]))); view_configuration = supported_view_configuration_types[0]; } @@ -752,43 +748,30 @@ bool OpenXRAPI::load_supported_environmental_blend_modes() { ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); - if (supported_environment_blend_modes != nullptr) { - // free previous results - memfree(supported_environment_blend_modes); - supported_environment_blend_modes = nullptr; - num_supported_environment_blend_modes = 0; - } + supported_environment_blend_modes.clear(); + uint32_t num_supported_environment_blend_modes = 0; XrResult result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, 0, &num_supported_environment_blend_modes, nullptr); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get supported environmental blend mode count [", get_error_string(result), "]"); return false; } - supported_environment_blend_modes = (XrEnvironmentBlendMode *)memalloc(sizeof(XrEnvironmentBlendMode) * num_supported_environment_blend_modes); - ERR_FAIL_NULL_V(supported_environment_blend_modes, false); + supported_environment_blend_modes.resize(num_supported_environment_blend_modes); - result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, num_supported_environment_blend_modes, &num_supported_environment_blend_modes, supported_environment_blend_modes); + result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, num_supported_environment_blend_modes, &num_supported_environment_blend_modes, supported_environment_blend_modes.ptrw()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate environmental blend modes"); ERR_FAIL_COND_V_MSG(num_supported_environment_blend_modes == 0, false, "OpenXR: Failed to enumerate environmental blend modes"); // JIC there should be at least 1! - for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) { - print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i])); + for (const XrEnvironmentBlendMode &supported_environment_blend_mode : supported_environment_blend_modes) { + print_verbose(vformat("OpenXR: Found environmental blend mode %s.", OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_mode))); } return true; } bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const { - ERR_FAIL_NULL_V(supported_view_configuration_types, false); - - for (uint32_t i = 0; i < num_view_configuration_types; i++) { - if (supported_view_configuration_types[i] == p_configuration_type) { - return true; - } - } - - return false; + return supported_view_configuration_types.has(p_configuration_type); } bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type) { @@ -800,58 +783,45 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType return false; } - if (view_configuration_views != nullptr) { + if (!view_configuration_views.is_empty()) { // free previous results - memfree(view_configuration_views); - view_configuration_views = nullptr; + view_configuration_views.clear(); } + uint32_t view_count = 0; XrResult result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, 0, &view_count, nullptr); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); return false; } - view_configuration_views = (XrViewConfigurationView *)memalloc(sizeof(XrViewConfigurationView) * view_count); - ERR_FAIL_NULL_V(view_configuration_views, false); + view_configuration_views.resize(view_count); - for (uint32_t i = 0; i < view_count; i++) { - view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; - view_configuration_views[i].next = nullptr; + for (XrViewConfigurationView &view_configuration_view : view_configuration_views) { + view_configuration_view.type = XR_TYPE_VIEW_CONFIGURATION_VIEW; + view_configuration_view.next = nullptr; } - result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views); + result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views.ptr()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations"); - for (uint32_t i = 0; i < view_count; i++) { + for (const XrViewConfigurationView &view_configuration_view : view_configuration_views) { print_verbose("OpenXR: Found supported view configuration view"); - print_verbose(String(" - width: ") + itos(view_configuration_views[i].maxImageRectWidth)); - print_verbose(String(" - height: ") + itos(view_configuration_views[i].maxImageRectHeight)); - print_verbose(String(" - sample count: ") + itos(view_configuration_views[i].maxSwapchainSampleCount)); - print_verbose(String(" - recommended render width: ") + itos(view_configuration_views[i].recommendedImageRectWidth)); - print_verbose(String(" - recommended render height: ") + itos(view_configuration_views[i].recommendedImageRectHeight)); - print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); + print_verbose(String(" - width: ") + itos(view_configuration_view.maxImageRectWidth)); + print_verbose(String(" - height: ") + itos(view_configuration_view.maxImageRectHeight)); + print_verbose(String(" - sample count: ") + itos(view_configuration_view.maxSwapchainSampleCount)); + print_verbose(String(" - recommended render width: ") + itos(view_configuration_view.recommendedImageRectWidth)); + print_verbose(String(" - recommended render height: ") + itos(view_configuration_view.recommendedImageRectHeight)); + print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_view.recommendedSwapchainSampleCount)); } return true; } void OpenXRAPI::destroy_instance() { - if (view_configuration_views != nullptr) { - memfree(view_configuration_views); - view_configuration_views = nullptr; - } - - if (supported_view_configuration_types != nullptr) { - memfree(supported_view_configuration_types); - supported_view_configuration_types = nullptr; - } - - if (supported_environment_blend_modes != nullptr) { - memfree(supported_environment_blend_modes); - supported_environment_blend_modes = nullptr; - num_supported_environment_blend_modes = 0; - } + view_configuration_views.clear(); + supported_view_configuration_types.clear(); + supported_environment_blend_modes.clear(); if (instance != XR_NULL_HANDLE) { for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { @@ -918,42 +888,30 @@ bool OpenXRAPI::load_supported_reference_spaces() { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - if (supported_reference_spaces != nullptr) { - // free previous results - memfree(supported_reference_spaces); - supported_reference_spaces = nullptr; - } + supported_reference_spaces.clear(); + uint32_t num_reference_spaces = 0; XrResult result = xrEnumerateReferenceSpaces(session, 0, &num_reference_spaces, nullptr); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get reference space count [", get_error_string(result), "]"); return false; } - supported_reference_spaces = (XrReferenceSpaceType *)memalloc(sizeof(XrReferenceSpaceType) * num_reference_spaces); - ERR_FAIL_NULL_V(supported_reference_spaces, false); + supported_reference_spaces.resize(num_reference_spaces); - result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces); + result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces.ptr()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces"); ERR_FAIL_COND_V_MSG(num_reference_spaces == 0, false, "OpenXR: Failed to enumerate reference spaces"); - for (uint32_t i = 0; i < num_reference_spaces; i++) { - print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); + for (const XrReferenceSpaceType &supported_reference_space : supported_reference_spaces) { + print_verbose(vformat("OpenXR: Found supported reference space %s.", OpenXRUtil::get_reference_space_name(supported_reference_space))); } return true; } bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_space) { - ERR_FAIL_NULL_V(supported_reference_spaces, false); - - for (uint32_t i = 0; i < num_reference_spaces; i++) { - if (supported_reference_spaces[i] == p_reference_space) { - return true; - } - } - - return false; + return supported_reference_spaces.has(p_reference_space); } bool OpenXRAPI::setup_play_space() { @@ -1147,41 +1105,29 @@ bool OpenXRAPI::reset_emulated_floor_height() { bool OpenXRAPI::load_supported_swapchain_formats() { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - if (supported_swapchain_formats != nullptr) { - // free previous results - memfree(supported_swapchain_formats); - supported_swapchain_formats = nullptr; - } + supported_swapchain_formats.clear(); + uint32_t num_swapchain_formats = 0; XrResult result = xrEnumerateSwapchainFormats(session, 0, &num_swapchain_formats, nullptr); if (XR_FAILED(result)) { print_line("OpenXR: Failed to get swapchain format count [", get_error_string(result), "]"); return false; } - supported_swapchain_formats = (int64_t *)memalloc(sizeof(int64_t) * num_swapchain_formats); - ERR_FAIL_NULL_V(supported_swapchain_formats, false); + supported_swapchain_formats.resize(num_swapchain_formats); - result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats); + result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats.ptrw()); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats"); - for (uint32_t i = 0; i < num_swapchain_formats; i++) { - print_verbose(String("OpenXR: Found supported swapchain format ") + get_swapchain_format_name(supported_swapchain_formats[i])); + for (int64_t swapchain_format : supported_swapchain_formats) { + print_verbose(String("OpenXR: Found supported swapchain format ") + get_swapchain_format_name(swapchain_format)); } return true; } bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { - ERR_FAIL_NULL_V(supported_swapchain_formats, false); - - for (uint32_t i = 0; i < num_swapchain_formats; i++) { - if (supported_swapchain_formats[i] == p_swapchain_format) { - return true; - } - } - - return false; + return supported_swapchain_formats.has(p_swapchain_format); } bool OpenXRAPI::obtain_swapchain_formats() { @@ -1254,7 +1200,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { // We start with our color swapchain... if (color_swapchain_format != 0) { - if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { + if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_configuration_views.size())) { return false; } @@ -1266,7 +1212,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) if (depth_swapchain_format != 0 && submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { - if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { + if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_configuration_views.size())) { return false; } @@ -1279,7 +1225,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { // TBD } - for (uint32_t i = 0; i < render_state.view_count; i++) { + for (uint32_t i = 0; i < render_state.views.size(); i++) { render_state.views[i].type = XR_TYPE_VIEW; render_state.views[i].next = nullptr; @@ -1292,7 +1238,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { render_state.projection_views[i].subImage.imageRect.extent.width = render_state.main_swapchain_size.width; render_state.projection_views[i].subImage.imageRect.extent.height = render_state.main_swapchain_size.height; - if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && render_state.depth_views) { + if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && !render_state.depth_views.is_empty()) { render_state.projection_views[i].next = &render_state.depth_views[i]; render_state.depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; @@ -1334,28 +1280,14 @@ void OpenXRAPI::destroy_session() { render_state.running = false; } - if (render_state.views != nullptr) { - memfree(render_state.views); - render_state.views = nullptr; - } - - if (render_state.projection_views != nullptr) { - memfree(render_state.projection_views); - render_state.projection_views = nullptr; - } - - if (render_state.depth_views != nullptr) { - memfree(render_state.depth_views); - render_state.depth_views = nullptr; - } + render_state.views.clear(); + render_state.projection_views.clear(); + render_state.depth_views.clear(); free_main_swapchains(); OpenXRSwapChainInfo::free_queued(); - if (supported_swapchain_formats != nullptr) { - memfree(supported_swapchain_formats); - supported_swapchain_formats = nullptr; - } + supported_swapchain_formats.clear(); // destroy our spaces if (play_space != XR_NULL_HANDLE) { @@ -1378,11 +1310,7 @@ void OpenXRAPI::destroy_session() { local_floor_emulation.enabled = false; local_floor_emulation.should_reset_floor_height = false; - if (supported_reference_spaces != nullptr) { - // free previous results - memfree(supported_reference_spaces); - supported_reference_spaces = nullptr; - } + supported_reference_spaces.clear(); if (session != XR_NULL_HANDLE) { for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { @@ -1542,7 +1470,7 @@ void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { } uint32_t OpenXRAPI::get_view_count() { - return view_count; + return view_configuration_views.size(); } void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { @@ -1779,7 +1707,7 @@ bool OpenXRAPI::initialize_session() { return false; } - allocate_view_buffers(view_count, submit_depth_buffer); + allocate_view_buffers(view_configuration_views.size(), submit_depth_buffer); return true; } @@ -1837,7 +1765,7 @@ XrHandTrackerEXT OpenXRAPI::get_hand_tracker(int p_hand_index) { Size2 OpenXRAPI::get_recommended_target_size() { RenderingServer *rendering_server = RenderingServer::get_singleton(); - ERR_FAIL_NULL_V(view_configuration_views, Size2()); + ERR_FAIL_COND_V(view_configuration_views.is_empty(), Size2()); Size2 target_size; @@ -1915,7 +1843,7 @@ bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { } // we don't have valid view info - if (render_state.views == nullptr || !render_state.view_pose_valid) { + if (render_state.views.is_empty() || !render_state.view_pose_valid) { return false; } @@ -1934,16 +1862,16 @@ bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z } // we don't have valid view info - if (render_state.views == nullptr || !render_state.view_pose_valid) { + if (render_state.views.is_empty() || !render_state.view_pose_valid) { return false; } // if we're using depth views, make sure we update our near and far there... - if (render_state.depth_views != nullptr) { - for (uint32_t i = 0; i < render_state.view_count; i++) { + if (!render_state.depth_views.is_empty()) { + for (XrCompositionLayerDepthInfoKHR &depth_view : render_state.depth_views) { // As we are using reverse-Z these need to be flipped. - render_state.depth_views[i].nearZ = p_z_far; - render_state.depth_views[i].farZ = p_z_near; + depth_view.nearZ = p_z_far; + depth_view.farZ = p_z_near; } } @@ -1967,7 +1895,7 @@ Vector2 OpenXRAPI::get_eye_focus(uint32_t p_view, float p_aspect) { } // we don't have valid view info - if (render_state.views == nullptr || !render_state.view_pose_valid) { + if (render_state.views.is_empty() || !render_state.view_pose_valid) { return Vector2(); } @@ -2113,22 +2041,14 @@ void OpenXRAPI::_allocate_view_buffers(uint32_t p_view_count, bool p_submit_dept OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); ERR_FAIL_NULL(openxr_api); - openxr_api->render_state.view_count = p_view_count; openxr_api->render_state.submit_depth_buffer = p_submit_depth_buffer; // Allocate buffers we'll be populating with view information. - openxr_api->render_state.views = (XrView *)memalloc(sizeof(XrView) * p_view_count); - ERR_FAIL_NULL_MSG(openxr_api->render_state.views, "OpenXR Couldn't allocate memory for views"); - memset(openxr_api->render_state.views, 0, sizeof(XrView) * p_view_count); - - openxr_api->render_state.projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * p_view_count); - ERR_FAIL_NULL_MSG(openxr_api->render_state.projection_views, "OpenXR Couldn't allocate memory for projection views"); - memset(openxr_api->render_state.projection_views, 0, sizeof(XrCompositionLayerProjectionView) * p_view_count); + openxr_api->render_state.views.resize(p_view_count); + openxr_api->render_state.projection_views.resize(p_view_count); if (p_submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { - openxr_api->render_state.depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count); - ERR_FAIL_NULL_MSG(openxr_api->render_state.depth_views, "OpenXR Couldn't allocate memory for depth views"); - memset(openxr_api->render_state.depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * p_view_count); + openxr_api->render_state.depth_views.resize(p_view_count); } } @@ -2295,7 +2215,7 @@ void OpenXRAPI::pre_render() { 0 // viewStateFlags }; uint32_t view_count_output; - XrResult result = xrLocateViews(session, &view_locate_info, &view_state, render_state.view_count, &view_count_output, render_state.views); + XrResult result = xrLocateViews(session, &view_locate_info, &view_state, render_state.views.size(), &view_count_output, render_state.views.ptr()); if (XR_FAILED(result)) { print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]"); return; @@ -2450,18 +2370,18 @@ void OpenXRAPI::end_frame() { Rect2i new_render_region = (render_state.render_region != Rect2i()) ? render_state.render_region : Rect2i(Point2i(0, 0), render_state.main_swapchain_size); - for (uint32_t i = 0; i < render_state.view_count; i++) { - render_state.projection_views[i].subImage.imageRect.offset.x = new_render_region.position.x; - render_state.projection_views[i].subImage.imageRect.offset.y = new_render_region.position.y; - render_state.projection_views[i].subImage.imageRect.extent.width = new_render_region.size.width; - render_state.projection_views[i].subImage.imageRect.extent.height = new_render_region.size.height; + for (XrCompositionLayerProjectionView &projection_view : render_state.projection_views) { + projection_view.subImage.imageRect.offset.x = new_render_region.position.x; + projection_view.subImage.imageRect.offset.y = new_render_region.position.y; + projection_view.subImage.imageRect.extent.width = new_render_region.size.width; + projection_view.subImage.imageRect.extent.height = new_render_region.size.height; } - if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && render_state.depth_views) { - for (uint32_t i = 0; i < render_state.view_count; i++) { - render_state.depth_views[i].subImage.imageRect.offset.x = new_render_region.position.x; - render_state.depth_views[i].subImage.imageRect.offset.y = new_render_region.position.y; - render_state.depth_views[i].subImage.imageRect.extent.width = new_render_region.size.width; - render_state.depth_views[i].subImage.imageRect.extent.height = new_render_region.size.height; + if (render_state.submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && !render_state.depth_views.is_empty()) { + for (XrCompositionLayerDepthInfoKHR &depth_view : render_state.depth_views) { + depth_view.subImage.imageRect.offset.x = new_render_region.position.x; + depth_view.subImage.imageRect.offset.y = new_render_region.position.y; + depth_view.subImage.imageRect.extent.width = new_render_region.size.width; + depth_view.subImage.imageRect.extent.height = new_render_region.size.height; } } @@ -2498,7 +2418,7 @@ void OpenXRAPI::end_frame() { } } - for (uint32_t eye = 0; eye < render_state.view_count; eye++) { + for (uint32_t eye = 0; eye < render_state.views.size(); eye++) { render_state.projection_views[eye].fov = render_state.views[eye].fov; render_state.projection_views[eye].pose = render_state.views[eye].pose; } @@ -2531,11 +2451,11 @@ void OpenXRAPI::end_frame() { render_state.projection_layer.layerFlags = layer_flags; render_state.projection_layer.space = render_state.play_space; - render_state.projection_layer.viewCount = render_state.view_count; - render_state.projection_layer.views = render_state.projection_views; + render_state.projection_layer.viewCount = (uint32_t)render_state.projection_views.size(); + render_state.projection_layer.views = render_state.projection_views.ptr(); if (projection_views_extensions.size() > 0) { - for (uint32_t v = 0; v < render_state.view_count; v++) { + for (uint32_t v = 0; v < render_state.projection_views.size(); v++) { void *next_pointer = nullptr; for (OpenXRExtensionWrapper *wrapper : projection_views_extensions) { void *np = wrapper->set_projection_views_and_get_next_pointer(v, next_pointer); @@ -2687,11 +2607,7 @@ Size2 OpenXRAPI::get_play_space_bounds() const { } PackedInt64Array OpenXRAPI::get_supported_swapchain_formats() { - PackedInt64Array supported_swapchain_list; - for (uint32_t i = 0; i < num_swapchain_formats; i++) { - supported_swapchain_list.push_back(supported_swapchain_formats[i]); - } - return supported_swapchain_list; + return supported_swapchain_formats; } OpenXRAPI::OpenXRAPI() { @@ -2775,15 +2691,8 @@ OpenXRAPI::~OpenXRAPI() { } composition_layer_providers.clear(); - if (supported_extensions != nullptr) { - memfree(supported_extensions); - supported_extensions = nullptr; - } - - if (layer_properties != nullptr) { - memfree(layer_properties); - layer_properties = nullptr; - } + supported_extensions.clear(); + layer_properties.clear(); #ifdef ANDROID_ENABLED if (openxr_loader_library_handle) { @@ -3705,21 +3614,12 @@ void OpenXRAPI::unregister_projection_views_extension(OpenXRExtensionWrapper *p_ projection_views_extensions.erase(p_extension); } -const XrEnvironmentBlendMode *OpenXRAPI::get_supported_environment_blend_modes(uint32_t &count) { - count = num_supported_environment_blend_modes; +const Vector OpenXRAPI::get_supported_environment_blend_modes() { return supported_environment_blend_modes; } bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const { - ERR_FAIL_NULL_V(supported_environment_blend_modes, false); - - for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) { - if (supported_environment_blend_modes[i] == p_blend_mode) { - return true; - } - } - - return false; + return supported_environment_blend_modes.has(p_blend_mode); } bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) { diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index c0c416943f..9ad36e72cc 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -90,12 +90,10 @@ private: OpenXRInterface *xr_interface = nullptr; // layers - uint32_t num_layer_properties = 0; - XrApiLayerProperties *layer_properties = nullptr; + LocalVector layer_properties; // extensions - uint32_t num_supported_extensions = 0; - XrExtensionProperties *supported_extensions = nullptr; + LocalVector supported_extensions; Vector enabled_extensions; // composition layer providers @@ -105,16 +103,13 @@ private: Vector projection_views_extensions; // view configuration - uint32_t num_view_configuration_types = 0; - XrViewConfigurationType *supported_view_configuration_types = nullptr; + LocalVector supported_view_configuration_types; // reference spaces - uint32_t num_reference_spaces = 0; - XrReferenceSpaceType *supported_reference_spaces = nullptr; + LocalVector supported_reference_spaces; // swapchains (note these are platform dependent) - uint32_t num_swapchain_formats = 0; - int64_t *supported_swapchain_formats = nullptr; + PackedInt64Array supported_swapchain_formats; // system info String runtime_name; @@ -130,8 +125,7 @@ private: // blend mode XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; XrEnvironmentBlendMode requested_environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; - uint32_t num_supported_environment_blend_modes = 0; - XrEnvironmentBlendMode *supported_environment_blend_modes = nullptr; + Vector supported_environment_blend_modes; bool emulate_environment_blend_mode_alpha_blend = false; // state @@ -150,8 +144,7 @@ private: OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; XrSystemGraphicsProperties graphics_properties; - uint32_t view_count = 0; - XrViewConfigurationView *view_configuration_views = nullptr; + LocalVector view_configuration_views; enum OpenXRSwapChainTypes { OPENXR_SWAPCHAIN_COLOR, @@ -346,10 +339,9 @@ private: uint64_t frame = 0; Rect2i render_region; - uint32_t view_count = 0; - XrView *views = nullptr; - XrCompositionLayerProjectionView *projection_views = nullptr; - XrCompositionLayerDepthInfoKHR *depth_views = nullptr; // Only used by Composition Layer Depth Extension if available + LocalVector views; + LocalVector projection_views; + LocalVector depth_views; // Only used by Composition Layer Depth Extension if available bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. bool view_pose_valid = false; @@ -592,7 +584,7 @@ public: void register_projection_views_extension(OpenXRExtensionWrapper *p_extension); void unregister_projection_views_extension(OpenXRExtensionWrapper *p_extension); - const XrEnvironmentBlendMode *get_supported_environment_blend_modes(uint32_t &count); + const Vector get_supported_environment_blend_modes(); bool is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const; bool set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode); XrEnvironmentBlendMode get_environment_blend_mode() const { return requested_environment_blend_mode; } diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp index f4099824b6..736e3c66e6 100644 --- a/modules/openxr/openxr_api_extension.cpp +++ b/modules/openxr/openxr_api_extension.cpp @@ -58,6 +58,9 @@ void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("get_next_frame_time"), &OpenXRAPIExtension::get_next_frame_time); ClassDB::bind_method(D_METHOD("can_render"), &OpenXRAPIExtension::can_render); + ClassDB::bind_method(D_METHOD("find_action", "name", "action_set"), &OpenXRAPIExtension::find_action); + ClassDB::bind_method(D_METHOD("action_get_handle", "action"), &OpenXRAPIExtension::action_get_handle); + ClassDB::bind_method(D_METHOD("get_hand_tracker", "hand_index"), &OpenXRAPIExtension::get_hand_tracker); ClassDB::bind_method(D_METHOD("register_composition_layer_provider", "extension"), &OpenXRAPIExtension::register_composition_layer_provider); @@ -203,6 +206,17 @@ bool OpenXRAPIExtension::can_render() { return OpenXRAPI::get_singleton()->can_render(); } +RID OpenXRAPIExtension::find_action(const String &p_name, const RID &p_action_set) { + ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), RID()); + return OpenXRAPI::get_singleton()->find_action(p_name, p_action_set); +} + +uint64_t OpenXRAPIExtension::action_get_handle(RID p_action) { + ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0); + XrAction action_hanlde = OpenXRAPI::get_singleton()->action_get_handle(p_action); + return reinterpret_cast(action_hanlde); +} + uint64_t OpenXRAPIExtension::get_hand_tracker(int p_hand_index) { ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), 0); return (uint64_t)OpenXRAPI::get_singleton()->get_hand_tracker(p_hand_index); diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h index be367dda2a..077d39d9c1 100644 --- a/modules/openxr/openxr_api_extension.h +++ b/modules/openxr/openxr_api_extension.h @@ -78,6 +78,9 @@ public: int64_t get_next_frame_time(); bool can_render(); + RID find_action(const String &p_name, const RID &p_action_set = RID()); + uint64_t action_get_handle(RID p_action); + uint64_t get_hand_tracker(int p_hand_index); void register_composition_layer_provider(OpenXRExtensionWrapperExtension *p_extension); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index c546861e0b..2960512347 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -1276,15 +1276,10 @@ Array OpenXRInterface::get_supported_environment_blend_modes() { return modes; } - uint32_t count = 0; - const XrEnvironmentBlendMode *env_blend_modes = openxr_api->get_supported_environment_blend_modes(count); + const Vector env_blend_modes = openxr_api->get_supported_environment_blend_modes(); - if (!env_blend_modes) { - return modes; - } - - for (uint32_t i = 0; i < count; i++) { - switch (env_blend_modes[i]) { + for (const XrEnvironmentBlendMode &env_blend_mode : env_blend_modes) { + switch (env_blend_mode) { case XR_ENVIRONMENT_BLEND_MODE_OPAQUE: modes.push_back(XR_ENV_BLEND_MODE_OPAQUE); break; @@ -1295,7 +1290,7 @@ Array OpenXRInterface::get_supported_environment_blend_modes() { modes.push_back(XR_ENV_BLEND_MODE_ALPHA_BLEND); break; default: - WARN_PRINT("Unsupported blend mode found: " + String::num_int64(int64_t(env_blend_modes[i]))); + WARN_PRINT(vformat("Unsupported blend mode found: %s.", String::num_int64(int64_t(env_blend_mode)))); } } diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index c28ed848c5..2150a3a38a 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -198,7 +198,7 @@ Error RegEx::compile(const String &p_pattern, bool p_show_error) { if (p_show_error) { PCRE2_UCHAR32 buf[256]; pcre2_get_error_message_32(err, buf, 256); - String message = String::num(offset) + ": " + String((const char32_t *)buf); + String message = String::num_int64(offset) + ": " + String((const char32_t *)buf); ERR_PRINT(message.utf8()); } return FAILED; diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 1457848bcf..dd9192896e 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -95,7 +95,7 @@ Error EMWSPeer::connect_to_url(const String &p_url, Ref p_tls_option requested_url = scheme + host; if (port && ((scheme == "ws://" && port != 80) || (scheme == "wss://" && port != 443))) { - requested_url += ":" + String::num(port); + requested_url += ":" + String::num_int64(port); } if (!path.is_empty()) { diff --git a/platform/android/.editorconfig b/platform/android/.editorconfig new file mode 100644 index 0000000000..47198ce223 --- /dev/null +++ b/platform/android/.editorconfig @@ -0,0 +1,2 @@ +[{*.gradle,AndroidManifest.xml}] +indent_style = space diff --git a/platform/android/SCsub b/platform/android/SCsub index 66c955252b..d0928a937b 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -29,6 +29,7 @@ android_files = [ "plugin/godot_plugin_jni.cpp", "rendering_context_driver_vulkan_android.cpp", "variant/callable_jni.cpp", + "dialog_utils_jni.cpp", ] env_android = env.Clone() diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index efabc2e565..ff001b603b 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -132,7 +132,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod _set_key_modifier_state(ev, keycode); - if (p_physical_keycode == AKEYCODE_BACK) { + if (p_physical_keycode == AKEYCODE_BACK && p_pressed) { if (DisplayServerAndroid *dsa = Object::cast_to(DisplayServer::get_singleton())) { dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST, true); } diff --git a/platform/android/dialog_utils_jni.cpp b/platform/android/dialog_utils_jni.cpp new file mode 100644 index 0000000000..0ea4b57aae --- /dev/null +++ b/platform/android/dialog_utils_jni.cpp @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* dialog_utils_jni.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "dialog_utils_jni.h" + +#include "display_server_android.h" +#include "jni_utils.h" + +extern "C" { + +JNIEXPORT void JNICALL Java_org_redotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index) { + DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); + if (ds) { + ds->emit_dialog_callback(p_button_index); + } +} + +JNIEXPORT void JNICALL Java_org_redotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) { + DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); + if (ds) { + String text = jstring_to_string(p_text, env); + ds->emit_input_dialog_callback(text); + } +} +} diff --git a/platform/android/dialog_utils_jni.h b/platform/android/dialog_utils_jni.h new file mode 100644 index 0000000000..f74307701d --- /dev/null +++ b/platform/android/dialog_utils_jni.h @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* dialog_utils_jni.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DIALOG_UTILS_JNI_H +#define DIALOG_UTILS_JNI_H + +#include + +extern "C" { +JNIEXPORT void JNICALL Java_org_redotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index); +JNIEXPORT void JNICALL Java_org_redotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text); +} + +#endif // DIALOG_UTILS_JNI_H diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index e7b54a9aac..03c82f4c7e 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -72,7 +72,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { //case FEATURE_IME: case FEATURE_MOUSE: //case FEATURE_MOUSE_WARP: - //case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG: case FEATURE_NATIVE_DIALOG_INPUT: case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_DIALOG_FILE_EXTRA: @@ -180,6 +180,19 @@ bool DisplayServerAndroid::clipboard_has() const { } } +Error DisplayServerAndroid::dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_NULL_V(godot_java, FAILED); + dialog_callback = p_callback; + return godot_java->show_dialog(p_title, p_description, p_buttons); +} + +void DisplayServerAndroid::emit_dialog_callback(int p_button_index) { + if (dialog_callback.is_valid()) { + dialog_callback.call_deferred(p_button_index); + } +} + Error DisplayServerAndroid::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) { GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); ERR_FAIL_NULL_V(godot_java, FAILED); diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 1968f1fe5e..70d61478bb 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -89,7 +89,9 @@ class DisplayServerAndroid : public DisplayServer { Callable system_theme_changed; + Callable dialog_callback; Callable input_dialog_callback; + Callable file_picker_callback; void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const; @@ -121,6 +123,9 @@ public: virtual String clipboard_get() const override; virtual bool clipboard_has() const override; + virtual Error dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) override; + void emit_dialog_callback(int p_button_index); + virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; void emit_input_dialog_callback(String p_text); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 97696cef32..5cd3fc2f2e 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1919,6 +1919,21 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport } } } + } else if (p_name == "package/show_in_android_tv") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (bool(p_preset->get("package/show_in_android_tv")) && !gradle_build_enabled) { + return TTR("\"Use Gradle Build\" must be enabled to enable \"Show In Android Tv\"."); + } + } else if (p_name == "package/show_as_launcher_app") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (bool(p_preset->get("package/show_as_launcher_app")) && !gradle_build_enabled) { + return TTR("\"Use Gradle Build\" must be enabled to enable \"Show As Launcher App\"."); + } + } else if (p_name == "package/show_in_app_library") { + bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); + if (!bool(p_preset->get("package/show_in_app_library")) && !gradle_build_enabled) { + return TTR("\"Use Gradle Build\" must be enabled to disable \"Show In App Library\"."); + } } } return String(); @@ -2285,15 +2300,31 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, } args.push_back("-a"); args.push_back("android.intent.action.MAIN"); - args.push_back("-n"); - args.push_back(get_package_name(package_name) + "/com.godot.game.GodotApp"); + + // Going with implicit launch first based on the LAUNCHER category and the app's package. + args.push_back("-c"); + args.push_back("android.intent.category.LAUNCHER"); + args.push_back(get_package_name(package_name)); output.clear(); err = OS::get_singleton()->execute(adb, args, &output, &rv, true); print_verbose(output); - if (err || rv != 0) { - add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device.")); - CLEANUP_AND_RETURN(ERR_CANT_CREATE); + if (err || rv != 0 || output.contains("Error: Activity not started")) { + // The implicit launch failed, let's try an explicit launch by specifying the component name before giving up. + const String component_name = get_package_name(package_name) + "/com.godot.game.GodotApp"; + print_line("Implicit launch failed.. Trying explicit launch using", component_name); + args.erase(get_package_name(package_name)); + args.push_back("-n"); + args.push_back(component_name); + + output.clear(); + err = OS::get_singleton()->execute(adb, args, &output, &rv, true); + print_verbose(output); + + if (err || rv != 0 || output.begins_with("Error: Activity not started")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device.")); + CLEANUP_AND_RETURN(ERR_CANT_CREATE); + } } CLEANUP_AND_RETURN(OK); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 755909c96e..db7afff376 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -262,7 +262,7 @@ String _get_screen_sizes_tag(const Ref &p_preset) { String _get_activity_tag(const Ref &p_export_platform, const Ref &p_preset, bool p_debug) { String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation")))); String manifest_activity_text = vformat( - " ): EditorWindowInfo { var hasEditor = false - var xrModeOn = false + var xrMode = XR_MODE_DEFAULT var i = 0 while (i < args.size) { when (args[i++]) { EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true XR_MODE_ARG -> { - val argValue = args[i++] - xrModeOn = xrModeOn || ("on" == argValue) + xrMode = args[i++] } } } @@ -76,7 +88,8 @@ open class GodotEditor : BaseGodotEditor() { return if (hasEditor) { EDITOR_MAIN_INFO } else { - val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() + val openxrEnabled = xrMode == XR_MODE_ON || + (xrMode == XR_MODE_DEFAULT && GodotLib.getGlobal("xr/openxr/enabled").toBoolean()) if (openxrEnabled && isNativeXRDevice()) { XR_RUN_GAME_INFO } else { diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index b89afafe57..706cfd9d00 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -55,6 +55,7 @@ import org.redotengine.godot.error.Error import org.redotengine.godot.utils.PermissionsUtil import org.redotengine.godot.utils.ProcessPhoenix import org.redotengine.godot.utils.isHorizonOSDevice +import org.redotengine.godot.utils.isPicoOSDevice import org.redotengine.godot.utils.isNativeXRDevice import java.util.* import kotlin.math.min @@ -542,6 +543,10 @@ abstract class BaseGodotEditor : GodotActivity() { return isHorizonOSDevice() } + if (featureTag == "picoos") { + return isPicoOSDevice() + } + return false } } diff --git a/platform/android/java/editor/src/picoos/AndroidManifest.xml b/platform/android/java/editor/src/picoos/AndroidManifest.xml new file mode 100644 index 0000000000..13ab40f90f --- /dev/null +++ b/platform/android/java/editor/src/picoos/AndroidManifest.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/android/java/editor/src/picoos/assets/vr_splash.png b/platform/android/java/editor/src/picoos/assets/vr_splash.png new file mode 100644 index 0000000000..7bddd4325a Binary files /dev/null and b/platform/android/java/editor/src/picoos/assets/vr_splash.png differ diff --git a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt new file mode 100644 index 0000000000..ee08c3bb1d --- /dev/null +++ b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotEditor.kt @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* GodotEditor.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.redotengine.editor + +import org.redotengine.godot.GodotLib +import org.redotengine.godot.utils.isNativeXRDevice + +/** + * Primary window of the Godot Editor. + * + * This is the implementation of the editor used when running on PicoOS devices. + */ +open class GodotEditor : BaseGodotEditor() { + + companion object { + private val TAG = GodotEditor::class.java.simpleName + + internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame") + } + + override fun retrieveEditorWindowInfo(args: Array): EditorWindowInfo { + var hasEditor = false + var xrModeOn = false + + var i = 0 + while (i < args.size) { + when (args[i++]) { + EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true + XR_MODE_ARG -> { + val argValue = args[i++] + xrModeOn = xrModeOn || ("on" == argValue) + } + } + } + + return if (hasEditor) { + EDITOR_MAIN_INFO + } else { + val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() + if (openxrEnabled && isNativeXRDevice()) { + XR_RUN_GAME_INFO + } else { + RUN_GAME_INFO + } + } + } + + override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { + return when (instanceId) { + XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO + else -> super.getEditorWindowInfoForInstanceId(instanceId) + } + } +} diff --git a/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt new file mode 100644 index 0000000000..6108b994e5 --- /dev/null +++ b/platform/android/java/editor/src/picoos/java/org/godotengine/editor/GodotXRGame.kt @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* GodotXRGame.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.redotengine.editor + +import org.redotengine.godot.GodotLib +import org.redotengine.godot.xr.XRMode + +/** + * Provide support for running XR apps / games from the editor window. + */ +open class GodotXRGame: GodotGame() { + + override fun overrideOrientationRequest() = true + + override fun updateCommandLineParams(args: List) { + val updatedArgs = ArrayList() + if (!args.contains(XRMode.OPENXR.cmdLineArg)) { + updatedArgs.add(XRMode.OPENXR.cmdLineArg) + } + if (!args.contains(XR_MODE_ARG)) { + updatedArgs.add(XR_MODE_ARG) + updatedArgs.add("on") + } + updatedArgs.addAll(args) + + super.updateCommandLineParams(updatedArgs) + } + + override fun getEditorWindowInfo() = XR_RUN_GAME_INFO + +} diff --git a/platform/android/java/lib/res/mipmap-hdpi/icon_monochrome.png b/platform/android/java/lib/res/mipmap-hdpi/icon_monochrome.png new file mode 100644 index 0000000000..294caf2dd0 Binary files /dev/null and b/platform/android/java/lib/res/mipmap-hdpi/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/mipmap-mdpi/icon_monochrome.png b/platform/android/java/lib/res/mipmap-mdpi/icon_monochrome.png new file mode 100644 index 0000000000..d6dd804f10 Binary files /dev/null and b/platform/android/java/lib/res/mipmap-mdpi/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/mipmap-xhdpi/icon_monochrome.png b/platform/android/java/lib/res/mipmap-xhdpi/icon_monochrome.png new file mode 100644 index 0000000000..6e1665aa57 Binary files /dev/null and b/platform/android/java/lib/res/mipmap-xhdpi/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/mipmap-xxhdpi/icon_monochrome.png b/platform/android/java/lib/res/mipmap-xxhdpi/icon_monochrome.png new file mode 100644 index 0000000000..f68976b9e1 Binary files /dev/null and b/platform/android/java/lib/res/mipmap-xxhdpi/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/mipmap-xxxhdpi/icon_monochrome.png b/platform/android/java/lib/res/mipmap-xxxhdpi/icon_monochrome.png new file mode 100644 index 0000000000..86627deb45 Binary files /dev/null and b/platform/android/java/lib/res/mipmap-xxxhdpi/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/mipmap/icon_monochrome.png b/platform/android/java/lib/res/mipmap/icon_monochrome.png index 28f59ea119..d6dd804f10 100644 Binary files a/platform/android/java/lib/res/mipmap/icon_monochrome.png and b/platform/android/java/lib/res/mipmap/icon_monochrome.png differ diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml index 287d1c8920..cc0f0788af 100644 --- a/platform/android/java/lib/res/values/dimens.xml +++ b/platform/android/java/lib/res/values/dimens.xml @@ -1,6 +1,8 @@ 48dp - 10dp - 5dp + 48dp + 10dp + 16dp + 8dp diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 32c296c505..1de172c861 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -46,7 +46,6 @@ import android.os.* import android.util.Log import android.util.TypedValue import android.view.* -import android.widget.EditText import android.widget.FrameLayout import androidx.annotation.Keep import androidx.annotation.StringRes @@ -67,6 +66,7 @@ import org.redotengine.godot.plugin.GodotPlugin import org.redotengine.godot.plugin.GodotPluginRegistry import org.redotengine.godot.tts.GodotTTS import org.redotengine.godot.utils.CommandLineFileParser +import org.redotengine.godot.utils.DialogUtils import org.redotengine.godot.utils.GodotNetUtils import org.redotengine.godot.utils.PermissionsUtil import org.redotengine.godot.utils.PermissionsUtil.requestPermission @@ -905,27 +905,27 @@ class Godot(private val context: Context) { } /** - * Popup a dialog to input text. + * This method shows a dialog with multiple buttons. + * + * @param title The title of the dialog. + * @param message The message displayed in the dialog. + * @param buttons An array of button labels to display. + */ + @Keep + private fun showDialog(title: String, message: String, buttons: Array) { + getActivity()?.let { DialogUtils.showDialog(it, title, message, buttons) } + } + + /** + * This method shows a dialog with a text input field, allowing the user to input text. + * + * @param title The title of the input dialog. + * @param message The message displayed in the input dialog. + * @param existingText The existing text that will be pre-filled in the input field. */ @Keep private fun showInputDialog(title: String, message: String, existingText: String) { - val activity: Activity = getActivity() ?: return - val inputField = EditText(activity) - val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_horizontal) - val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_vertical) - inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) - inputField.setText(existingText) - runOnUiThread { - val builder = AlertDialog.Builder(activity) - builder.setMessage(message).setTitle(title).setView(inputField) - builder.setPositiveButton(R.string.dialog_ok) { - dialog: DialogInterface, id: Int -> - GodotLib.inputDialogCallback(inputField.text.toString()) - dialog.dismiss() - } - val dialog = builder.create() - dialog.show() - } + getActivity()?.let { DialogUtils.showInputDialog(it, title, message, existingText) } } @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index e6e808d74c..8e678111ed 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -237,11 +237,6 @@ public class GodotLib { */ public static native void onNightModeChanged(); - /** - * Invoked on the input dialog submitted. - */ - public static native void inputDialogCallback(String p_text); - /** * Invoked on the file picker closed. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt index a5c17243a0..b93e42df21 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt @@ -46,9 +46,16 @@ fun isHorizonOSDevice(): Boolean { return "Oculus".equals(Build.BRAND, true) } +/** + * Returns true if running on PICO OS. + */ +fun isPicoOSDevice(): Boolean { + return ("Pico".equals(Build.BRAND, true)) +} + /** * Returns true if running on a native Android XR device. */ fun isNativeXRDevice(): Boolean { - return isHorizonOSDevice() + return isHorizonOSDevice() || isPicoOSDevice() } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt new file mode 100644 index 0000000000..0161236455 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt @@ -0,0 +1,185 @@ +/**************************************************************************/ +/* DialogUtils.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.redotengine.godot.utils + +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout + +import org.redotengine.godot.R + +/** + * Utility class for managing dialogs. + */ +internal class DialogUtils { + companion object { + private val TAG = DialogUtils::class.java.simpleName + + /** + * Invoked on dialog button press. + */ + @JvmStatic + private external fun dialogCallback(buttonIndex: Int) + + /** + * Invoked on the input dialog submitted. + */ + @JvmStatic + private external fun inputDialogCallback(text: String) + + /** + * Displays a dialog with dynamically arranged buttons based on their text length. + * + * The buttons are laid out in rows, with a maximum of 2 buttons per row. If a button's text + * is too long to fit within half the screen width, it occupies the entire row. + * + * @param activity The activity where the dialog will be displayed. + * @param title The title of the dialog. + * @param message The message displayed in the dialog. + * @param buttons An array of button labels to display. + */ + fun showDialog(activity: Activity, title: String, message: String, buttons: Array) { + var dismissDialog: () -> Unit = {} // Helper to dismiss the Dialog when a button is clicked. + activity.runOnUiThread { + val builder = AlertDialog.Builder(activity) + builder.setTitle(title) + builder.setMessage(message) + + val buttonHeight = activity.resources.getDimensionPixelSize(R.dimen.button_height) + val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal) + val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical) + val buttonPadding = activity.resources.getDimensionPixelSize(R.dimen.button_padding) + + // Create a vertical parent layout to hold all rows of buttons. + val parentLayout = LinearLayout(activity) + parentLayout.orientation = LinearLayout.VERTICAL + parentLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) + + // Horizontal row layout for arranging buttons. + var rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + rowLayout.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + + // Calculate the maximum width for a button to allow two buttons per row. + val screenWidth = activity.resources.displayMetrics.widthPixels + val availableWidth = screenWidth - (2 * paddingHorizontal) + val maxButtonWidth = availableWidth / 2 + + buttons.forEachIndexed { index, buttonLabel -> + val button = Button(activity) + button.text = buttonLabel + button.isSingleLine = true + button.setPadding(buttonPadding, buttonPadding, buttonPadding, buttonPadding) + + // Measure the button to determine its width. + button.measure(0, 0) + val buttonWidth = button.measuredWidth + + val params = LinearLayout.LayoutParams( + if (buttonWidth > maxButtonWidth) LinearLayout.LayoutParams.MATCH_PARENT else 0, + buttonHeight + ) + params.weight = if (buttonWidth > maxButtonWidth) 0f else 1f + button.layoutParams = params + + // Handle full-width buttons by finalizing the current row, if needed. + if (buttonWidth > maxButtonWidth) { + if (rowLayout.childCount > 0) { + parentLayout.addView(rowLayout) + rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + } + // Add the full-width button directly to the parent layout. + parentLayout.addView(button) + } else { + rowLayout.addView(button) + + // Finalize the row if it reaches 2 buttons. + if (rowLayout.childCount == 2) { + parentLayout.addView(rowLayout) + rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + } + + // Handle the last button with incomplete row. + if (index == buttons.size - 1 && rowLayout.childCount > 0) { + parentLayout.addView(rowLayout) + } + } + + button.setOnClickListener { + dialogCallback(index) + dismissDialog() + } + } + + // Attach the parent layout to the dialog. + builder.setView(parentLayout) + val dialog = builder.create() + dismissDialog = {dialog.dismiss()} + dialog.show() + } + } + + /** + * This method shows a dialog with a text input field, allowing the user to input text. + * + * @param activity The activity where the input dialog will be displayed. + * @param title The title of the input dialog. + * @param message The message displayed in the input dialog. + * @param existingText The existing text that will be pre-filled in the input field. + */ + fun showInputDialog(activity: Activity, title: String, message: String, existingText: String) { + val inputField = EditText(activity) + val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal) + val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical) + inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) + inputField.setText(existingText) + activity.runOnUiThread { + val builder = AlertDialog.Builder(activity) + builder.setMessage(message).setTitle(title).setView(inputField) + builder.setPositiveButton(R.string.dialog_ok) { + dialog: DialogInterface, id: Int -> + inputDialogCallback(inputField.text.toString()) + dialog.dismiss() + } + val dialog = builder.create() + dialog.show() + } + } + } +} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 9280a30290..f72c222826 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -496,14 +496,6 @@ JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_onNightModeChanged(JN } } -JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) { - DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); - if (ds) { - String text = jstring_to_string(p_text, env); - ds->emit_input_dialog_callback(text); - } -} - JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) { DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); if (ds) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index a7c01fece8..3ec8cf37ba 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -67,7 +67,6 @@ JNIEXPORT jstring JNICALL Java_org_redotengine_godot_GodotLib_getEditorSetting(J JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height); JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result); JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); -JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text); JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths); JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_redotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 5ac372869d..b873f47825 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -71,6 +71,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;"); _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V"); _has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z"); + _show_dialog = p_env->GetMethodID(godot_class, "showDialog", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"); _show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); _show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V"); _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); @@ -305,6 +306,28 @@ bool GodotJavaWrapper::has_clipboard() { } } +Error GodotJavaWrapper::show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons) { + if (_show_input_dialog) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED); + jstring j_title = env->NewStringUTF(p_title.utf8().get_data()); + jstring j_description = env->NewStringUTF(p_description.utf8().get_data()); + jobjectArray j_buttons = env->NewObjectArray(p_buttons.size(), env->FindClass("java/lang/String"), nullptr); + for (int i = 0; i < p_buttons.size(); ++i) { + jstring j_button = env->NewStringUTF(p_buttons[i].utf8().get_data()); + env->SetObjectArrayElement(j_buttons, i, j_button); + env->DeleteLocalRef(j_button); + } + env->CallVoidMethod(godot_instance, _show_dialog, j_title, j_description, j_buttons); + env->DeleteLocalRef(j_title); + env->DeleteLocalRef(j_description); + env->DeleteLocalRef(j_buttons); + return OK; + } else { + return ERR_UNCONFIGURED; + } +} + Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text) { if (_show_input_dialog) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index f5eaecabaa..aa0fca085d 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -62,6 +62,7 @@ private: jmethodID _get_clipboard = nullptr; jmethodID _set_clipboard = nullptr; jmethodID _has_clipboard = nullptr; + jmethodID _show_dialog = nullptr; jmethodID _show_input_dialog = nullptr; jmethodID _show_file_picker = nullptr; jmethodID _request_permission = nullptr; @@ -111,6 +112,7 @@ public: void set_clipboard(const String &p_text); bool has_has_clipboard(); bool has_clipboard(); + Error show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons); Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text); Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector &p_filters); bool request_permission(const String &p_name); diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 832843960e..44e60892cb 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -3165,7 +3165,7 @@ Error EditorExportPlatformIOS::run(const Ref &p_preset, int if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { cmd_args_list.push_back("--remote-debug"); - cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); + cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num_int64(remote_port)); List breakpoints; ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); diff --git a/platform/ios/ios.mm b/platform/ios/ios.mm index 7e2f90520b..2826900177 100644 --- a/platform/ios/ios.mm +++ b/platform/ios/ios.mm @@ -193,7 +193,7 @@ String iOS::get_model() const { String iOS::get_rate_url(int p_app_id) const { String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID"; - String ret = app_url_path.replace("APP_ID", String::num(p_app_id)); + String ret = app_url_path.replace("APP_ID", String::num_int64(p_app_id)); print_verbose(vformat("Returning rate url %s", ret)); return ret; diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index bebaba2bc7..c6940fe0ea 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -211,6 +211,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_IME: + case FEATURE_WINDOW_DRAG: case FEATURE_CLIPBOARD_PRIMARY: { return true; } break; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index e915c22743..3f27051803 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -154,6 +154,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: case FEATURE_WINDOW_EMBEDDING: + case FEATURE_WINDOW_DRAG: return true; case FEATURE_SCREEN_CAPTURE: return !xwayland; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 6a63da11c4..a9a0210c86 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -140,6 +140,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_SCREEN_CAPTURE: case FEATURE_STATUS_INDICATOR: case FEATURE_WINDOW_EMBEDDING: + case FEATURE_WINDOW_DRAG: case FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE: return true; case FEATURE_EMOJI_AND_SYMBOL_PICKER: diff --git a/scene/2d/cpu_particles_2d.compat.inc b/scene/2d/cpu_particles_2d.compat.inc new file mode 100644 index 0000000000..89fe6770c3 --- /dev/null +++ b/scene/2d/cpu_particles_2d.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* cpu_particles_2d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void CPUParticles2D::_restart_bind_compat_92089() { + restart(false); +} + +void CPUParticles2D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("restart"), &CPUParticles2D::_restart_bind_compat_92089); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/2d/cpu_particles_2d.cpp b/scene/2d/cpu_particles_2d.cpp index 8a6f281d80..224266bcb6 100644 --- a/scene/2d/cpu_particles_2d.cpp +++ b/scene/2d/cpu_particles_2d.cpp @@ -31,6 +31,7 @@ /**************************************************************************/ #include "cpu_particles_2d.h" +#include "cpu_particles_2d.compat.inc" #include "scene/2d/gpu_particles_2d.h" #include "scene/resources/atlas_texture.h" @@ -263,7 +264,7 @@ PackedStringArray CPUParticles2D::get_configuration_warnings() const { return warnings; } -void CPUParticles2D::restart() { +void CPUParticles2D::restart(bool p_keep_seed) { time = 0; frame_remainder = 0; cycle = 0; @@ -277,6 +278,9 @@ void CPUParticles2D::restart() { w[i].active = false; } } + if (!p_keep_seed && !use_fixed_seed) { + seed = Math::rand(); + } set_emitting(true); } @@ -508,6 +512,30 @@ bool CPUParticles2D::get_split_scale() { return split_scale; } +void CPUParticles2D::set_use_fixed_seed(bool p_use_fixed_seed) { + if (p_use_fixed_seed == use_fixed_seed) { + return; + } + use_fixed_seed = p_use_fixed_seed; + notify_property_list_changed(); +} + +bool CPUParticles2D::get_use_fixed_seed() const { + return use_fixed_seed; +} + +void CPUParticles2D::set_seed(uint32_t p_seed) { + seed = p_seed; +} + +uint32_t CPUParticles2D::get_seed() const { + return seed; +} + +void CPUParticles2D::request_particles_process(real_t p_requested_process_time) { + _requested_process_time = p_requested_process_time; +} + void CPUParticles2D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "emitting") { p_property.hint = one_shot ? PROPERTY_HINT_ONESHOT : PROPERTY_HINT_NONE; @@ -539,6 +567,10 @@ void CPUParticles2D::_validate_property(PropertyInfo &p_property) const { if (p_property.name.begins_with("scale_curve_") && !split_scale) { p_property.usage = PROPERTY_USAGE_NONE; } + + if (p_property.name == "seed" && !use_fixed_seed) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } static uint32_t idhash(uint32_t x) { @@ -581,25 +613,28 @@ void CPUParticles2D::_update_internal() { return; } _set_do_redraw(true); - - if (time == 0 && pre_process_time > 0.0) { - double frame_time; - if (fixed_fps > 0) { - frame_time = 1.0 / fixed_fps; - } else { - frame_time = 1.0 / 30.0; - } - - double todo = pre_process_time; - - while (todo >= 0) { - _particles_process(frame_time); - todo -= frame_time; - } + double frame_time; + if (fixed_fps > 0) { + frame_time = 1.0 / fixed_fps; + } else { + frame_time = 1.0 / 30.0; } + double todo = _requested_process_time; + _requested_process_time = 0; + if (time == 0 && pre_process_time > 0.0) { + todo += pre_process_time; + } + real_t tmp_speed = speed_scale; + speed_scale = 1.0; + while (todo > 0) { + _particles_process(frame_time); + todo -= frame_time; + } + speed_scale = tmp_speed; + + todo = 0.0; if (fixed_fps > 0) { - double frame_time = 1.0 / fixed_fps; double decr = frame_time; double ldelta = delta; @@ -608,13 +643,12 @@ void CPUParticles2D::_update_internal() { } else if (ldelta <= 0.0) { //unlikely but.. ldelta = 0.001; } - double todo = frame_remainder + ldelta; + todo = frame_remainder + ldelta; while (todo >= frame_time) { _particles_process(frame_time); todo -= decr; } - frame_remainder = todo; } else { @@ -669,13 +703,13 @@ void CPUParticles2D::_particles_process(double p_delta) { double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { - uint32_t seed = cycle; + uint32_t _seed = cycle; if (restart_phase >= system_phase) { - seed -= uint32_t(1); + _seed -= uint32_t(1); } - seed *= uint32_t(pcount); - seed += uint32_t(i); - double random = double(idhash(seed) % uint32_t(65536)) / 65536.0; + _seed *= uint32_t(pcount); + _seed += uint32_t(i); + double random = double(idhash(_seed) % uint32_t(65536)) / 65536.0; restart_phase += randomness_ratio * random * 1.0 / double(pcount); } @@ -736,22 +770,23 @@ void CPUParticles2D::_particles_process(double p_delta) { tex_anim_offset = curve_parameters[PARAM_ANGLE]->sample(tv); } - p.seed = Math::rand(); + p.seed = seed + uint32_t(i) + i + cycle; + uint32_t _seed = p.seed; - p.angle_rand = Math::randf(); - p.scale_rand = Math::randf(); - p.hue_rot_rand = Math::randf(); - p.anim_offset_rand = Math::randf(); + p.angle_rand = rand_from_seed(_seed); + p.scale_rand = rand_from_seed(_seed); + p.hue_rot_rand = rand_from_seed(_seed); + p.anim_offset_rand = rand_from_seed(_seed); if (color_initial_ramp.is_valid()) { - p.start_color_rand = color_initial_ramp->get_color_at_offset(Math::randf()); + p.start_color_rand = color_initial_ramp->get_color_at_offset(rand_from_seed(_seed)); } else { p.start_color_rand = Color(1, 1, 1, 1); } - real_t angle1_rad = direction.angle() + Math::deg_to_rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = direction.angle() + Math::deg_to_rad((rand_from_seed(_seed) * 2.0 - 1.0) * spread); Vector2 rot = Vector2(Math::cos(angle1_rad), Math::sin(angle1_rad)); - p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf()); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)rand_from_seed(_seed)); real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.rotation = Math::deg_to_rad(base_angle); @@ -759,7 +794,7 @@ void CPUParticles2D::_particles_process(double p_delta) { p.custom[0] = 0.0; // unused p.custom[1] = 0.0; // phase [0..1] p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); - p.custom[3] = (1.0 - Math::randf() * lifetime_randomness); + p.custom[3] = (1.0 - rand_from_seed(_seed) * lifetime_randomness); p.transform = Transform2D(); p.time = 0; p.lifetime = lifetime * p.custom[3]; @@ -770,17 +805,17 @@ void CPUParticles2D::_particles_process(double p_delta) { //do none } break; case EMISSION_SHAPE_SPHERE: { - real_t t = Math_TAU * Math::randf(); - real_t radius = emission_sphere_radius * Math::randf(); + real_t t = Math_TAU * rand_from_seed(_seed); + real_t radius = emission_sphere_radius * rand_from_seed(_seed); p.transform[2] = Vector2(Math::cos(t), Math::sin(t)) * radius; } break; case EMISSION_SHAPE_SPHERE_SURFACE: { - real_t s = Math::randf(), t = Math_TAU * Math::randf(); + real_t s = rand_from_seed(_seed), t = Math_TAU * rand_from_seed(_seed); real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); p.transform[2] = Vector2(Math::cos(t), Math::sin(t)) * radius; } break; case EMISSION_SHAPE_RECTANGLE: { - p.transform[2] = Vector2(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_rect_extents; + p.transform[2] = Vector2(rand_from_seed(_seed) * 2.0 - 1.0, rand_from_seed(_seed) * 2.0 - 1.0) * emission_rect_extents; } break; case EMISSION_SHAPE_POINTS: case EMISSION_SHAPE_DIRECTED_POINTS: { @@ -821,8 +856,7 @@ void CPUParticles2D::_particles_process(double p_delta) { p.active = false; tv = 1.0; } else { - uint32_t alt_seed = p.seed; - + uint32_t _seed = p.seed; p.time += local_delta; p.custom[1] = p.time / lifetime; tv = p.time / p.lifetime; @@ -880,18 +914,18 @@ void CPUParticles2D::_particles_process(double p_delta) { Vector2 pos = p.transform[2]; //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(alt_seed)) : Vector2(); + force += p.velocity.length() > 0.0 ? p.velocity.normalized() * tex_linear_accel * Math::lerp(parameters_min[PARAM_LINEAR_ACCEL], parameters_max[PARAM_LINEAR_ACCEL], rand_from_seed(_seed)) : Vector2(); //apply radial acceleration Vector2 org = emission_xform[2]; Vector2 diff = pos - org; - force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(alt_seed)) : Vector2(); + force += diff.length() > 0.0 ? diff.normalized() * (tex_radial_accel)*Math::lerp(parameters_min[PARAM_RADIAL_ACCEL], parameters_max[PARAM_RADIAL_ACCEL], rand_from_seed(_seed)) : Vector2(); //apply tangential acceleration; Vector2 yx = Vector2(diff.y, diff.x); - force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(alt_seed))) : Vector2(); + force += yx.length() > 0.0 ? (yx * Vector2(-1.0, 1.0)).normalized() * (tex_tangential_accel * Math::lerp(parameters_min[PARAM_TANGENTIAL_ACCEL], parameters_max[PARAM_TANGENTIAL_ACCEL], rand_from_seed(_seed))) : Vector2(); //apply attractor forces p.velocity += force * local_delta; //orbit velocity - real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(alt_seed)); + real_t orbit_amount = tex_orbit_velocity * Math::lerp(parameters_min[PARAM_ORBIT_VELOCITY], parameters_max[PARAM_ORBIT_VELOCITY], rand_from_seed(_seed)); if (orbit_amount != 0.0) { real_t ang = orbit_amount * local_delta * Math_TAU; // Not sure why the ParticleProcessMaterial code uses a clockwise rotation matrix, @@ -906,7 +940,7 @@ void CPUParticles2D::_particles_process(double p_delta) { if (parameters_max[PARAM_DAMPING] + tex_damping > 0.0) { real_t v = p.velocity.length(); - real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(alt_seed)); + real_t damp = tex_damping * Math::lerp(parameters_min[PARAM_DAMPING], parameters_max[PARAM_DAMPING], rand_from_seed(_seed)); v -= damp * local_delta; if (v < 0.0) { p.velocity = Vector2(); @@ -915,9 +949,9 @@ void CPUParticles2D::_particles_process(double p_delta) { } } real_t base_angle = (tex_angle)*Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); - base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(alt_seed)); + base_angle += p.custom[1] * lifetime * tex_angular_velocity * Math::lerp(parameters_min[PARAM_ANGULAR_VELOCITY], parameters_max[PARAM_ANGULAR_VELOCITY], rand_from_seed(_seed)); p.rotation = Math::deg_to_rad(base_angle); //angle - p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + tv * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(alt_seed)); + p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand) + tv * tex_anim_speed * Math::lerp(parameters_min[PARAM_ANIM_SPEED], parameters_max[PARAM_ANIM_SPEED], rand_from_seed(_seed)); } //apply color //apply hue rotation @@ -1269,6 +1303,7 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fixed_fps", "fps"), &CPUParticles2D::set_fixed_fps); ClassDB::bind_method(D_METHOD("set_fractional_delta", "enable"), &CPUParticles2D::set_fractional_delta); ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &CPUParticles2D::set_speed_scale); + ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &CPUParticles2D::request_particles_process); ClassDB::bind_method(D_METHOD("is_emitting"), &CPUParticles2D::is_emitting); ClassDB::bind_method(D_METHOD("get_amount"), &CPUParticles2D::get_amount); @@ -1282,6 +1317,11 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_fixed_fps"), &CPUParticles2D::get_fixed_fps); ClassDB::bind_method(D_METHOD("get_fractional_delta"), &CPUParticles2D::get_fractional_delta); ClassDB::bind_method(D_METHOD("get_speed_scale"), &CPUParticles2D::get_speed_scale); + ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &CPUParticles2D::set_use_fixed_seed); + ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &CPUParticles2D::get_use_fixed_seed); + + ClassDB::bind_method(D_METHOD("set_seed", "seed"), &CPUParticles2D::set_seed); + ClassDB::bind_method(D_METHOD("get_seed"), &CPUParticles2D::get_seed); ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &CPUParticles2D::set_draw_order); @@ -1290,10 +1330,11 @@ void CPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &CPUParticles2D::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &CPUParticles2D::get_texture); - ClassDB::bind_method(D_METHOD("restart"), &CPUParticles2D::restart); + ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &CPUParticles2D::restart, DEFVAL(false)); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); // FIXME: Evaluate support for `exp` in integer properties, or remove this. + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); @@ -1301,6 +1342,8 @@ void CPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed"), "set_use_fixed_seed", "get_use_fixed_seed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); @@ -1308,7 +1351,6 @@ void CPUParticles2D::_bind_methods() { // No visibility_rect property contrarily to Particles2D, it's updated automatically. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates"); ADD_PROPERTY(PropertyInfo(Variant::INT, "draw_order", PROPERTY_HINT_ENUM, "Index,Lifetime"), "set_draw_order", "get_draw_order"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); diff --git a/scene/2d/cpu_particles_2d.h b/scene/2d/cpu_particles_2d.h index 4148ca7621..c52ea76a93 100644 --- a/scene/2d/cpu_particles_2d.h +++ b/scene/2d/cpu_particles_2d.h @@ -135,6 +135,7 @@ private: double lifetime = 1.0; double pre_process_time = 0.0; + double _requested_process_time = 0.0; real_t explosiveness_ratio = 0.0; real_t randomness_ratio = 0.0; double lifetime_randomness = 0.0; @@ -142,6 +143,8 @@ private: bool local_coords = false; int fixed_fps = 0; bool fractional_delta = true; + uint32_t seed = 0; + bool use_fixed_seed = false; Transform2D inv_emission_transform; @@ -197,6 +200,11 @@ protected: void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; +#ifndef DISABLE_DEPRECATED + void _restart_bind_compat_92089(); + static void _bind_compatibility_methods(); +#endif + public: void set_emitting(bool p_emitting); void set_amount(int p_amount); @@ -232,6 +240,14 @@ public: void set_texture(const Ref &p_texture); Ref get_texture() const; + void set_use_fixed_seed(bool p_use_fixed_seed); + bool get_use_fixed_seed() const; + + void set_seed(uint32_t p_seed); + uint32_t get_seed() const; + + void request_particles_process(real_t p_requested_process_time); + /////////////////// void set_direction(Vector2 p_direction); @@ -286,7 +302,7 @@ public: PackedStringArray get_configuration_warnings() const override; - void restart(); + void restart(bool p_keep_seed = false); void convert_from_particles(Node *p_particles); diff --git a/scene/2d/gpu_particles_2d.compat.inc b/scene/2d/gpu_particles_2d.compat.inc new file mode 100644 index 0000000000..bff4002c09 --- /dev/null +++ b/scene/2d/gpu_particles_2d.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* gpu_particles_2d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void GPUParticles2D::_restart_bind_compat_92089() { + restart(false); +} + +void GPUParticles2D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("restart"), &GPUParticles2D::_restart_bind_compat_92089); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index 674a085ff1..e11bac98bb 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -31,6 +31,7 @@ /**************************************************************************/ #include "gpu_particles_2d.h" +#include "gpu_particles_2d.compat.inc" #include "scene/2d/cpu_particles_2d.h" #include "scene/resources/atlas_texture.h" @@ -325,6 +326,31 @@ float GPUParticles2D::get_interp_to_end() const { return interp_to_end_factor; } +void GPUParticles2D::set_use_fixed_seed(bool p_use_fixed_seed) { + if (p_use_fixed_seed == use_fixed_seed) { + return; + } + use_fixed_seed = p_use_fixed_seed; + notify_property_list_changed(); +} + +bool GPUParticles2D::get_use_fixed_seed() const { + return use_fixed_seed; +} + +void GPUParticles2D::set_seed(uint32_t p_seed) { + seed = p_seed; + RS::get_singleton()->particles_set_seed(particles, p_seed); +} + +uint32_t GPUParticles2D::get_seed() const { + return seed; +} + +void GPUParticles2D::request_particles_process(real_t p_requested_process_time) { + RS::get_singleton()->particles_request_process_time(particles, p_requested_process_time); +} + PackedStringArray GPUParticles2D::get_configuration_warnings() const { PackedStringArray warnings = Node2D::get_configuration_warnings(); @@ -383,6 +409,9 @@ Ref GPUParticles2D::get_texture() const { } void GPUParticles2D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "seed" && !use_fixed_seed) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } if (p_property.name == "emitting") { p_property.hint = one_shot ? PROPERTY_HINT_ONESHOT : PROPERTY_HINT_NONE; } @@ -442,7 +471,10 @@ float GPUParticles2D::get_amount_ratio() const { return amount_ratio; } -void GPUParticles2D::restart() { +void GPUParticles2D::restart(bool p_keep_seed) { + if (!p_keep_seed && !use_fixed_seed) { + set_seed(Math::rand()); + } RS::get_singleton()->particles_restart(particles); RS::get_singleton()->particles_set_emitting(particles, true); @@ -769,6 +801,8 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size); ClassDB::bind_method(D_METHOD("set_interp_to_end", "interp"), &GPUParticles2D::set_interp_to_end); + ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &GPUParticles2D::request_particles_process); + ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles2D::is_emitting); ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles2D::get_amount); ClassDB::bind_method(D_METHOD("get_lifetime"), &GPUParticles2D::get_lifetime); @@ -794,7 +828,7 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("capture_rect"), &GPUParticles2D::capture_rect); - ClassDB::bind_method(D_METHOD("restart"), &GPUParticles2D::restart); + ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &GPUParticles2D::restart, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles2D::set_sub_emitter); ClassDB::bind_method(D_METHOD("get_sub_emitter"), &GPUParticles2D::get_sub_emitter); @@ -818,6 +852,12 @@ void GPUParticles2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles2D::set_amount_ratio); ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles2D::get_amount_ratio); + ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &GPUParticles2D::set_use_fixed_seed); + ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &GPUParticles2D::get_use_fixed_seed); + + ClassDB::bind_method(D_METHOD("set_seed", "seed"), &GPUParticles2D::set_seed); + ClassDB::bind_method(D_METHOD("get_seed"), &GPUParticles2D::get_seed); + ADD_SIGNAL(MethodInfo("finished")); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting"); @@ -825,6 +865,7 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount"); // FIXME: Evaluate support for `exp` in integer properties, or remove this. ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_GROUP("Time", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end"); @@ -833,6 +874,8 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed", PROPERTY_HINT_ENUM), "set_use_fixed_seed", "get_use_fixed_seed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); @@ -848,7 +891,6 @@ void GPUParticles2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_trail_sections", "get_trail_sections"); ADD_PROPERTY(PropertyInfo(Variant::INT, "trail_section_subdivisions", PROPERTY_HINT_RANGE, "1,1024,1"), "set_trail_section_subdivisions", "get_trail_section_subdivisions"); ADD_GROUP("Process Material", ""); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ParticleProcessMaterial,ShaderMaterial"), "set_process_material", "get_process_material"); BIND_ENUM_CONSTANT(DRAW_ORDER_INDEX); BIND_ENUM_CONSTANT(DRAW_ORDER_LIFETIME); diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 7a8b22efb9..7ce022e179 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -68,6 +68,8 @@ private: float interp_to_end_factor = 0; Vector3 previous_velocity; Vector2 previous_position; + uint32_t seed = 0; + bool use_fixed_seed = false; #ifdef TOOLS_ENABLED bool show_visibility_rect = false; #endif @@ -103,6 +105,11 @@ protected: void _notification(int p_what); void _update_collision_size(); +#ifndef DISABLE_DEPRECATED + void _restart_bind_compat_92089(); + static void _bind_compatibility_methods(); +#endif + public: void set_emitting(bool p_emitting); void set_amount(int p_amount); @@ -121,6 +128,7 @@ public: void set_trail_sections(int p_sections); void set_trail_section_subdivisions(int p_subdivisions); void set_interp_to_end(float p_interp); + void request_particles_process(real_t p_requested_process_time); #ifdef TOOLS_ENABLED void set_show_visibility_rect(bool p_show_visibility_rect); @@ -168,6 +176,12 @@ public: void set_sub_emitter(const NodePath &p_path); NodePath get_sub_emitter() const; + void set_use_fixed_seed(bool p_use_fixed_seed); + bool get_use_fixed_seed() const; + + void set_seed(uint32_t p_seed); + uint32_t get_seed() const; + enum EmitFlags { EMIT_FLAG_POSITION = RS::PARTICLES_EMIT_FLAG_POSITION, EMIT_FLAG_ROTATION_SCALE = RS::PARTICLES_EMIT_FLAG_ROTATION_SCALE, @@ -178,7 +192,7 @@ public: void emit_particle(const Transform2D &p_transform, const Vector2 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags); - void restart(); + void restart(bool p_keep_seed = false); Rect2 capture_rect() const; void convert_from_particles(Node *p_particles); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 8fb8e9c971..a24882fdae 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -202,6 +202,9 @@ void NavigationRegion2D::set_navigation_polygon(const Ref &p_ #ifdef DEBUG_ENABLED debug_mesh_dirty = true; #endif // DEBUG_ENABLED + + _update_bounds(); + NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, p_navigation_polygon); if (navigation_polygon.is_valid()) { @@ -343,6 +346,8 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed); + ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion2D::get_bounds); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections"); @@ -650,3 +655,26 @@ void NavigationRegion2D::_set_debug_visible(bool p_visible) { } } #endif // DEBUG_ENABLED + +void NavigationRegion2D::_update_bounds() { + if (navigation_polygon.is_null()) { + bounds = Rect2(); + return; + } + + const Vector &vertices = navigation_polygon->get_vertices(); + if (vertices.is_empty()) { + bounds = Rect2(); + return; + } + + const Transform2D gt = is_inside_tree() ? get_global_transform() : get_transform(); + + Rect2 new_bounds; + new_bounds.position = gt.xform(vertices[0]); + + for (const Vector2 &vertex : vertices) { + new_bounds.expand_to(gt.xform(vertex)); + } + bounds = new_bounds; +} diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 6de9607cf8..b76021839d 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -52,6 +52,8 @@ class NavigationRegion2D : public Node2D { void _navigation_polygon_changed(); + Rect2 bounds; + #ifdef DEBUG_ENABLED private: RID debug_mesh_rid; @@ -115,10 +117,13 @@ public: void _bake_finished(Ref p_navigation_polygon); bool is_baking() const; + Rect2 get_bounds() const { return bounds; } + NavigationRegion2D(); ~NavigationRegion2D(); private: + void _update_bounds(); void _region_enter_navigation_map(); void _region_exit_navigation_map(); void _region_update_transform(); diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp index dc28cd3742..ab8136418e 100644 --- a/scene/2d/node_2d.cpp +++ b/scene/2d/node_2d.cpp @@ -489,7 +489,7 @@ void Node2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_relative_transform_to_parent", "parent"), &Node2D::get_relative_transform_to_parent); ADD_GROUP("Transform", ""); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_RANGE, "-99999,99999,or_less,or_greater,hide_slider,suffix:px"), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale", PROPERTY_HINT_LINK), "set_scale", "get_scale"); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 75d9a257bd..0c99fcd704 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -1836,7 +1836,7 @@ void TileMapLayer::_bind_methods() { // --- Runtime --- ClassDB::bind_method(D_METHOD("update_internals"), &TileMapLayer::update_internals); - ClassDB::bind_method(D_METHOD("notify_runtime_tile_data_update"), &TileMapLayer::notify_runtime_tile_data_update, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("notify_runtime_tile_data_update"), &TileMapLayer::notify_runtime_tile_data_update); // --- Shortcuts to methods defined in TileSet --- ClassDB::bind_method(D_METHOD("map_pattern", "position_in_tilemap", "coords_in_pattern", "pattern"), &TileMapLayer::map_pattern); diff --git a/scene/3d/cpu_particles_3d.compat.inc b/scene/3d/cpu_particles_3d.compat.inc new file mode 100644 index 0000000000..c89f0cec3a --- /dev/null +++ b/scene/3d/cpu_particles_3d.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* cpu_particles_3d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void CPUParticles3D::_restart_bind_compat_92089() { + restart(false); +} + +void CPUParticles3D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("restart"), &CPUParticles3D::_restart_bind_compat_92089); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 86b2fa1bbf..99abce0e13 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -31,6 +31,7 @@ /**************************************************************************/ #include "cpu_particles_3d.h" +#include "cpu_particles_3d.compat.inc" #include "scene/3d/camera_3d.h" #include "scene/3d/gpu_particles_3d.h" @@ -233,7 +234,7 @@ PackedStringArray CPUParticles3D::get_configuration_warnings() const { return warnings; } -void CPUParticles3D::restart() { +void CPUParticles3D::restart(bool p_keep_seed) { time = 0; frame_remainder = 0; cycle = 0; @@ -247,6 +248,9 @@ void CPUParticles3D::restart() { w[i].active = false; } } + if (!p_keep_seed && !use_fixed_seed) { + seed = Math::rand(); + } set_emitting(true); } @@ -551,6 +555,30 @@ AABB CPUParticles3D::capture_aabb() const { return RS::get_singleton()->multimesh_get_aabb(multimesh); } +void CPUParticles3D::set_use_fixed_seed(bool p_use_fixed_seed) { + if (p_use_fixed_seed == use_fixed_seed) { + return; + } + use_fixed_seed = p_use_fixed_seed; + notify_property_list_changed(); +} + +bool CPUParticles3D::get_use_fixed_seed() const { + return use_fixed_seed; +} + +void CPUParticles3D::set_seed(uint32_t p_seed) { + seed = p_seed; +} + +uint32_t CPUParticles3D::get_seed() const { + return seed; +} + +void CPUParticles3D::request_particles_process(real_t p_requested_process_time) { + _requested_process_time = p_requested_process_time; +} + void CPUParticles3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "emitting") { p_property.hint = one_shot ? PROPERTY_HINT_ONESHOT : PROPERTY_HINT_NONE; @@ -583,6 +611,10 @@ void CPUParticles3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name.begins_with("scale_curve_") && !split_scale) { p_property.usage = PROPERTY_USAGE_NONE; } + + if (p_property.name == "seed" && !use_fixed_seed) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } static uint32_t idhash(uint32_t x) { @@ -628,25 +660,27 @@ void CPUParticles3D::_update_internal() { bool processed = false; - if (time == 0 && pre_process_time > 0.0) { - double frame_time; - if (fixed_fps > 0) { - frame_time = 1.0 / fixed_fps; - } else { - frame_time = 1.0 / 30.0; - } - - double todo = pre_process_time; - - while (todo >= 0) { - _particles_process(frame_time); - processed = true; - todo -= frame_time; - } + double frame_time; + if (fixed_fps > 0) { + frame_time = 1.0 / fixed_fps; + } else { + frame_time = 1.0 / 30.0; } + double todo = _requested_process_time; + _requested_process_time = 0.; + if (time == 0 && pre_process_time > 0.0) { + todo += pre_process_time; + } + real_t tmp_speed = speed_scale; + speed_scale = 1.0; + while (todo > 0) { + _particles_process(frame_time); + todo -= frame_time; + } + speed_scale = tmp_speed; + todo = 0.0; if (fixed_fps > 0) { - double frame_time = 1.0 / fixed_fps; double decr = frame_time; double ldelta = delta; @@ -655,7 +689,7 @@ void CPUParticles3D::_update_internal() { } else if (ldelta <= 0.0) { //unlikely but.. ldelta = 0.001; } - double todo = frame_remainder + ldelta; + todo = frame_remainder + ldelta; while (todo >= frame_time) { _particles_process(frame_time); @@ -719,13 +753,13 @@ void CPUParticles3D::_particles_process(double p_delta) { double restart_phase = double(i) / double(pcount); if (randomness_ratio > 0.0) { - uint32_t seed = cycle; + uint32_t _seed = cycle; if (restart_phase >= system_phase) { - seed -= uint32_t(1); + _seed -= uint32_t(1); } - seed *= uint32_t(pcount); - seed += uint32_t(i); - double random = double(idhash(seed) % uint32_t(65536)) / 65536.0; + _seed *= uint32_t(pcount); + _seed += uint32_t(i); + double random = double(idhash(_seed) % uint32_t(65536)) / 65536.0; restart_phase += randomness_ratio * random * 1.0 / double(pcount); } @@ -786,27 +820,27 @@ void CPUParticles3D::_particles_process(double p_delta) { tex_anim_offset = curve_parameters[PARAM_ANGLE]->sample(tv); } - p.seed = Math::rand(); - - p.angle_rand = Math::randf(); - p.scale_rand = Math::randf(); - p.hue_rot_rand = Math::randf(); - p.anim_offset_rand = Math::randf(); + p.seed = seed; + uint32_t _seed = seed + uint32_t(1) + i + cycle; + p.angle_rand = rand_from_seed(_seed); + p.scale_rand = rand_from_seed(_seed); + p.hue_rot_rand = rand_from_seed(_seed); + p.anim_offset_rand = rand_from_seed(_seed); if (color_initial_ramp.is_valid()) { - p.start_color_rand = color_initial_ramp->get_color_at_offset(Math::randf()); + p.start_color_rand = color_initial_ramp->get_color_at_offset(rand_from_seed(_seed)); } else { p.start_color_rand = Color(1, 1, 1, 1); } if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { - real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg_to_rad((Math::randf() * 2.0 - 1.0) * spread); + real_t angle1_rad = Math::atan2(direction.y, direction.x) + Math::deg_to_rad((rand_from_seed(_seed) * 2.0 - 1.0) * spread); Vector3 rot = Vector3(Math::cos(angle1_rad), Math::sin(angle1_rad), 0.0); - p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf()); + p.velocity = rot * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], rand_from_seed(_seed)); } else { //initiate velocity spread in 3D - real_t angle1_rad = Math::deg_to_rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * spread); - real_t angle2_rad = Math::deg_to_rad((Math::randf() * (real_t)2.0 - (real_t)1.0) * ((real_t)1.0 - flatness) * spread); + real_t angle1_rad = Math::deg_to_rad((rand_from_seed(_seed) * (real_t)2.0 - (real_t)1.0) * spread); + real_t angle2_rad = Math::deg_to_rad((rand_from_seed(_seed) * (real_t)2.0 - (real_t)1.0) * ((real_t)1.0 - flatness) * spread); Vector3 direction_xz = Vector3(Math::sin(angle1_rad), 0, Math::cos(angle1_rad)); Vector3 direction_yz = Vector3(0, Math::sin(angle2_rad), Math::cos(angle2_rad)); @@ -826,14 +860,14 @@ void CPUParticles3D::_particles_process(double p_delta) { binormal.normalize(); Vector3 normal = binormal.cross(direction_nrm); spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z; - p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], (real_t)Math::randf()); + p.velocity = spread_direction * Math::lerp(parameters_min[PARAM_INITIAL_LINEAR_VELOCITY], parameters_max[PARAM_INITIAL_LINEAR_VELOCITY], rand_from_seed(_seed)); } real_t base_angle = tex_angle * Math::lerp(parameters_min[PARAM_ANGLE], parameters_max[PARAM_ANGLE], p.angle_rand); p.custom[0] = Math::deg_to_rad(base_angle); //angle p.custom[1] = 0.0; //phase p.custom[2] = tex_anim_offset * Math::lerp(parameters_min[PARAM_ANIM_OFFSET], parameters_max[PARAM_ANIM_OFFSET], p.anim_offset_rand); //animation offset (0-1) - p.custom[3] = (1.0 - Math::randf() * lifetime_randomness); + p.custom[3] = (1.0 - rand_from_seed(_seed) * lifetime_randomness); p.transform = Transform3D(); p.time = 0; p.lifetime = lifetime * p.custom[3]; @@ -844,20 +878,20 @@ void CPUParticles3D::_particles_process(double p_delta) { //do none } break; case EMISSION_SHAPE_SPHERE: { - real_t s = 2.0 * Math::randf() - 1.0; - real_t t = Math_TAU * Math::randf(); - real_t x = Math::randf(); + real_t s = 2.0 * rand_from_seed(_seed) - 1.0; + real_t t = Math_TAU * rand_from_seed(_seed); + real_t x = rand_from_seed(_seed); real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); p.transform.origin = Vector3(0, 0, 0).lerp(Vector3(radius * Math::cos(t), radius * Math::sin(t), emission_sphere_radius * s), x); } break; case EMISSION_SHAPE_SPHERE_SURFACE: { - real_t s = 2.0 * Math::randf() - 1.0; - real_t t = Math_TAU * Math::randf(); + real_t s = 2.0 * rand_from_seed(_seed) - 1.0; + real_t t = Math_TAU * rand_from_seed(_seed); real_t radius = emission_sphere_radius * Math::sqrt(1.0 - s * s); p.transform.origin = Vector3(radius * Math::cos(t), radius * Math::sin(t), emission_sphere_radius * s); } break; case EMISSION_SHAPE_BOX: { - p.transform.origin = Vector3(Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0, Math::randf() * 2.0 - 1.0) * emission_box_extents; + p.transform.origin = Vector3(rand_from_seed(_seed) * 2.0 - 1.0, rand_from_seed(_seed) * 2.0 - 1.0, rand_from_seed(_seed) * 2.0 - 1.0) * emission_box_extents; } break; case EMISSION_SHAPE_POINTS: case EMISSION_SHAPE_DIRECTED_POINTS: { @@ -901,11 +935,11 @@ void CPUParticles3D::_particles_process(double p_delta) { case EMISSION_SHAPE_RING: { real_t radius_clamped = MAX(0.001, emission_ring_radius); real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0); - real_t y_pos = Math::randf(); + real_t y_pos = rand_from_seed(_seed); real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5); y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew); - real_t ring_random_angle = Math::randf() * Math_TAU; - real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); + real_t ring_random_angle = rand_from_seed(_seed) * Math_TAU; + real_t ring_random_radius = Math::sqrt(rand_from_seed(_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos); Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized(); Vector3 ortho_axis; @@ -1496,7 +1530,14 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &CPUParticles3D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &CPUParticles3D::get_mesh); - ClassDB::bind_method(D_METHOD("restart"), &CPUParticles3D::restart); + ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &CPUParticles3D::set_use_fixed_seed); + ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &CPUParticles3D::get_use_fixed_seed); + + ClassDB::bind_method(D_METHOD("set_seed", "seed"), &CPUParticles3D::set_seed); + ClassDB::bind_method(D_METHOD("get_seed"), &CPUParticles3D::get_seed); + + ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &CPUParticles3D::restart, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &CPUParticles3D::request_particles_process); ClassDB::bind_method(D_METHOD("capture_aabb"), &CPUParticles3D::capture_aabb); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting"); @@ -1505,9 +1546,12 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,10.0,0.01,or_greater,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed"), "set_use_fixed_seed", "get_use_fixed_seed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 111cd26177..196140f752 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -136,6 +136,7 @@ private: double lifetime = 1.0; double pre_process_time = 0.0; + double _requested_process_time = 0.0; real_t explosiveness_ratio = 0.0; real_t randomness_ratio = 0.0; double lifetime_randomness = 0.0; @@ -144,6 +145,8 @@ private: bool local_coords = false; int fixed_fps = 0; bool fractional_delta = true; + uint32_t seed = 0; + bool use_fixed_seed = false; Transform3D inv_emission_transform; @@ -204,6 +207,11 @@ protected: void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; +#ifndef DISABLE_DEPRECATED + void _restart_bind_compat_92089(); + static void _bind_compatibility_methods(); +#endif + public: AABB get_aabb() const override; @@ -243,6 +251,14 @@ public: void set_mesh(const Ref &p_mesh); Ref get_mesh() const; + void set_use_fixed_seed(bool p_use_fixed_seed = false); + bool get_use_fixed_seed() const; + + void set_seed(uint32_t p_seed); + uint32_t get_seed() const; + + void request_particles_process(real_t p_requested_process_time); + /////////////////// void set_direction(Vector3 p_direction); @@ -312,7 +328,7 @@ public: PackedStringArray get_configuration_warnings() const override; - void restart(); + void restart(bool p_keep_seed = false); void convert_from_particles(Node *p_particles); diff --git a/scene/3d/gpu_particles_3d.compat.inc b/scene/3d/gpu_particles_3d.compat.inc new file mode 100644 index 0000000000..8561aaa204 --- /dev/null +++ b/scene/3d/gpu_particles_3d.compat.inc @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* gpu_particles_3d.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void GPUParticles3D::_restart_bind_compat_92089() { + restart(false); +} + +void GPUParticles3D::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("restart"), &GPUParticles3D::_restart_bind_compat_92089); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index 4a71dc49c9..fb45773417 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -31,6 +31,7 @@ /**************************************************************************/ #include "gpu_particles_3d.h" +#include "gpu_particles_3d.compat.inc" #include "scene/3d/cpu_particles_3d.h" #include "scene/resources/curve_texture.h" @@ -93,11 +94,32 @@ void GPUParticles3D::set_one_shot(bool p_one_shot) { if (is_emitting()) { if (!one_shot) { - RenderingServer::get_singleton()->particles_restart(particles); + restart(); } } } +void GPUParticles3D::set_use_fixed_seed(bool p_use_fixed_seed) { + if (p_use_fixed_seed == use_fixed_seed) { + return; + } + use_fixed_seed = p_use_fixed_seed; + notify_property_list_changed(); +} + +bool GPUParticles3D::get_use_fixed_seed() const { + return use_fixed_seed; +} + +void GPUParticles3D::set_seed(uint32_t p_seed) { + seed = p_seed; + RS::get_singleton()->particles_set_seed(particles, p_seed); +} + +uint32_t GPUParticles3D::get_seed() const { + return seed; +} + void GPUParticles3D::set_pre_process_time(double p_time) { pre_process_time = p_time; RS::get_singleton()->particles_set_pre_process_time(particles, pre_process_time); @@ -399,7 +421,10 @@ PackedStringArray GPUParticles3D::get_configuration_warnings() const { return warnings; } -void GPUParticles3D::restart() { +void GPUParticles3D::restart(bool p_keep_seed) { + if (!p_keep_seed && !use_fixed_seed) { + set_seed(Math::rand()); + } RenderingServer::get_singleton()->particles_restart(particles); RenderingServer::get_singleton()->particles_set_emitting(particles, true); @@ -428,6 +453,13 @@ void GPUParticles3D::_validate_property(PropertyInfo &p_property) const { return; } } + if (p_property.name == "seed" && !use_fixed_seed) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void GPUParticles3D::request_particles_process(real_t p_requested_process_time) { + RS::get_singleton()->particles_request_process_time(particles, p_requested_process_time); } void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) { @@ -527,7 +559,6 @@ void GPUParticles3D::_notification(int p_what) { Ref material = get_process_material(); ERR_FAIL_COND(material.is_null()); - material->disconnect("emission_shape_changed", callable_mp((Node3D *)this, &GPUParticles3D::update_gizmos)); } break; case NOTIFICATION_SUSPENDED: @@ -730,6 +761,12 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles3D::get_collision_base_size); ClassDB::bind_method(D_METHOD("get_interp_to_end"), &GPUParticles3D::get_interp_to_end); + ClassDB::bind_method(D_METHOD("set_use_fixed_seed", "use_fixed_seed"), &GPUParticles3D::set_use_fixed_seed); + ClassDB::bind_method(D_METHOD("get_use_fixed_seed"), &GPUParticles3D::get_use_fixed_seed); + + ClassDB::bind_method(D_METHOD("set_seed", "seed"), &GPUParticles3D::set_seed); + ClassDB::bind_method(D_METHOD("get_seed"), &GPUParticles3D::get_seed); + ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles3D::set_draw_order); ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles3D::get_draw_order); @@ -743,7 +780,7 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skin", "skin"), &GPUParticles3D::set_skin); ClassDB::bind_method(D_METHOD("get_skin"), &GPUParticles3D::get_skin); - ClassDB::bind_method(D_METHOD("restart"), &GPUParticles3D::restart); + ClassDB::bind_method(D_METHOD("restart", "keep_seed"), &GPUParticles3D::restart, DEFVAL(false)); ClassDB::bind_method(D_METHOD("capture_aabb"), &GPUParticles3D::capture_aabb); ClassDB::bind_method(D_METHOD("set_sub_emitter", "path"), &GPUParticles3D::set_sub_emitter); @@ -765,6 +802,8 @@ void GPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles3D::set_amount_ratio); ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles3D::get_amount_ratio); + ClassDB::bind_method(D_METHOD("request_particles_process", "process_time"), &GPUParticles3D::request_particles_process); + ADD_SIGNAL(MethodInfo("finished")); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting", PROPERTY_HINT_ONESHOT), "set_emitting", "is_emitting"); @@ -780,9 +819,12 @@ void GPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "explosiveness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_explosiveness_ratio", "get_explosiveness_ratio"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_randomness_ratio", "get_randomness_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_fixed_seed"), "set_use_fixed_seed", "get_use_fixed_seed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "seed", PROPERTY_HINT_RANGE, "0," + itos(UINT32_MAX) + ",1"), "set_seed", "get_seed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta"); + ADD_GROUP("Collision", "collision_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:m"), "set_collision_base_size", "get_collision_base_size"); ADD_GROUP("Drawing", ""); @@ -845,6 +887,8 @@ GPUParticles3D::GPUParticles3D() { set_speed_scale(1); set_collision_base_size(collision_base_size); set_transform_align(TRANSFORM_ALIGN_DISABLED); + set_use_fixed_seed(false); + set_seed(0); } GPUParticles3D::~GPUParticles3D() { diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index e983626567..3fc56c9f1f 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -80,6 +80,8 @@ private: bool interpolate = true; NodePath sub_emitter; real_t collision_base_size = 0.01; + uint32_t seed = 0; + bool use_fixed_seed = false; bool trail_enabled = false; double trail_lifetime = 0.3; @@ -109,6 +111,11 @@ protected: void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; +#ifndef DISABLE_DEPRECATED + void _restart_bind_compat_92089(); + static void _bind_compatibility_methods(); +#endif + public: AABB get_aabb() const override; @@ -177,7 +184,14 @@ public: void set_transform_align(TransformAlign p_align); TransformAlign get_transform_align() const; - void restart(); + void restart(bool p_keep_seed = false); + + void set_use_fixed_seed(bool p_use_fixed_seed); + bool get_use_fixed_seed() const; + + void set_seed(uint32_t p_seed); + uint32_t get_seed() const; + void request_particles_process(real_t p_requested_process_time); enum EmitFlags { EMIT_FLAG_POSITION = RS::PARTICLES_EMIT_FLAG_POSITION, diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index 0517c27310..f950b8a40c 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -195,6 +195,8 @@ void NavigationRegion3D::set_navigation_mesh(const Ref &p_naviga navigation_mesh->connect_changed(callable_mp(this, &NavigationRegion3D::_navigation_mesh_changed)); } + _update_bounds(); + NavigationServer3D::get_singleton()->region_set_navigation_mesh(region, p_navigation_mesh); #ifdef DEBUG_ENABLED @@ -316,6 +318,8 @@ void NavigationRegion3D::_bind_methods() { ClassDB::bind_method(D_METHOD("bake_navigation_mesh", "on_thread"), &NavigationRegion3D::bake_navigation_mesh, DEFVAL(true)); ClassDB::bind_method(D_METHOD("is_baking"), &NavigationRegion3D::is_baking); + ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion3D::get_bounds); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_mesh", PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh"), "set_navigation_mesh", "get_navigation_mesh"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections"); @@ -742,3 +746,26 @@ void NavigationRegion3D::_update_debug_edge_connections_mesh() { } } #endif // DEBUG_ENABLED + +void NavigationRegion3D::_update_bounds() { + if (navigation_mesh.is_null()) { + bounds = AABB(); + return; + } + + const Vector &vertices = navigation_mesh->get_vertices(); + if (vertices.is_empty()) { + bounds = AABB(); + return; + } + + const Transform3D gt = is_inside_tree() ? get_global_transform() : get_transform(); + + AABB new_bounds; + new_bounds.position = gt.xform(vertices[0]); + + for (const Vector3 &vertex : vertices) { + new_bounds.expand_to(gt.xform(vertex)); + } + bounds = new_bounds; +} diff --git a/scene/3d/navigation_region_3d.h b/scene/3d/navigation_region_3d.h index 7806798848..bb8196c3ac 100644 --- a/scene/3d/navigation_region_3d.h +++ b/scene/3d/navigation_region_3d.h @@ -53,6 +53,8 @@ class NavigationRegion3D : public Node3D { void _navigation_mesh_changed(); + AABB bounds; + #ifdef DEBUG_ENABLED RID debug_instance; RID debug_edge_connections_instance; @@ -112,10 +114,13 @@ public: PackedStringArray get_configuration_warnings() const override; + AABB get_bounds() const { return bounds; } + NavigationRegion3D(); ~NavigationRegion3D(); private: + void _update_bounds(); void _region_enter_navigation_map(); void _region_exit_navigation_map(); void _region_update_transform(); diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 8ce62f502f..e825019ebc 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -1339,7 +1339,7 @@ void Node3D::_bind_methods() { ADD_GROUP("Transform", ""); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0.001,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,or_greater,or_less,hide_slider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians_as_degrees", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation_degrees", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_HIDE_QUATERNION_EDIT, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion"); diff --git a/scene/3d/retarget_modifier_3d.cpp b/scene/3d/retarget_modifier_3d.cpp index 722714fa01..71e9bdaeb5 100644 --- a/scene/3d/retarget_modifier_3d.cpp +++ b/scene/3d/retarget_modifier_3d.cpp @@ -215,6 +215,19 @@ void RetargetModifier3D::_reset_child_skeletons() { child_skeletons.clear(); } +#ifdef TOOLS_ENABLED +void RetargetModifier3D::_force_update_child_skeletons() { + for (const RetargetInfo &E : child_skeletons) { + Skeleton3D *c = Object::cast_to(ObjectDB::get_instance(E.skeleton_id)); + if (!c) { + continue; + } + c->force_update_all_dirty_bones(); + c->emit_signal(SceneStringName(skeleton_updated)); + } +} +#endif // TOOLS_ENABLED + /// General functions void RetargetModifier3D::add_child_notify(Node *p_child) { @@ -454,9 +467,12 @@ void RetargetModifier3D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { _update_child_skeletons(); } break; +#ifdef TOOLS_ENABLED case NOTIFICATION_EDITOR_PRE_SAVE: { _reset_child_skeleton_poses(); + _force_update_child_skeletons(); } break; +#endif // TOOLS_ENABLED case NOTIFICATION_EXIT_TREE: { _reset_child_skeletons(); } break; diff --git a/scene/3d/retarget_modifier_3d.h b/scene/3d/retarget_modifier_3d.h index 8f40ac645c..4b74691059 100644 --- a/scene/3d/retarget_modifier_3d.h +++ b/scene/3d/retarget_modifier_3d.h @@ -72,6 +72,10 @@ private: void _reset_child_skeleton_poses(); void _reset_child_skeletons(); +#ifdef TOOLS_ENABLED + void _force_update_child_skeletons(); +#endif // TOOLS_ENABLED + void cache_rests_with_reset(); void cache_rests(); Vector cache_bone_global_rests(Skeleton3D *p_skeleton); diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index adad2ba99c..20f9ff90e1 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -327,6 +327,12 @@ void Skeleton3D::_notification(int p_what) { update_flags = UPDATE_FLAG_POSE; _notification(NOTIFICATION_UPDATE_SKELETON); } break; +#ifdef TOOLS_ENABLED + case NOTIFICATION_EDITOR_PRE_SAVE: { + force_update_all_dirty_bones(); + emit_signal(SceneStringName(skeleton_updated)); + } break; +#endif // TOOLS_ENABLED case NOTIFICATION_UPDATE_SKELETON: { // Update bone transforms to apply unprocessed poses. force_update_all_dirty_bones(); diff --git a/scene/3d/spring_bone_collision_3d.cpp b/scene/3d/spring_bone_collision_3d.cpp new file mode 100644 index 0000000000..003ed5afbc --- /dev/null +++ b/scene/3d/spring_bone_collision_3d.cpp @@ -0,0 +1,194 @@ +/**************************************************************************/ +/* spring_bone_collision_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_3d.h" + +#include "scene/3d/spring_bone_simulator_3d.h" + +PackedStringArray SpringBoneCollision3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); + + SpringBoneSimulator3D *parent = Object::cast_to(get_parent()); + if (!parent) { + warnings.push_back(RTR("Parent node should be a SpringBoneSimulator3D node.")); + } + + return warnings; +} + +void SpringBoneCollision3D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "bone_name") { + Skeleton3D *sk = get_skeleton(); + if (sk) { + p_property.hint = PROPERTY_HINT_ENUM_SUGGESTION; + p_property.hint_string = sk->get_concatenated_bone_names(); + } else { + p_property.hint = PROPERTY_HINT_NONE; + p_property.hint_string = ""; + } + } else if (bone < 0 && (p_property.name == "position_offset" || p_property.name == "rotation_offset")) { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + +Skeleton3D *SpringBoneCollision3D::get_skeleton() const { + SpringBoneSimulator3D *parent = Object::cast_to(get_parent()); + if (!parent) { + return nullptr; + } + return parent->get_skeleton(); +} + +void SpringBoneCollision3D::set_bone_name(const String &p_name) { + bone_name = p_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_bone(sk->find_bone(bone_name)); + } +} + +String SpringBoneCollision3D::get_bone_name() const { + return bone_name; +} + +void SpringBoneCollision3D::set_bone(int p_bone) { + bone = p_bone; + + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (bone <= -1 || bone >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!"); + bone = -1; + } else { + bone_name = sk->get_bone_name(bone); + } + } + + notify_property_list_changed(); +} + +int SpringBoneCollision3D::get_bone() const { + return bone; +} + +void SpringBoneCollision3D::set_position_offset(const Vector3 &p_offset) { + if (position_offset == p_offset) { + return; + } + position_offset = p_offset; + sync_pose(); +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +Vector3 SpringBoneCollision3D::get_position_offset() const { + return position_offset; +} + +void SpringBoneCollision3D::set_rotation_offset(const Quaternion &p_offset) { + if (rotation_offset == p_offset) { + return; + } + rotation_offset = p_offset; + sync_pose(); +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +Quaternion SpringBoneCollision3D::get_rotation_offset() const { + return rotation_offset; +} + +void SpringBoneCollision3D::sync_pose() { + if (bone >= 0) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + Transform3D tr = sk->get_global_transform() * sk->get_bone_global_pose(bone); + tr.origin += tr.basis.get_rotation_quaternion().xform(position_offset); + tr.basis *= Basis(rotation_offset); + set_global_transform(tr); + } + } +} + +Transform3D SpringBoneCollision3D::get_transform_from_skeleton(const Transform3D &p_center) const { + Transform3D gtr = get_global_transform(); + Skeleton3D *sk = get_skeleton(); + if (sk) { + Transform3D tr = sk->get_global_transform(); + gtr = tr.affine_inverse() * p_center * gtr; + } + return gtr; +} + +void SpringBoneCollision3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skeleton"), &SpringBoneCollision3D::get_skeleton); + + ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &SpringBoneCollision3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name"), &SpringBoneCollision3D::get_bone_name); + + ClassDB::bind_method(D_METHOD("set_bone", "bone"), &SpringBoneCollision3D::set_bone); + ClassDB::bind_method(D_METHOD("get_bone"), &SpringBoneCollision3D::get_bone); + + ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &SpringBoneCollision3D::set_position_offset); + ClassDB::bind_method(D_METHOD("get_position_offset"), &SpringBoneCollision3D::get_position_offset); + + ClassDB::bind_method(D_METHOD("set_rotation_offset", "offset"), &SpringBoneCollision3D::set_rotation_offset); + ClassDB::bind_method(D_METHOD("get_rotation_offset"), &SpringBoneCollision3D::get_rotation_offset); + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_bone", "get_bone"); + + ADD_GROUP("Offset", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position_offset"), "set_position_offset", "get_position_offset"); + ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation_offset"), "set_rotation_offset", "get_rotation_offset"); +} + +Vector3 SpringBoneCollision3D::collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return _collide(p_center, p_bone_radius, p_bone_length, p_current); +} + +Vector3 SpringBoneCollision3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return Vector3(0, 0, 0); +} + +#ifdef TOOLS_ENABLED +void SpringBoneCollision3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_EDITOR_PRE_SAVE: { + sync_pose(); + } break; + } +} +#endif // TOOLS_ENABLED diff --git a/scene/3d/spring_bone_collision_3d.h b/scene/3d/spring_bone_collision_3d.h new file mode 100644 index 0000000000..953772c1e1 --- /dev/null +++ b/scene/3d/spring_bone_collision_3d.h @@ -0,0 +1,77 @@ +/**************************************************************************/ +/* spring_bone_collision_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_3D_H +#define SPRING_BONE_COLLISION_3D_H + +#include "scene/3d/skeleton_3d.h" + +class SpringBoneCollision3D : public Node3D { + GDCLASS(SpringBoneCollision3D, Node3D); + + String bone_name; + int bone = -1; + + Vector3 position_offset; + Quaternion rotation_offset; + +protected: + PackedStringArray get_configuration_warnings() const override; + + void _validate_property(PropertyInfo &p_property) const; + static void _bind_methods(); +#ifdef TOOLS_ENABLED + virtual void _notification(int p_what); +#endif // TOOLS_ENABLED + + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const; + +public: + Skeleton3D *get_skeleton() const; + + void set_bone_name(const String &p_name); + String get_bone_name() const; + void set_bone(int p_bone); + int get_bone() const; + + void set_position_offset(const Vector3 &p_offset); + Vector3 get_position_offset() const; + void set_rotation_offset(const Quaternion &p_offset); + Quaternion get_rotation_offset() const; + + void sync_pose(); + Transform3D get_transform_from_skeleton(const Transform3D &p_center) const; + + Vector3 collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const; +}; + +#endif // SPRING_BONE_COLLISION_3D_H diff --git a/scene/3d/spring_bone_collision_capsule_3d.cpp b/scene/3d/spring_bone_collision_capsule_3d.cpp new file mode 100644 index 0000000000..c4e4ee1eca --- /dev/null +++ b/scene/3d/spring_bone_collision_capsule_3d.cpp @@ -0,0 +1,114 @@ +/**************************************************************************/ +/* spring_bone_collision_capsule_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_capsule_3d.h" + +#include "scene/3d/spring_bone_collision_sphere_3d.h" + +void SpringBoneCollisionCapsule3D::set_radius(float p_radius) { + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionCapsule3D::get_radius() const { + return radius; +} + +void SpringBoneCollisionCapsule3D::set_height(float p_height) { + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionCapsule3D::get_height() const { + return height; +} + +void SpringBoneCollisionCapsule3D::set_inside(bool p_enabled) { + inside = p_enabled; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +bool SpringBoneCollisionCapsule3D::is_inside() const { + return inside; +} + +Pair SpringBoneCollisionCapsule3D::get_head_and_tail(const Transform3D &p_center) const { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_DOWN = Vector3(0, -1, 0); + Transform3D tr = get_transform_from_skeleton(p_center); + return Pair(tr.origin + tr.basis.xform(VECTOR3_UP * (height * 0.5 - radius)), tr.origin + tr.basis.xform(VECTOR3_DOWN * (height * 0.5 - radius))); +} + +void SpringBoneCollisionCapsule3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionCapsule3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionCapsule3D::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &SpringBoneCollisionCapsule3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &SpringBoneCollisionCapsule3D::get_height); + ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionCapsule3D::set_inside); + ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionCapsule3D::is_inside); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside"); +} + +Vector3 SpringBoneCollisionCapsule3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + Pair head_tail = get_head_and_tail(p_center); + Vector3 head = head_tail.first; + Vector3 tail = head_tail.second; + Vector3 p = tail - head; + Vector3 q = p_current - head; + float dot = p.dot(q); + if (dot <= 0) { + return SpringBoneCollisionSphere3D::_collide_sphere(head, radius, inside, p_bone_radius, p_bone_length, p_current); + } + float pls = p.length_squared(); + if (Math::is_zero_approx(pls)) { + return p_current; + } + if (pls <= dot) { + return SpringBoneCollisionSphere3D::_collide_sphere(head + p, radius, inside, p_bone_radius, p_bone_length, p_current); + } + return SpringBoneCollisionSphere3D::_collide_sphere(head + p * (dot / pls), radius, inside, p_bone_radius, p_bone_length, p_current); +} diff --git a/scene/3d/spring_bone_collision_capsule_3d.h b/scene/3d/spring_bone_collision_capsule_3d.h new file mode 100644 index 0000000000..9af2afa01f --- /dev/null +++ b/scene/3d/spring_bone_collision_capsule_3d.h @@ -0,0 +1,62 @@ +/**************************************************************************/ +/* spring_bone_collision_capsule_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_CAPSULE_3D_H +#define SPRING_BONE_COLLISION_CAPSULE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionCapsule3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionCapsule3D, SpringBoneCollision3D); + + float radius = 0.1; + float height = 0.5; + bool inside = false; + +protected: + static void _bind_methods(); + + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_height(float p_height); + float get_height() const; + void set_inside(bool p_enabled); + bool is_inside() const; + + // Helper. + Pair get_head_and_tail(const Transform3D &p_center) const; +}; + +#endif // SPRING_BONE_COLLISION_CAPSULE_3D_H diff --git a/scene/3d/spring_bone_collision_plane_3d.cpp b/scene/3d/spring_bone_collision_plane_3d.cpp new file mode 100644 index 0000000000..5fcd392eae --- /dev/null +++ b/scene/3d/spring_bone_collision_plane_3d.cpp @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* spring_bone_collision_plane_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_plane_3d.h" + +Vector3 SpringBoneCollisionPlane3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + Transform3D tr = get_transform_from_skeleton(p_center); + Vector3 pos = tr.origin; + Vector3 normal = tr.basis.get_rotation_quaternion().xform(VECTOR3_UP); + Vector3 to_vec = p_current - pos; + float distance = to_vec.dot(normal) - p_bone_radius; + if (distance > 0) { + return p_current; + } + return p_current + normal * -distance; +} diff --git a/scene/3d/spring_bone_collision_plane_3d.h b/scene/3d/spring_bone_collision_plane_3d.h new file mode 100644 index 0000000000..e5a012973b --- /dev/null +++ b/scene/3d/spring_bone_collision_plane_3d.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* spring_bone_collision_plane_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_PLANE_3D_H +#define SPRING_BONE_COLLISION_PLANE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionPlane3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionPlane3D, SpringBoneCollision3D); + +protected: + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; +}; + +#endif // SPRING_BONE_COLLISION_PLANE_3D_H diff --git a/scene/3d/spring_bone_collision_sphere_3d.cpp b/scene/3d/spring_bone_collision_sphere_3d.cpp new file mode 100644 index 0000000000..c3c8ec9b2d --- /dev/null +++ b/scene/3d/spring_bone_collision_sphere_3d.cpp @@ -0,0 +1,80 @@ +/**************************************************************************/ +/* spring_bone_collision_sphere_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_sphere_3d.h" + +void SpringBoneCollisionSphere3D::set_radius(float p_radius) { + radius = p_radius; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionSphere3D::get_radius() const { + return radius; +} + +void SpringBoneCollisionSphere3D::set_inside(bool p_enabled) { + inside = p_enabled; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +bool SpringBoneCollisionSphere3D::is_inside() const { + return inside; +} + +void SpringBoneCollisionSphere3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionSphere3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionSphere3D::get_radius); + ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionSphere3D::set_inside); + ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionSphere3D::is_inside); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside"); +} + +Vector3 SpringBoneCollisionSphere3D::_collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current) { + Vector3 diff = p_current - p_origin; + float length = diff.length(); + float r = p_inside ? p_radius - p_bone_radius : p_bone_radius + p_radius; + float distance = p_inside ? r - length : length - r; + if (distance > 0) { + return p_current; + } + return p_origin + diff.normalized() * r; +} + +Vector3 SpringBoneCollisionSphere3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return _collide_sphere(get_transform_from_skeleton(p_center).origin, radius, inside, p_bone_radius, p_bone_length, p_current); +} diff --git a/scene/3d/spring_bone_collision_sphere_3d.h b/scene/3d/spring_bone_collision_sphere_3d.h new file mode 100644 index 0000000000..5789099509 --- /dev/null +++ b/scene/3d/spring_bone_collision_sphere_3d.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* spring_bone_collision_sphere_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_SPHERE_3D_H +#define SPRING_BONE_COLLISION_SPHERE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionCapsule3D; + +class SpringBoneCollisionSphere3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionSphere3D, SpringBoneCollision3D); + + friend class SpringBoneCollisionCapsule3D; + + float radius = 0.1; + bool inside = false; + +protected: + static void _bind_methods(); + + static Vector3 _collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current); + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_inside(bool p_enabled); + bool is_inside() const; +}; + +#endif // SPRING_BONE_COLLISION_SPHERE_3D_H diff --git a/scene/3d/spring_bone_simulator_3d.cpp b/scene/3d/spring_bone_simulator_3d.cpp new file mode 100644 index 0000000000..5b23c789ba --- /dev/null +++ b/scene/3d/spring_bone_simulator_3d.cpp @@ -0,0 +1,1588 @@ +/**************************************************************************/ +/* spring_bone_simulator_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_simulator_3d.h" + +#include "scene/3d/spring_bone_collision_3d.h" + +// Original VRM Spring Bone movement logic was distributed by (c) VRM Consortium. Licensed under the MIT license. + +bool SpringBoneSimulator3D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("settings/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, settings.size(), false); + + if (what == "root_bone_name") { + set_root_bone_name(which, p_value); + } else if (what == "root_bone") { + set_root_bone(which, p_value); + } else if (what == "end_bone_name") { + set_end_bone_name(which, p_value); + } else if (what == "end_bone") { + String opt = path.get_slicec('/', 3); + if (opt.is_empty()) { + set_end_bone(which, p_value); + } else if (opt == "direction") { + set_end_bone_direction(which, static_cast((int)p_value)); + } else if (opt == "length") { + set_end_bone_length(which, p_value); + } else { + return false; + } + } else if (what == "extend_end_bone") { + set_extend_end_bone(which, p_value); + } else if (what == "center_from") { + set_center_from(which, static_cast((int)p_value)); + } else if (what == "center_node") { + set_center_node(which, p_value); + } else if (what == "center_bone") { + set_center_bone(which, p_value); + } else if (what == "center_bone_name") { + set_center_bone_name(which, p_value); + } else if (what == "individual_config") { + set_individual_config(which, p_value); + } else if (what == "rotation_axis") { + set_rotation_axis(which, static_cast((int)p_value)); + } else if (what == "radius") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_radius(which, p_value); + } else if (opt == "damping_curve") { + set_radius_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "stiffness") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_stiffness(which, p_value); + } else if (opt == "damping_curve") { + set_stiffness_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "drag") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_drag(which, p_value); + } else if (opt == "damping_curve") { + set_drag_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "gravity") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_gravity(which, p_value); + } else if (opt == "damping_curve") { + set_gravity_damping_curve(which, p_value); + } else if (opt == "direction") { + set_gravity_direction(which, p_value); + } else { + return false; + } + } else if (what == "enable_all_child_collisions") { + set_enable_all_child_collisions(which, p_value); + } else if (what == "joint_count") { + set_joint_count(which, p_value); + } else if (what == "joints") { + int idx = path.get_slicec('/', 3).to_int(); + String prop = path.get_slicec('/', 4); + if (prop == "bone_name") { + set_joint_bone_name(which, idx, p_value); + } else if (prop == "bone") { + set_joint_bone(which, idx, p_value); + } else if (prop == "rotation_axis") { + set_joint_rotation_axis(which, idx, static_cast((int)p_value)); + } else if (prop == "radius") { + set_joint_radius(which, idx, p_value); + } else if (prop == "stiffness") { + set_joint_stiffness(which, idx, p_value); + } else if (prop == "drag") { + set_joint_drag(which, idx, p_value); + } else if (prop == "gravity") { + set_joint_gravity(which, idx, p_value); + } else if (prop == "gravity_direction") { + set_joint_gravity_direction(which, idx, p_value); + } else { + return false; + } + } else if (what == "exclude_collision_count") { + set_exclude_collision_count(which, p_value); + } else if (what == "exclude_collisions") { + int idx = path.get_slicec('/', 3).to_int(); + set_exclude_collision_path(which, idx, p_value); + } else if (what == "collision_count") { + set_collision_count(which, p_value); + } else if (what == "collisions") { + int idx = path.get_slicec('/', 3).to_int(); + set_collision_path(which, idx, p_value); + } else { + return false; + } + } + return true; +} + +bool SpringBoneSimulator3D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("settings/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, settings.size(), false); + + if (what == "root_bone_name") { + r_ret = get_root_bone_name(which); + } else if (what == "root_bone") { + r_ret = get_root_bone(which); + } else if (what == "end_bone_name") { + r_ret = get_end_bone_name(which); + } else if (what == "end_bone") { + String opt = path.get_slicec('/', 3); + if (opt.is_empty()) { + r_ret = get_end_bone(which); + } else if (opt == "direction") { + r_ret = (int)get_end_bone_direction(which); + } else if (opt == "length") { + r_ret = get_end_bone_length(which); + } else { + return false; + } + } else if (what == "extend_end_bone") { + r_ret = is_end_bone_extended(which); + } else if (what == "center_from") { + r_ret = (int)get_center_from(which); + } else if (what == "center_node") { + r_ret = get_center_node(which); + } else if (what == "center_bone") { + r_ret = get_center_bone(which); + } else if (what == "center_bone_name") { + r_ret = get_center_bone_name(which); + } else if (what == "individual_config") { + r_ret = is_config_individual(which); + } else if (what == "rotation_axis") { + r_ret = (int)get_rotation_axis(which); + } else if (what == "radius") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_radius(which); + } else if (opt == "damping_curve") { + r_ret = get_radius_damping_curve(which); + } else { + return false; + } + } else if (what == "stiffness") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_stiffness(which); + } else if (opt == "damping_curve") { + r_ret = get_stiffness_damping_curve(which); + } else { + return false; + } + } else if (what == "drag") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_drag(which); + } else if (opt == "damping_curve") { + r_ret = get_drag_damping_curve(which); + } else { + return false; + } + } else if (what == "gravity") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_gravity(which); + } else if (opt == "damping_curve") { + r_ret = get_gravity_damping_curve(which); + } else if (opt == "direction") { + r_ret = get_gravity_direction(which); + } else { + return false; + } + } else if (what == "enable_all_child_collisions") { + r_ret = are_all_child_collisions_enabled(which); + } else if (what == "joint_count") { + r_ret = get_joint_count(which); + } else if (what == "joints") { + int idx = path.get_slicec('/', 3).to_int(); + String prop = path.get_slicec('/', 4); + if (prop == "bone_name") { + r_ret = get_joint_bone_name(which, idx); + } else if (prop == "bone") { + r_ret = get_joint_bone(which, idx); + } else if (prop == "rotation_axis") { + r_ret = (int)get_joint_rotation_axis(which, idx); + } else if (prop == "radius") { + r_ret = get_joint_radius(which, idx); + } else if (prop == "stiffness") { + r_ret = get_joint_stiffness(which, idx); + } else if (prop == "drag") { + r_ret = get_joint_drag(which, idx); + } else if (prop == "gravity") { + r_ret = get_joint_gravity(which, idx); + } else if (prop == "gravity_direction") { + r_ret = get_joint_gravity_direction(which, idx); + } else { + return false; + } + } else if (what == "exclude_collision_count") { + r_ret = get_exclude_collision_count(which); + } else if (what == "exclude_collisions") { + int idx = path.get_slicec('/', 3).to_int(); + r_ret = get_exclude_collision_path(which, idx); + } else if (what == "collision_count") { + r_ret = get_collision_count(which); + } else if (what == "collisions") { + int idx = path.get_slicec('/', 3).to_int(); + r_ret = get_collision_path(which, idx); + } else { + return false; + } + } + return true; +} + +void SpringBoneSimulator3D::_get_property_list(List *p_list) const { + String enum_hint; + Skeleton3D *skeleton = get_skeleton(); + if (skeleton) { + enum_hint = skeleton->get_concatenated_bone_names(); + } + + for (int i = 0; i < settings.size(); i++) { + String path = "settings/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, path + "root_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "root_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, path + "end_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "end_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, path + "extend_end_bone")); + p_list->push_back(PropertyInfo(Variant::INT, path + "end_bone/direction", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z,FromParent")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "end_bone/length", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::INT, path + "center_from", PROPERTY_HINT_ENUM, "WorldOrigin,Node,Bone")); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, path + "center_node")); + p_list->push_back(PropertyInfo(Variant::STRING, path + "center_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "center_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, path + "individual_config")); + p_list->push_back(PropertyInfo(Variant::INT, path + "rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z,All")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "radius/value", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "radius/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "stiffness/value", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "stiffness/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "drag/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "drag/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "gravity/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "gravity/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, path + "gravity/direction")); + p_list->push_back(PropertyInfo(Variant::INT, path + "joint_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Joints," + path + "joints/,static,const")); + for (int j = 0; j < settings[i]->joints.size(); j++) { + String joint_path = path + "joints/" + itos(j) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, joint_path + "bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint, PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, joint_path + "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + p_list->push_back(PropertyInfo(Variant::INT, joint_path + "rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z,All")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "stiffness", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "drag", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "gravity", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, joint_path + "gravity_direction")); + } + p_list->push_back(PropertyInfo(Variant::BOOL, path + "enable_all_child_collisions")); + p_list->push_back(PropertyInfo(Variant::INT, path + "exclude_collision_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Exclude Collisions," + path + "exclude_collisions/")); + for (int j = 0; j < settings[i]->exclude_collisions.size(); j++) { + String collision_path = path + "exclude_collisions/" + itos(j); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, collision_path, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SpringBoneCollision3D")); + } + p_list->push_back(PropertyInfo(Variant::INT, path + "collision_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Collisions," + path + "collisions/")); + for (int j = 0; j < settings[i]->collisions.size(); j++) { + String collision_path = path + "collisions/" + itos(j); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, collision_path, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SpringBoneCollision3D")); + } + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void SpringBoneSimulator3D::_validate_property(PropertyInfo &p_property) const { + PackedStringArray split = p_property.name.split("/"); + if (split.size() > 2 && split[0] == "settings") { + int which = split[1].to_int(); + + // Extended end bone option. + if (split[2] == "end_bone" && !is_end_bone_extended(which) && split.size() > 3) { + p_property.usage = PROPERTY_USAGE_NONE; + } + + // Center option. + if (get_center_from(which) != CENTER_FROM_BONE && (split[2] == "center_bone" || split[2] == "center_bone_name")) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (get_center_from(which) != CENTER_FROM_NODE && split[2] == "center_node") { + p_property.usage = PROPERTY_USAGE_NONE; + } + + // Joints option. + if (is_config_individual(which)) { + if (split[2] == "rotation_axis" || split[2] == "radius" || split[2] == "radius_damping_curve" || + split[2] == "stiffness" || split[2] == "stiffness_damping_curve" || + split[2] == "drag" || split[2] == "drag_damping_curve" || + split[2] == "gravity" || split[2] == "gravity_damping_curve" || split[2] == "gravity_direction") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } else { + if (split[2] == "joints" || split[2] == "joint_count") { + // Don't storage them since they are overridden by _update_joints(). + p_property.usage ^= PROPERTY_USAGE_STORAGE; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } + + // Collisions option. + if (are_all_child_collisions_enabled(which)) { + if (split[2] == "collisions" || split[2] == "collision_count") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } else { + if (split[2] == "exclude_collisions" || split[2] == "exclude_collision_count") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } + } +} + +void SpringBoneSimulator3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + set_notify_local_transform(true); // Used for updating gizmo in editor. + } +#endif // TOOLS_ENABLED + _make_collisions_dirty(); + _make_all_joints_dirty(); + } break; +#ifdef TOOLS_ENABLED + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + update_gizmos(); + } break; +#endif // TOOLS_ENABLED + } +} + +// Setting. + +void SpringBoneSimulator3D::set_root_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->root_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_root_bone(p_index, sk->find_bone(settings[p_index]->root_bone_name)); + } +} + +String SpringBoneSimulator3D::get_root_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->root_bone_name; +} + +void SpringBoneSimulator3D::set_root_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool changed = settings[p_index]->root_bone != p_bone; + settings[p_index]->root_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->root_bone <= -1 || settings[p_index]->root_bone >= sk->get_bone_count()) { + WARN_PRINT("Root bone index out of range!"); + settings[p_index]->root_bone = -1; + } else { + settings[p_index]->root_bone_name = sk->get_bone_name(settings[p_index]->root_bone); + } + } + if (changed) { + _update_joint_array(p_index); + } +} + +int SpringBoneSimulator3D::get_root_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->root_bone; +} + +void SpringBoneSimulator3D::set_end_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_end_bone(p_index, sk->find_bone(settings[p_index]->end_bone_name)); + } +} + +String SpringBoneSimulator3D::get_end_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->end_bone_name; +} + +void SpringBoneSimulator3D::set_end_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool changed = settings[p_index]->end_bone != p_bone; + settings[p_index]->end_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->end_bone <= -1 || settings[p_index]->end_bone >= sk->get_bone_count()) { + WARN_PRINT("End bone index out of range!"); + settings[p_index]->end_bone = -1; + } else { + settings[p_index]->end_bone_name = sk->get_bone_name(settings[p_index]->end_bone); + } + } + if (changed) { + _update_joint_array(p_index); + } +} + +int SpringBoneSimulator3D::get_end_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->end_bone; +} + +void SpringBoneSimulator3D::set_extend_end_bone(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->extend_end_bone = p_enabled; + _make_joints_dirty(p_index); + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::is_end_bone_extended(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->extend_end_bone; +} + +void SpringBoneSimulator3D::set_end_bone_direction(int p_index, BoneDirection p_bone_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_direction = p_bone_direction; + _make_joints_dirty(p_index); +} + +SpringBoneSimulator3D::BoneDirection SpringBoneSimulator3D::get_end_bone_direction(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), BONE_DIRECTION_FROM_PARENT); + return settings[p_index]->end_bone_direction; +} + +void SpringBoneSimulator3D::set_end_bone_length(int p_index, float p_length) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_length = p_length; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_end_bone_length(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->end_bone_length; +} + +Vector3 SpringBoneSimulator3D::get_end_bone_axis(int p_end_bone, BoneDirection p_direction) const { + Vector3 axis; + if (p_direction == BONE_DIRECTION_FROM_PARENT) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + axis = sk->get_bone_rest(p_end_bone).basis.xform_inv(sk->get_bone_rest(p_end_bone).origin); + axis.normalize(); + } + } else { + axis = get_vector_from_bone_axis(static_cast((int)p_direction)); + } + return axis; +} + +void SpringBoneSimulator3D::set_center_from(int p_index, CenterFrom p_center_from) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_from != p_center_from; + settings[p_index]->center_from = p_center_from; + if (center_changed) { + reset(); + } + notify_property_list_changed(); +} + +SpringBoneSimulator3D::CenterFrom SpringBoneSimulator3D::get_center_from(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), CENTER_FROM_WORLD_ORIGIN); + return settings[p_index]->center_from; +} + +void SpringBoneSimulator3D::set_center_node(int p_index, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_node != p_node_path; + settings[p_index]->center_node = p_node_path; + if (center_changed) { + reset(); + } +} + +NodePath SpringBoneSimulator3D::get_center_node(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + return settings[p_index]->center_node; +} + +void SpringBoneSimulator3D::set_center_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->center_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_center_bone(p_index, sk->find_bone(settings[p_index]->center_bone_name)); + } +} + +String SpringBoneSimulator3D::get_center_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->center_bone_name; +} + +void SpringBoneSimulator3D::set_center_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_bone != p_bone; + settings[p_index]->center_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->center_bone <= -1 || settings[p_index]->center_bone >= sk->get_bone_count()) { + WARN_PRINT("Center bone index out of range!"); + settings[p_index]->center_bone = -1; + } else { + settings[p_index]->center_bone_name = sk->get_bone_name(settings[p_index]->center_bone); + } + } + if (center_changed) { + reset(); + } +} + +int SpringBoneSimulator3D::get_center_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->center_bone; +} + +void SpringBoneSimulator3D::set_radius(int p_index, float p_radius) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->radius = p_radius; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_radius(int p_index) const { + return settings[p_index]->radius; +} + +void SpringBoneSimulator3D::set_radius_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->radius_damping_curve.is_valid()) { + settings[p_index]->radius_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->radius_damping_curve = p_damping_curve; + if (settings[p_index]->radius_damping_curve.is_valid()) { + settings[p_index]->radius_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_radius_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->radius_damping_curve; +} + +void SpringBoneSimulator3D::set_stiffness(int p_index, float p_stiffness) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->stiffness = p_stiffness; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_stiffness(int p_index) const { + return settings[p_index]->stiffness; +} + +void SpringBoneSimulator3D::set_stiffness_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->stiffness_damping_curve.is_valid()) { + settings[p_index]->stiffness_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->stiffness_damping_curve = p_damping_curve; + if (settings[p_index]->stiffness_damping_curve.is_valid()) { + settings[p_index]->stiffness_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_stiffness_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->stiffness_damping_curve; +} + +void SpringBoneSimulator3D::set_drag(int p_index, float p_drag) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->drag = p_drag; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_drag(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->drag; +} + +void SpringBoneSimulator3D::set_drag_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->drag_damping_curve.is_valid()) { + settings[p_index]->drag_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->drag_damping_curve = p_damping_curve; + if (settings[p_index]->drag_damping_curve.is_valid()) { + settings[p_index]->drag_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_drag_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->drag_damping_curve; +} + +void SpringBoneSimulator3D::set_gravity(int p_index, float p_gravity) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->gravity = p_gravity; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_gravity(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->gravity; +} + +void SpringBoneSimulator3D::set_gravity_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->gravity_damping_curve.is_valid()) { + settings[p_index]->gravity_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->gravity_damping_curve = p_damping_curve; + if (settings[p_index]->gravity_damping_curve.is_valid()) { + settings[p_index]->gravity_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_gravity_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->gravity_damping_curve; +} + +void SpringBoneSimulator3D::set_gravity_direction(int p_index, const Vector3 &p_gravity_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_gravity_direction.is_zero_approx()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->gravity_direction = p_gravity_direction; + _make_joints_dirty(p_index); +} + +Vector3 SpringBoneSimulator3D::get_gravity_direction(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3(0, -1, 0)); + return settings[p_index]->gravity_direction; +} + +void SpringBoneSimulator3D::set_rotation_axis(int p_index, RotationAxis p_axis) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->rotation_axis = p_axis; + _make_joints_dirty(p_index); +} + +SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_rotation_axis(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), ROTATION_AXIS_ALL); + return settings[p_index]->rotation_axis; +} + +void SpringBoneSimulator3D::set_setting_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + int delta = p_count - settings.size() + 1; + settings.resize(p_count); + if (delta > 1) { + for (int i = 1; i < delta; i++) { + settings.write[p_count - i] = memnew(SpringBone3DSetting); + } + } + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_setting_count() const { + return settings.size(); +} + +void SpringBoneSimulator3D::clear_settings() { + set_setting_count(0); +} + +// Individual joints. + +void SpringBoneSimulator3D::set_individual_config(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->individual_config = p_enabled; + _make_joints_dirty(p_index); + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::is_config_individual(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->individual_config; +} + +void SpringBoneSimulator3D::set_joint_bone_name(int p_index, int p_joint, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_joint_bone(p_index, p_joint, sk->find_bone(joints[p_joint]->bone_name)); + } +} + +String SpringBoneSimulator3D::get_joint_bone_name(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), String()); + return joints[p_joint]->bone_name; +} + +void SpringBoneSimulator3D::set_joint_bone(int p_index, int p_joint, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (joints[p_joint]->bone <= -1 || joints[p_joint]->bone >= sk->get_bone_count()) { + WARN_PRINT("Joint bone index out of range!"); + joints[p_joint]->bone = -1; + } else { + joints[p_joint]->bone_name = sk->get_bone_name(joints[p_joint]->bone); + } + } +} + +int SpringBoneSimulator3D::get_joint_bone(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), -1); + return joints[p_joint]->bone; +} + +void SpringBoneSimulator3D::set_joint_radius(int p_index, int p_joint, float p_radius) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->radius = p_radius; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneSimulator3D::get_joint_radius(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->radius; +} + +void SpringBoneSimulator3D::set_joint_stiffness(int p_index, int p_joint, float p_stiffness) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->stiffness = p_stiffness; +} + +float SpringBoneSimulator3D::get_joint_stiffness(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->stiffness; +} + +void SpringBoneSimulator3D::set_joint_drag(int p_index, int p_joint, float p_drag) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->drag = p_drag; +} + +float SpringBoneSimulator3D::get_joint_drag(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->drag; +} + +void SpringBoneSimulator3D::set_joint_gravity(int p_index, int p_joint, float p_gravity) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->gravity = p_gravity; +} + +float SpringBoneSimulator3D::get_joint_gravity(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->gravity; +} + +void SpringBoneSimulator3D::set_joint_gravity_direction(int p_index, int p_joint, const Vector3 &p_gravity_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_gravity_direction.is_zero_approx()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->gravity_direction = p_gravity_direction; +} + +Vector3 SpringBoneSimulator3D::get_joint_gravity_direction(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3(0, -1, 0)); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), Vector3(0, -1, 0)); + return joints[p_joint]->gravity_direction; +} + +void SpringBoneSimulator3D::set_joint_rotation_axis(int p_index, int p_joint, RotationAxis p_axis) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->rotation_axis = p_axis; +} + +SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_joint_rotation_axis(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), ROTATION_AXIS_ALL); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), ROTATION_AXIS_ALL); + return joints[p_joint]->rotation_axis; +} + +void SpringBoneSimulator3D::set_joint_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_count < 0); + Vector &joints = settings[p_index]->joints; + int delta = p_count - joints.size() + 1; + joints.resize(p_count); + if (delta > 1) { + for (int i = 1; i < delta; i++) { + joints.write[p_count - i] = memnew(SpringBone3DJointSetting); + } + } + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_joint_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + return joints.size(); +} + +// Individual collisions. + +void SpringBoneSimulator3D::set_enable_all_child_collisions(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->enable_all_child_collisions = p_enabled; + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::are_all_child_collisions_enabled(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->enable_all_child_collisions; +} + +void SpringBoneSimulator3D::set_exclude_collision_path(int p_index, int p_collision, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + Vector &setting_exclude_collisions = settings[p_index]->exclude_collisions; + ERR_FAIL_INDEX(p_collision, setting_exclude_collisions.size()); + setting_exclude_collisions.write[p_collision] = NodePath(); // Reset first. + if (is_inside_tree()) { + Node *node = get_node_or_null(p_node_path); + if (!node) { + _make_collisions_dirty(); + return; + } + node = node->get_parent(); + if (!node || node != this) { + _make_collisions_dirty(); + ERR_FAIL_EDMSG("Collision must be child of current SpringBoneSimulator3D."); + } + } + setting_exclude_collisions.write[p_collision] = p_node_path; + _make_collisions_dirty(); +} + +NodePath SpringBoneSimulator3D::get_exclude_collision_path(int p_index, int p_collision) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + Vector setting_exclude_collisions = settings[p_index]->exclude_collisions; + ERR_FAIL_INDEX_V(p_collision, setting_exclude_collisions.size(), NodePath()); + return setting_exclude_collisions[p_collision]; +} + +void SpringBoneSimulator3D::set_exclude_collision_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + Vector &setting_exclude_collisions = settings[p_index]->exclude_collisions; + setting_exclude_collisions.resize(p_count); + _make_collisions_dirty(); + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_exclude_collision_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector setting_exclude_collisions = settings[p_index]->exclude_collisions; + return setting_exclude_collisions.size(); +} + +void SpringBoneSimulator3D::clear_exclude_collisions(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + set_exclude_collision_count(p_index, 0); +} + +void SpringBoneSimulator3D::set_collision_path(int p_index, int p_collision, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + Vector &setting_collisions = settings[p_index]->collisions; + ERR_FAIL_INDEX(p_collision, setting_collisions.size()); + setting_collisions.write[p_collision] = NodePath(); // Reset first. + if (is_inside_tree()) { + Node *node = get_node_or_null(p_node_path); + if (!node) { + _make_collisions_dirty(); + return; + } + node = node->get_parent(); + if (!node || node != this) { + _make_collisions_dirty(); + ERR_FAIL_EDMSG("Collision must be child of current SpringBoneSimulator3D."); + } + } + setting_collisions.write[p_collision] = p_node_path; + _make_collisions_dirty(); +} + +NodePath SpringBoneSimulator3D::get_collision_path(int p_index, int p_collision) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + Vector setting_collisions = settings[p_index]->collisions; + ERR_FAIL_INDEX_V(p_collision, setting_collisions.size(), NodePath()); + return setting_collisions[p_collision]; +} + +void SpringBoneSimulator3D::set_collision_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + Vector &setting_collisions = settings[p_index]->collisions; + setting_collisions.resize(p_count); + _make_collisions_dirty(); + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_collision_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector setting_collisions = settings[p_index]->collisions; + return setting_collisions.size(); +} + +void SpringBoneSimulator3D::clear_collisions(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + set_collision_count(p_index, 0); +} + +LocalVector SpringBoneSimulator3D::get_valid_collision_instance_ids(int p_index) { + ERR_FAIL_INDEX_V(p_index, settings.size(), LocalVector()); + if (collisions_dirty) { + _find_collisions(); + } + return settings[p_index]->cached_collisions; +} + +void SpringBoneSimulator3D::_bind_methods() { + // Setting. + ClassDB::bind_method(D_METHOD("set_root_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_root_bone_name); + ClassDB::bind_method(D_METHOD("get_root_bone_name", "index"), &SpringBoneSimulator3D::get_root_bone_name); + ClassDB::bind_method(D_METHOD("set_root_bone", "index", "bone"), &SpringBoneSimulator3D::set_root_bone); + ClassDB::bind_method(D_METHOD("get_root_bone", "index"), &SpringBoneSimulator3D::get_root_bone); + + ClassDB::bind_method(D_METHOD("set_end_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_end_bone_name); + ClassDB::bind_method(D_METHOD("get_end_bone_name", "index"), &SpringBoneSimulator3D::get_end_bone_name); + ClassDB::bind_method(D_METHOD("set_end_bone", "index", "bone"), &SpringBoneSimulator3D::set_end_bone); + ClassDB::bind_method(D_METHOD("get_end_bone", "index"), &SpringBoneSimulator3D::get_end_bone); + + ClassDB::bind_method(D_METHOD("set_extend_end_bone", "index", "enabled"), &SpringBoneSimulator3D::set_extend_end_bone); + ClassDB::bind_method(D_METHOD("is_end_bone_extended", "index"), &SpringBoneSimulator3D::is_end_bone_extended); + ClassDB::bind_method(D_METHOD("set_end_bone_direction", "index", "bone_direction"), &SpringBoneSimulator3D::set_end_bone_direction); + ClassDB::bind_method(D_METHOD("get_end_bone_direction", "index"), &SpringBoneSimulator3D::get_end_bone_direction); + ClassDB::bind_method(D_METHOD("set_end_bone_length", "index", "length"), &SpringBoneSimulator3D::set_end_bone_length); + ClassDB::bind_method(D_METHOD("get_end_bone_length", "index"), &SpringBoneSimulator3D::get_end_bone_length); + + ClassDB::bind_method(D_METHOD("set_center_from", "index", "center_from"), &SpringBoneSimulator3D::set_center_from); + ClassDB::bind_method(D_METHOD("get_center_from", "index"), &SpringBoneSimulator3D::get_center_from); + ClassDB::bind_method(D_METHOD("set_center_node", "index", "node_path"), &SpringBoneSimulator3D::set_center_node); + ClassDB::bind_method(D_METHOD("get_center_node", "index"), &SpringBoneSimulator3D::get_center_node); + ClassDB::bind_method(D_METHOD("set_center_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_center_bone_name); + ClassDB::bind_method(D_METHOD("get_center_bone_name", "index"), &SpringBoneSimulator3D::get_center_bone_name); + ClassDB::bind_method(D_METHOD("set_center_bone", "index", "bone"), &SpringBoneSimulator3D::set_center_bone); + ClassDB::bind_method(D_METHOD("get_center_bone", "index"), &SpringBoneSimulator3D::get_center_bone); + + ClassDB::bind_method(D_METHOD("set_radius", "index", "radius"), &SpringBoneSimulator3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius", "index"), &SpringBoneSimulator3D::get_radius); + ClassDB::bind_method(D_METHOD("set_rotation_axis", "index", "axis"), &SpringBoneSimulator3D::set_rotation_axis); + ClassDB::bind_method(D_METHOD("get_rotation_axis", "index"), &SpringBoneSimulator3D::get_rotation_axis); + ClassDB::bind_method(D_METHOD("set_radius_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_radius_damping_curve); + ClassDB::bind_method(D_METHOD("get_radius_damping_curve", "index"), &SpringBoneSimulator3D::get_radius_damping_curve); + ClassDB::bind_method(D_METHOD("set_stiffness", "index", "stiffness"), &SpringBoneSimulator3D::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness", "index"), &SpringBoneSimulator3D::get_stiffness); + ClassDB::bind_method(D_METHOD("set_stiffness_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_stiffness_damping_curve); + ClassDB::bind_method(D_METHOD("get_stiffness_damping_curve", "index"), &SpringBoneSimulator3D::get_stiffness_damping_curve); + ClassDB::bind_method(D_METHOD("set_drag", "index", "drag"), &SpringBoneSimulator3D::set_drag); + ClassDB::bind_method(D_METHOD("get_drag", "index"), &SpringBoneSimulator3D::get_drag); + ClassDB::bind_method(D_METHOD("set_drag_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_drag_damping_curve); + ClassDB::bind_method(D_METHOD("get_drag_damping_curve", "index"), &SpringBoneSimulator3D::get_drag_damping_curve); + ClassDB::bind_method(D_METHOD("set_gravity", "index", "gravity"), &SpringBoneSimulator3D::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity", "index"), &SpringBoneSimulator3D::get_gravity); + ClassDB::bind_method(D_METHOD("set_gravity_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_gravity_damping_curve); + ClassDB::bind_method(D_METHOD("get_gravity_damping_curve", "index"), &SpringBoneSimulator3D::get_gravity_damping_curve); + ClassDB::bind_method(D_METHOD("set_gravity_direction", "index", "gravity_direction"), &SpringBoneSimulator3D::set_gravity_direction); + ClassDB::bind_method(D_METHOD("get_gravity_direction", "index"), &SpringBoneSimulator3D::get_gravity_direction); + + ClassDB::bind_method(D_METHOD("set_setting_count", "count"), &SpringBoneSimulator3D::set_setting_count); + ClassDB::bind_method(D_METHOD("get_setting_count"), &SpringBoneSimulator3D::get_setting_count); + ClassDB::bind_method(D_METHOD("clear_settings"), &SpringBoneSimulator3D::clear_settings); + + // Individual joints. + ClassDB::bind_method(D_METHOD("set_individual_config", "index", "enabled"), &SpringBoneSimulator3D::set_individual_config); + ClassDB::bind_method(D_METHOD("is_config_individual", "index"), &SpringBoneSimulator3D::is_config_individual); + + ClassDB::bind_method(D_METHOD("get_joint_bone_name", "index", "joint"), &SpringBoneSimulator3D::get_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_bone", "index", "joint"), &SpringBoneSimulator3D::get_joint_bone); + ClassDB::bind_method(D_METHOD("set_joint_rotation_axis", "index", "joint", "axis"), &SpringBoneSimulator3D::set_joint_rotation_axis); + ClassDB::bind_method(D_METHOD("get_joint_rotation_axis", "index", "joint"), &SpringBoneSimulator3D::get_joint_rotation_axis); + ClassDB::bind_method(D_METHOD("set_joint_radius", "index", "joint", "radius"), &SpringBoneSimulator3D::set_joint_radius); + ClassDB::bind_method(D_METHOD("get_joint_radius", "index", "joint"), &SpringBoneSimulator3D::get_joint_radius); + ClassDB::bind_method(D_METHOD("set_joint_stiffness", "index", "joint", "stiffness"), &SpringBoneSimulator3D::set_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_joint_stiffness", "index", "joint"), &SpringBoneSimulator3D::get_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_joint_drag", "index", "joint", "drag"), &SpringBoneSimulator3D::set_joint_drag); + ClassDB::bind_method(D_METHOD("get_joint_drag", "index", "joint"), &SpringBoneSimulator3D::get_joint_drag); + ClassDB::bind_method(D_METHOD("set_joint_gravity", "index", "joint", "gravity"), &SpringBoneSimulator3D::set_joint_gravity); + ClassDB::bind_method(D_METHOD("get_joint_gravity", "index", "joint"), &SpringBoneSimulator3D::get_joint_gravity); + ClassDB::bind_method(D_METHOD("set_joint_gravity_direction", "index", "joint", "gravity_direction"), &SpringBoneSimulator3D::set_joint_gravity_direction); + ClassDB::bind_method(D_METHOD("get_joint_gravity_direction", "index", "joint"), &SpringBoneSimulator3D::get_joint_gravity_direction); + + ClassDB::bind_method(D_METHOD("get_joint_count", "index"), &SpringBoneSimulator3D::get_joint_count); + + // Individual collisions. + ClassDB::bind_method(D_METHOD("set_enable_all_child_collisions", "index", "enabled"), &SpringBoneSimulator3D::set_enable_all_child_collisions); + ClassDB::bind_method(D_METHOD("are_all_child_collisions_enabled", "index"), &SpringBoneSimulator3D::are_all_child_collisions_enabled); + + ClassDB::bind_method(D_METHOD("set_exclude_collision_path", "index", "collision", "node_path"), &SpringBoneSimulator3D::set_exclude_collision_path); + ClassDB::bind_method(D_METHOD("get_exclude_collision_path", "index", "collision"), &SpringBoneSimulator3D::get_exclude_collision_path); + + ClassDB::bind_method(D_METHOD("set_exclude_collision_count", "index", "count"), &SpringBoneSimulator3D::set_exclude_collision_count); + ClassDB::bind_method(D_METHOD("get_exclude_collision_count", "index"), &SpringBoneSimulator3D::get_exclude_collision_count); + ClassDB::bind_method(D_METHOD("clear_exclude_collisions", "index"), &SpringBoneSimulator3D::clear_exclude_collisions); + + ClassDB::bind_method(D_METHOD("set_collision_path", "index", "collision", "node_path"), &SpringBoneSimulator3D::set_collision_path); + ClassDB::bind_method(D_METHOD("get_collision_path", "index", "collision"), &SpringBoneSimulator3D::get_collision_path); + + ClassDB::bind_method(D_METHOD("set_collision_count", "index", "count"), &SpringBoneSimulator3D::set_collision_count); + ClassDB::bind_method(D_METHOD("get_collision_count", "index"), &SpringBoneSimulator3D::get_collision_count); + ClassDB::bind_method(D_METHOD("clear_collisions", "index"), &SpringBoneSimulator3D::clear_collisions); + + // To process manually. + ClassDB::bind_method(D_METHOD("reset"), &SpringBoneSimulator3D::reset); + + ADD_ARRAY_COUNT("Settings", "setting_count", "set_setting_count", "get_setting_count", "settings/"); + + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_X); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_X); + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_Y); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_Y); + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_Z); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_Z); + BIND_ENUM_CONSTANT(BONE_DIRECTION_FROM_PARENT); + + BIND_ENUM_CONSTANT(CENTER_FROM_WORLD_ORIGIN); + BIND_ENUM_CONSTANT(CENTER_FROM_NODE); + BIND_ENUM_CONSTANT(CENTER_FROM_BONE); + + BIND_ENUM_CONSTANT(ROTATION_AXIS_X); + BIND_ENUM_CONSTANT(ROTATION_AXIS_Y); + BIND_ENUM_CONSTANT(ROTATION_AXIS_Z); + BIND_ENUM_CONSTANT(ROTATION_AXIS_ALL); +} + +void SpringBoneSimulator3D::_make_joints_dirty(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->joints_dirty = true; + if (joints_dirty) { + return; + } + joints_dirty = true; + callable_mp(this, &SpringBoneSimulator3D::_update_joints).call_deferred(); +} + +void SpringBoneSimulator3D::_make_all_joints_dirty() { + for (int i = 0; i < settings.size(); i++) { + _update_joint_array(i); + } +} + +void SpringBoneSimulator3D::add_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::move_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::remove_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::_find_collisions() { + if (!collisions_dirty) { + return; + } + collisions.clear(); + for (int i = 0; i < get_child_count(); i++) { + SpringBoneCollision3D *c = Object::cast_to(get_child(i)); + if (c) { + collisions.push_back(c->get_instance_id()); + } + } + + bool setting_updated = false; + + for (int i = 0; i < settings.size(); i++) { + LocalVector &cache = settings[i]->cached_collisions; + cache.clear(); + if (!settings[i]->enable_all_child_collisions) { + // Allow list. + Vector &setting_collisions = settings[i]->collisions; + for (int j = 0; j < setting_collisions.size(); j++) { + Node *n = get_node_or_null(setting_collisions[j]); + if (!n) { + continue; + } + ObjectID id = n->get_instance_id(); + if (!collisions.has(id)) { + setting_collisions.write[j] = NodePath(); // Clear path if not found. + } else { + cache.push_back(id); + } + } + } else { + // Deny list. + LocalVector masks; + Vector &setting_exclude_collisions = settings[i]->exclude_collisions; + for (int j = 0; j < setting_exclude_collisions.size(); j++) { + Node *n = get_node_or_null(setting_exclude_collisions[j]); + if (!n) { + continue; + } + ObjectID id = n->get_instance_id(); + int find = collisions.find(id); + if (find < 0) { + setting_exclude_collisions.write[j] = NodePath(); // Clear path if not found. + } else { + masks.push_back((uint32_t)find); + } + } + uint32_t mask_index = 0; + for (uint32_t j = 0; j < collisions.size(); j++) { + if (mask_index < masks.size() && j == masks[mask_index]) { + mask_index++; + continue; + } + cache.push_back(collisions[j]); + } + } + } + + collisions_dirty = false; + + if (setting_updated) { + notify_property_list_changed(); + } +} + +void SpringBoneSimulator3D::_process_collisions() { + for (const ObjectID &oid : collisions) { + Object *t_obj = ObjectDB::get_instance(oid); + if (!t_obj) { + continue; + } + SpringBoneCollision3D *col = Object::cast_to(t_obj); + if (!col) { + continue; + } + col->sync_pose(); + } +} + +void SpringBoneSimulator3D::_make_collisions_dirty() { + collisions_dirty = true; +} + +void SpringBoneSimulator3D::_update_joint_array(int p_index) { + _make_joints_dirty(p_index); + set_joint_count(p_index, 0); + + Skeleton3D *sk = get_skeleton(); + int current_bone = settings[p_index]->end_bone; + int root_bone = settings[p_index]->root_bone; + if (!sk || current_bone < 0 || root_bone < 0) { + return; + } + + // Validation. + bool valid = false; + while (current_bone >= 0) { + if (current_bone == root_bone) { + valid = true; + break; + } + current_bone = sk->get_bone_parent(current_bone); + } + ERR_FAIL_COND_EDMSG(!valid, "End bone must be the same as or a child of root bone."); + + Vector new_joints; + current_bone = settings[p_index]->end_bone; + while (current_bone != root_bone) { + new_joints.push_back(current_bone); + current_bone = sk->get_bone_parent(current_bone); + } + new_joints.push_back(current_bone); + new_joints.reverse(); + + set_joint_count(p_index, new_joints.size()); + for (int i = 0; i < new_joints.size(); i++) { + set_joint_bone(p_index, i, new_joints[i]); + } +} + +void SpringBoneSimulator3D::_update_joints() { + if (!joints_dirty) { + return; + } + for (int i = 0; i < settings.size(); i++) { + if (!settings[i]->joints_dirty) { + continue; + } + if (settings[i]->individual_config) { + settings[i]->joints_dirty = false; + continue; // Abort. + } + Vector &joints = settings[i]->joints; + float unit = joints.size() > 0 ? (1.0 / float(joints.size() - 1)) : 0.0; + for (int j = 0; j < joints.size(); j++) { + float offset = j * unit; + + if (settings[i]->radius_damping_curve.is_valid()) { + joints[j]->radius = settings[i]->radius * settings[i]->radius_damping_curve->sample_baked(offset); + } else { + joints[j]->radius = settings[i]->radius; + } + + if (settings[i]->stiffness_damping_curve.is_valid()) { + joints[j]->stiffness = settings[i]->stiffness * settings[i]->stiffness_damping_curve->sample_baked(offset); + } else { + joints[j]->stiffness = settings[i]->stiffness; + } + + if (settings[i]->drag_damping_curve.is_valid()) { + joints[j]->drag = settings[i]->drag * settings[i]->drag_damping_curve->sample_baked(offset); + } else { + joints[j]->drag = settings[i]->drag; + } + + if (settings[i]->gravity_damping_curve.is_valid()) { + joints[j]->gravity = settings[i]->gravity * settings[i]->gravity_damping_curve->sample_baked(offset); + } else { + joints[j]->gravity = settings[i]->gravity; + } + + joints[j]->gravity_direction = settings[i]->gravity_direction; + joints[j]->rotation_axis = settings[i]->rotation_axis; + } + settings[i]->simulation_dirty = true; + settings[i]->joints_dirty = false; + } + joints_dirty = false; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +void SpringBoneSimulator3D::_set_active(bool p_active) { + if (p_active) { + reset(); + } +} + +void SpringBoneSimulator3D::_process_modification() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + _find_collisions(); + _process_collisions(); + double delta = skeleton->get_modifier_callback_mode_process() == Skeleton3D::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? skeleton->get_process_delta_time() : skeleton->get_physics_process_delta_time(); + for (int i = 0; i < settings.size(); i++) { + _init_joints(skeleton, settings[i]); + _process_joints(delta, skeleton, settings[i]->joints, get_valid_collision_instance_ids(i), settings[i]->cached_center, settings[i]->cached_inverted_center, settings[i]->cached_inverted_center.basis.get_rotation_quaternion()); + } +} + +void SpringBoneSimulator3D::reset() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + _find_collisions(); + _process_collisions(); + for (int i = 0; i < settings.size(); i++) { + settings[i]->simulation_dirty = true; + _init_joints(skeleton, settings[i]); + } +} + +void SpringBoneSimulator3D::_init_joints(Skeleton3D *p_skeleton, SpringBone3DSetting *setting) { + if (setting->center_from == CENTER_FROM_WORLD_ORIGIN) { + setting->cached_center = p_skeleton->get_global_transform(); + } else if (setting->center_from == CENTER_FROM_NODE) { + if (setting->center_node == NodePath()) { + setting->cached_center = Transform3D(); + } else { + Node3D *nd = Object::cast_to(get_node_or_null(setting->center_node)); + if (!nd) { + setting->cached_center = Transform3D(); + } else { + setting->cached_center = nd->get_global_transform().affine_inverse() * p_skeleton->get_global_transform(); + } + } + } else { + if (setting->center_bone >= 0) { + setting->cached_center = p_skeleton->get_bone_global_pose(setting->center_bone); + } else { + setting->cached_center = Transform3D(); + } + } + setting->cached_inverted_center = setting->cached_center.affine_inverse(); + + if (!setting->simulation_dirty) { + return; + } + for (int i = 0; i < setting->joints.size(); i++) { + if (setting->joints[i]->verlet) { + memdelete(setting->joints[i]->verlet); + setting->joints[i]->verlet = nullptr; + } + if (i < setting->joints.size() - 1) { + setting->joints[i]->verlet = memnew(SpringBone3DVerletInfo); + Vector3 axis = p_skeleton->get_bone_rest(setting->joints[i + 1]->bone).origin; + setting->joints[i]->verlet->current_tail = setting->cached_center.xform(p_skeleton->get_bone_global_pose(setting->joints[i]->bone).xform(axis)); + setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; + setting->joints[i]->verlet->forward_vector = axis.normalized(); + setting->joints[i]->verlet->length = axis.length(); + } else if (setting->extend_end_bone && setting->end_bone_length > 0) { + Vector3 axis = get_end_bone_axis(setting->end_bone, setting->end_bone_direction); + if (axis.is_zero_approx()) { + continue; + } + setting->joints[i]->verlet = memnew(SpringBone3DVerletInfo); + setting->joints[i]->verlet->forward_vector = axis; + setting->joints[i]->verlet->length = setting->end_bone_length; + setting->joints[i]->verlet->current_tail = setting->cached_center.xform(p_skeleton->get_bone_global_pose(setting->joints[i]->bone).xform(axis * setting->end_bone_length)); + setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; + } + } + setting->simulation_dirty = false; +} + +Vector3 SpringBoneSimulator3D::snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position) { + if (p_axis == ROTATION_AXIS_ALL) { + return p_position; + } + Vector3 result = p_position; + result = p_rest.affine_inverse().xform(result); + result[(int)p_axis] = 0; + result = p_rest.xform(result); + return result; +} + +Vector3 SpringBoneSimulator3D::limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length) { + return p_origin + (p_destination - p_origin).normalized() * p_length; +} + +void SpringBoneSimulator3D::_process_joints(double p_delta, Skeleton3D *p_skeleton, Vector &p_joints, const LocalVector &p_collisions, const Transform3D &p_center_transform, const Transform3D &p_inverted_center_transform, const Quaternion &p_inverted_center_rotation) { + for (int i = 0; i < p_joints.size(); i++) { + SpringBone3DVerletInfo *verlet = p_joints[i]->verlet; + if (!verlet) { + continue; // Means not extended end bone. + } + Transform3D current_global_pose = p_skeleton->get_bone_global_pose(p_joints[i]->bone); + Transform3D current_world_pose = p_center_transform * current_global_pose; + Quaternion current_rot = current_global_pose.basis.get_rotation_quaternion(); + Vector3 current_origin = p_center_transform.xform(current_global_pose.origin); + Vector3 external = p_inverted_center_rotation.xform(p_joints[i]->gravity_direction * p_joints[i]->gravity * p_delta); + + // Integration of velocity by verlet. + Vector3 next_tail = verlet->current_tail + + (verlet->current_tail - verlet->prev_tail) * (1.0 - p_joints[i]->drag) + + p_center_transform.basis.get_rotation_quaternion().xform(current_rot.xform(verlet->forward_vector * (p_joints[i]->stiffness * p_delta)) + external); + // Snap to plane if axis locked. + next_tail = snap_position_to_plane(current_world_pose, p_joints[i]->rotation_axis, next_tail); + // Limit bone length. + next_tail = limit_length(current_origin, next_tail, verlet->length); + + // Collision movement. + for (uint32_t j = 0; j < p_collisions.size(); j++) { + Object *obj = ObjectDB::get_instance(p_collisions[j]); + if (!obj) { + continue; + } + SpringBoneCollision3D *col = Object::cast_to(obj); + if (col) { + // Collider movement should separate from the effect of the center. + next_tail = col->collide(p_center_transform, p_joints[i]->radius, verlet->length, next_tail); + // Snap to plane if axis locked. + next_tail = snap_position_to_plane(current_world_pose, p_joints[i]->rotation_axis, next_tail); + // Limit bone length. + next_tail = limit_length(current_origin, next_tail, verlet->length); + } + } + + // Store current tails for next process. + verlet->prev_tail = verlet->current_tail; + verlet->current_tail = next_tail; + + // Apply rotation. + Vector3 from = current_rot.xform(verlet->forward_vector); + Vector3 to = p_inverted_center_transform.basis.xform(next_tail - current_origin).normalized(); + Quaternion from_to = get_from_to_rotation(from, to); + from_to *= current_rot; + from_to = get_local_pose_rotation(p_skeleton, p_joints[i]->bone, from_to); + p_skeleton->set_bone_pose_rotation(p_joints[i]->bone, from_to); + } +} + +Quaternion SpringBoneSimulator3D::get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation) { + int parent = p_skeleton->get_bone_parent(p_bone); + if (parent < 0) { + return p_global_pose_rotation; + } + return p_skeleton->get_bone_global_pose(parent).basis.orthonormalized().inverse() * p_global_pose_rotation; +} + +Quaternion SpringBoneSimulator3D::get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to) { + Vector3 axis = p_from.cross(p_to); + if (axis.is_zero_approx()) { + return Quaternion(0, 0, 0, 1); + } + float angle = p_from.angle_to(p_to); + if (Math::is_zero_approx(angle)) { + angle = 0.0; + } + return Quaternion(axis.normalized(), angle); +} diff --git a/scene/3d/spring_bone_simulator_3d.h b/scene/3d/spring_bone_simulator_3d.h new file mode 100644 index 0000000000..092b229542 --- /dev/null +++ b/scene/3d/spring_bone_simulator_3d.h @@ -0,0 +1,281 @@ +/**************************************************************************/ +/* spring_bone_simulator_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_SIMULATOR_3D_H +#define SPRING_BONE_SIMULATOR_3D_H + +#include "scene/3d/skeleton_modifier_3d.h" + +class SpringBoneSimulator3D : public SkeletonModifier3D { + GDCLASS(SpringBoneSimulator3D, SkeletonModifier3D); + + bool joints_dirty = false; + + LocalVector collisions; // To process collisions for sync position with skeleton. + bool collisions_dirty = false; + void _find_collisions(); + void _process_collisions(); + void _make_collisions_dirty(); + +public: + enum BoneDirection { + BONE_DIRECTION_PLUS_X, + BONE_DIRECTION_MINUS_X, + BONE_DIRECTION_PLUS_Y, + BONE_DIRECTION_MINUS_Y, + BONE_DIRECTION_PLUS_Z, + BONE_DIRECTION_MINUS_Z, + BONE_DIRECTION_FROM_PARENT, + }; + + enum CenterFrom { + CENTER_FROM_WORLD_ORIGIN, + CENTER_FROM_NODE, + CENTER_FROM_BONE, + }; + + enum RotationAxis { + ROTATION_AXIS_X, + ROTATION_AXIS_Y, + ROTATION_AXIS_Z, + ROTATION_AXIS_ALL, + }; + + struct SpringBone3DVerletInfo { + Vector3 prev_tail; + Vector3 current_tail; + Vector3 forward_vector; + float length = 0.0; + }; + + struct SpringBone3DJointSetting { + String bone_name; + int bone = -1; + + RotationAxis rotation_axis = ROTATION_AXIS_ALL; + float radius = 0.1; + float stiffness = 1.0; + float drag = 0.0; + float gravity = 0.0; + Vector3 gravity_direction = Vector3(0, -1, 0); + + // To process. + SpringBone3DVerletInfo *verlet = nullptr; + }; + + struct SpringBone3DSetting { + bool joints_dirty = false; + + String root_bone_name; + int root_bone = -1; + + String end_bone_name; + int end_bone = -1; + + // To make virtual end joint. + bool extend_end_bone = false; + BoneDirection end_bone_direction = BONE_DIRECTION_FROM_PARENT; + float end_bone_length = 0.0; + float end_bone_tip_radius = 0.02; + + CenterFrom center_from = CENTER_FROM_WORLD_ORIGIN; + NodePath center_node; + String center_bone_name; + int center_bone = -1; + + // Cache into joints. + bool individual_config = false; + float radius = 0.02; + Ref radius_damping_curve; + float stiffness = 1.0; + Ref stiffness_damping_curve; + float drag = 0.4; + Ref drag_damping_curve; + float gravity = 0.0; + Ref gravity_damping_curve; + Vector3 gravity_direction = Vector3(0, -1, 0); + RotationAxis rotation_axis = ROTATION_AXIS_ALL; + Vector joints; + + // Cache into collisions. + bool enable_all_child_collisions = true; + Vector collisions; + Vector exclude_collisions; + LocalVector cached_collisions; + + // To process. + bool simulation_dirty = false; + Transform3D cached_center; + Transform3D cached_inverted_center; + }; + +protected: + Vector settings; + + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + void _validate_property(PropertyInfo &p_property) const; + + void _notification(int p_what); + + static void _bind_methods(); + + virtual void _set_active(bool p_active) override; + virtual void _process_modification() override; + void _init_joints(Skeleton3D *p_skeleton, SpringBone3DSetting *p_setting); + void _process_joints(double p_delta, Skeleton3D *p_skeleton, Vector &p_joints, const LocalVector &p_collisions, const Transform3D &p_center_transform, const Transform3D &p_inverted_center_transform, const Quaternion &p_inverted_center_rotation); + + void _make_joints_dirty(int p_index); + void _make_all_joints_dirty(); + + void _update_joint_array(int p_index); + void _update_joints(); + + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + +public: + // Setting. + void set_root_bone_name(int p_index, const String &p_bone_name); + String get_root_bone_name(int p_index) const; + void set_root_bone(int p_index, int p_bone); + int get_root_bone(int p_index) const; + + void set_end_bone_name(int p_index, const String &p_bone_name); + String get_end_bone_name(int p_index) const; + void set_end_bone(int p_index, int p_bone); + int get_end_bone(int p_index) const; + + void set_extend_end_bone(int p_index, bool p_enabled); + bool is_end_bone_extended(int p_index) const; + void set_end_bone_direction(int p_index, BoneDirection p_bone_direction); + BoneDirection get_end_bone_direction(int p_index) const; + void set_end_bone_length(int p_index, float p_length); + float get_end_bone_length(int p_index) const; + Vector3 get_end_bone_axis(int p_end_bone, BoneDirection p_direction) const; // Helper. + + void set_center_from(int p_index, CenterFrom p_center_from); + CenterFrom get_center_from(int p_index) const; + void set_center_node(int p_index, const NodePath &p_node_path); + NodePath get_center_node(int p_index) const; + void set_center_bone_name(int p_index, const String &p_bone_name); + String get_center_bone_name(int p_index) const; + void set_center_bone(int p_index, int p_bone); + int get_center_bone(int p_index) const; + + void set_rotation_axis(int p_index, RotationAxis p_axis); + RotationAxis get_rotation_axis(int p_index) const; + void set_radius(int p_index, float p_radius); + float get_radius(int p_index) const; + void set_radius_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_radius_damping_curve(int p_index) const; + void set_stiffness(int p_index, float p_stiffness); + float get_stiffness(int p_index) const; + void set_stiffness_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_stiffness_damping_curve(int p_index) const; + void set_drag(int p_index, float p_drag); + float get_drag(int p_index) const; + void set_drag_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_drag_damping_curve(int p_index) const; + void set_gravity(int p_index, float p_gravity); + float get_gravity(int p_index) const; + void set_gravity_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_gravity_damping_curve(int p_index) const; + void set_gravity_direction(int p_index, const Vector3 &p_gravity_direction); + Vector3 get_gravity_direction(int p_index) const; + + void set_setting_count(int p_count); + int get_setting_count() const; + void clear_settings(); + + // Individual joints. + void set_individual_config(int p_index, bool p_enabled); + bool is_config_individual(int p_index) const; + + void set_joint_bone_name(int p_index, int p_joint, const String &p_bone_name); + String get_joint_bone_name(int p_index, int p_joint) const; + void set_joint_bone(int p_index, int p_joint, int p_bone); + int get_joint_bone(int p_index, int p_joint) const; + + void set_joint_rotation_axis(int p_index, int p_joint, RotationAxis p_axis); + RotationAxis get_joint_rotation_axis(int p_index, int p_joint) const; + void set_joint_radius(int p_index, int p_joint, float p_radius); + float get_joint_radius(int p_index, int p_joint) const; + void set_joint_stiffness(int p_index, int p_joint, float p_stiffness); + float get_joint_stiffness(int p_index, int p_joint) const; + void set_joint_drag(int p_index, int p_joint, float p_drag); + float get_joint_drag(int p_index, int p_joint) const; + void set_joint_gravity(int p_index, int p_joint, float p_gravity); + float get_joint_gravity(int p_index, int p_joint) const; + void set_joint_gravity_direction(int p_index, int p_joint, const Vector3 &p_gravity_direction); + Vector3 get_joint_gravity_direction(int p_index, int p_joint) const; + + void set_joint_count(int p_index, int p_count); + int get_joint_count(int p_index) const; + + // Individual collisions. + void set_enable_all_child_collisions(int p_index, bool p_enabled); + bool are_all_child_collisions_enabled(int p_index) const; + + void set_exclude_collision_path(int p_index, int p_collision, const NodePath &p_node_path); + NodePath get_exclude_collision_path(int p_index, int p_collision) const; + + void set_exclude_collision_count(int p_index, int p_count); + int get_exclude_collision_count(int p_index) const; + void clear_exclude_collisions(int p_index); + + void set_collision_path(int p_index, int p_collision, const NodePath &p_node_path); + NodePath get_collision_path(int p_index, int p_collision) const; + + void set_collision_count(int p_index, int p_count); + int get_collision_count(int p_index) const; + void clear_collisions(int p_index); + + LocalVector get_valid_collision_instance_ids(int p_index); + + // Helper. + static Quaternion get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation); + static Quaternion get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to); + static Vector3 snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position); + static Vector3 limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length); + + // To process manually. + void reset(); +}; + +VARIANT_ENUM_CAST(SpringBoneSimulator3D::BoneDirection); +VARIANT_ENUM_CAST(SpringBoneSimulator3D::CenterFrom); +VARIANT_ENUM_CAST(SpringBoneSimulator3D::RotationAxis); + +#endif // SPRING_BONE_SIMULATOR_3D_H diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 7ec6da0402..000489a558 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -161,7 +161,6 @@ void ColorPicker::_notification(int p_what) { } DisplayServer *ds = DisplayServer::get_singleton(); Vector2 ofs = ds->mouse_get_position(); - picker_window->set_position(ofs - Vector2(28, 28)); Color c = DisplayServer::get_singleton()->screen_get_pixel(ofs); @@ -173,8 +172,13 @@ void ColorPicker::_notification(int p_what) { picker_window->set_position(ofs - Vector2(28, 28)); picker_texture_zoom->set_texture(ImageTexture::create_from_image(zoom_preview_img)); } else { - Size2i screen_size = ds->screen_get_size(); - picker_window->set_position(ofs + Vector2(ofs.x < screen_size.width / 2 ? 8 : -36, ofs.y < screen_size.height / 2 ? 8 : -36)); + Size2i screen_size = ds->screen_get_size(DisplayServer::SCREEN_WITH_MOUSE_FOCUS); + Vector2i screen_position = ds->screen_get_position(DisplayServer::SCREEN_WITH_MOUSE_FOCUS); + + float ofs_decal_x = (ofs.x < screen_position.x + screen_size.width - 51) ? 8 : -36; + float ofs_decal_y = (ofs.y < screen_position.y + screen_size.height - 51) ? 8 : -36; + + picker_window->set_position(ofs + Vector2(ofs_decal_x, ofs_decal_y)); } set_pick_color(c); @@ -1889,35 +1893,23 @@ void ColorPicker::_pick_button_pressed_legacy() { picker_window->add_child(picker_texture_rect); picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input)); - bool has_feature_exclude_from_capture = DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE); picker_preview = memnew(Panel); picker_preview->set_mouse_filter(MOUSE_FILTER_IGNORE); - if (!has_feature_exclude_from_capture) { - picker_preview->set_size(Vector2i(28, 28)); - } else { - picker_preview->set_size(Vector2i(55, 72)); - } + picker_preview->set_size(Vector2i(55, 72)); picker_window->add_child(picker_preview); picker_preview_color = memnew(Panel); picker_preview_color->set_mouse_filter(MOUSE_FILTER_IGNORE); - if (!has_feature_exclude_from_capture) { - picker_preview_color->set_size(Vector2i(24, 24)); - picker_preview_color->set_position(Vector2i(2, 2)); - } else { - picker_preview_color->set_size(Vector2i(51, 15)); - picker_preview_color->set_position(Vector2i(2, 55)); - } + picker_preview_color->set_size(Vector2i(51, 15)); + picker_preview_color->set_position(Vector2i(2, 55)); picker_preview->add_child(picker_preview_color); - if (has_feature_exclude_from_capture) { - picker_texture_zoom = memnew(TextureRect); - picker_texture_zoom->set_mouse_filter(MOUSE_FILTER_IGNORE); - picker_texture_zoom->set_custom_minimum_size(Vector2i(51, 51)); - picker_texture_zoom->set_position(Vector2i(2, 2)); - picker_texture_zoom->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); - picker_preview->add_child(picker_texture_zoom); - } + picker_texture_zoom = memnew(TextureRect); + picker_texture_zoom->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_texture_zoom->set_custom_minimum_size(Vector2i(51, 51)); + picker_texture_zoom->set_position(Vector2i(2, 2)); + picker_texture_zoom->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + picker_preview->add_child(picker_texture_zoom); picker_preview_style_box.instantiate(); picker_preview->add_theme_style_override(SceneStringName(panel), picker_preview_style_box); @@ -1933,9 +1925,16 @@ void ColorPicker::_pick_button_pressed_legacy() { picker_window->set_position(Point2i()); picker_texture_rect->set_texture(tx); + Vector2 ofs = picker_window->get_mouse_position(); + picker_preview->set_position(ofs - Vector2(28, 28)); + + Vector2 scale = screen_rect.size / tx->get_image()->get_size(); + ofs /= scale; + Ref atlas; atlas.instantiate(); atlas->set_atlas(tx); + atlas->set_region(Rect2i(ofs.x - 8, ofs.y - 8, 17, 17)); picker_texture_zoom->set_texture(atlas); } else { screen_rect = picker_window->get_parent_rect(); @@ -1961,7 +1960,17 @@ void ColorPicker::_pick_button_pressed_legacy() { target_image->blit_rect(img, Rect2i(Point2i(0, 0), img->get_size()), w->get_position()); } - picker_texture_rect->set_texture(ImageTexture::create_from_image(target_image)); + Ref tx = ImageTexture::create_from_image(target_image); + picker_texture_rect->set_texture(tx); + + Vector2 ofs = screen_rect.position - DisplayServer::get_singleton()->mouse_get_position(); + picker_preview->set_position(ofs - Vector2(28, 28)); + + Ref atlas; + atlas.instantiate(); + atlas->set_atlas(tx); + atlas->set_region(Rect2i(ofs.x - 8, ofs.y - 8, 17, 17)); + picker_texture_zoom->set_texture(atlas); } picker_window->set_size(screen_rect.size); diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc index b326f3cc60..2909cd3c4a 100644 --- a/scene/gui/rich_text_label.compat.inc +++ b/scene/gui/rich_text_label.compat.inc @@ -32,6 +32,18 @@ #ifndef DISABLE_DEPRECATED +void RichTextLabel::_push_font_bind_compat_79053(const Ref &p_font, int p_size) { + push_font(p_font, p_size); +} + +void RichTextLabel::_set_table_column_expand_bind_compat_79053(int p_column, bool p_expand, int p_ratio) { + set_table_column_expand(p_column, p_expand, p_ratio, true); +} + +void RichTextLabel::_set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio) { + set_table_column_expand(p_column, p_expand, p_ratio, true); +} + void RichTextLabel::_push_meta_bind_compat_99481(const Variant &p_meta, MetaUnderline p_underline_mode) { push_meta(p_meta, p_underline_mode, String()); } @@ -49,6 +61,9 @@ bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) { } void RichTextLabel::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::_push_font_bind_compat_79053); + ClassDB::bind_compatibility_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::_set_table_column_expand_bind_compat_79053); + ClassDB::bind_compatibility_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::_set_table_column_expand_bind_compat_101482, DEFVAL(1)); ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data", "underline_mode"), &RichTextLabel::_push_meta_bind_compat_99481, DEFVAL(META_UNDERLINE_ALWAYS)); ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024); ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2())); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 30e144a45c..09caec9aea 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -695,7 +695,7 @@ void RichTextLabel::_set_table_size(ItemTable *p_table, int p_available_width) { table_need_fit = false; // Fit slim. for (int i = 0; i < col_count; i++) { - if (!p_table->columns[i].expand) { + if (!p_table->columns[i].expand || !p_table->columns[i].shrink) { continue; } int dif = p_table->columns[i].width - p_table->columns[i].max_width; @@ -1186,8 +1186,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o //Apply fx. if (fade) { float faded_visibility = 1.0f; - if (glyphs[i].start >= fade->starting_index) { - faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length; + if (l.char_offset + glyphs[i].start >= fade->char_ofs + fade->starting_index) { + faded_visibility -= (float)((l.char_offset + glyphs[i].start) - (fade->char_ofs + fade->starting_index)) / (float)fade->length; faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; } font_color.a = faded_visibility; @@ -1224,7 +1224,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o custom_fx_ok = effect_status; char_xform = charfx->transform; - fx_offset += charfx->offset; + fx_offset = charfx->offset; font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; @@ -2058,6 +2058,7 @@ void RichTextLabel::gui_input(const Ref &p_event) { int c_index = 0; bool outside; + selection.double_click = false; selection.drag_attempt = false; _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false); @@ -2124,6 +2125,13 @@ void RichTextLabel::gui_input(const Ref &p_event) { break; } } + + selection.click_frame = c_frame; + selection.click_item = c_item; + selection.click_line = c_line; + selection.click_char = c_index; + + selection.double_click = true; } } else if (!b->is_pressed()) { if (selection.enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { @@ -2283,7 +2291,7 @@ void RichTextLabel::gui_input(const Ref &p_event) { const Line &l2 = selection.click_frame->lines[selection.click_line]; if (l1.char_offset + c_index < l2.char_offset + selection.click_char) { swap = true; - } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char) { + } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char && !selection.double_click) { deselect(); return; } @@ -2296,6 +2304,29 @@ void RichTextLabel::gui_input(const Ref &p_event) { SWAP(selection.from_char, selection.to_char); } + if (selection.double_click && c_frame) { + // Expand the selection to word edges. + + Line *l = &selection.from_frame->lines[selection.from_line]; + MutexLock lock(l->text_buf->get_mutex()); + PackedInt32Array words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (selection.from_char > words[i] && selection.from_char < words[i + 1]) { + selection.from_char = words[i]; + break; + } + } + l = &selection.to_frame->lines[selection.to_line]; + lock = MutexLock(l->text_buf->get_mutex()); + words = TS->shaped_text_get_word_breaks(l->text_buf->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (selection.to_char > words[i] && selection.to_char < words[i + 1]) { + selection.to_char = words[i + 1]; + break; + } + } + } + selection.active = true; queue_redraw(); } @@ -3871,6 +3902,7 @@ void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment, int p item->align_to_row = p_align_to_row; for (int i = 0; i < (int)item->columns.size(); i++) { item->columns[i].expand = false; + item->columns[i].shrink = true; item->columns[i].expand_ratio = 1; } _add_item(item, true, false); @@ -4009,7 +4041,7 @@ void RichTextLabel::push_context() { _add_item(item, true); } -void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) { +void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio, bool p_shrink) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -4018,6 +4050,7 @@ void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_r ItemTable *table = static_cast(current); ERR_FAIL_INDEX(p_column, (int)table->columns.size()); table->columns[p_column].expand = p_expand; + table->columns[p_column].shrink = p_shrink; table->columns[p_column].expand_ratio = p_ratio; } @@ -4553,13 +4586,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("cell"); } else if (tag.begins_with("cell ")) { + bool shrink = true; + OptionMap::Iterator shrink_option = bbcode_options.find("shrink"); + if (shrink_option) { + shrink = (shrink_option->value == "true"); + } + OptionMap::Iterator expand_option = bbcode_options.find("expand"); if (expand_option) { int ratio = expand_option->value.to_int(); if (ratio < 1) { ratio = 1; } - set_table_column_expand(get_current_table_column(), true, ratio); + set_table_column_expand(get_current_table_column(), true, ratio, shrink); } push_cell(); @@ -5302,10 +5341,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("outline_size"); } else if (bbcode_name == "fade") { - int start_index = brk_pos; + int start_index = 0; OptionMap::Iterator start_option = bbcode_options.find("start"); if (start_option) { - start_index += start_option->value.to_int(); + start_index = start_option->value.to_int(); } int length = 10; @@ -6400,7 +6439,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough); ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align", "align_to_row"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGNMENT_TOP), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("push_dropcap", "string", "font", "size", "dropcap_margins", "color", "outline_size", "outline_color"), &RichTextLabel::push_dropcap, DEFVAL(Rect2()), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(0, 0, 0, 0))); - ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio", "shrink"), &RichTextLabel::set_table_column_expand, DEFVAL(1), DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_cell_row_background_color", "odd_row_bg", "even_row_bg"), &RichTextLabel::set_cell_row_background_color); ClassDB::bind_method(D_METHOD("set_cell_border_color", "color"), &RichTextLabel::set_cell_border_color); ClassDB::bind_method(D_METHOD("set_cell_size_override", "min_size", "max_size"), &RichTextLabel::set_cell_size_override); @@ -6539,11 +6578,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("is_menu_visible"), &RichTextLabel::is_menu_visible); ClassDB::bind_method(D_METHOD("menu_option", "option"), &RichTextLabel::menu_option); -#ifndef DISABLE_DEPRECATED - ClassDB::bind_compatibility_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font); - ClassDB::bind_compatibility_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand); -#endif // DISABLE_DEPRECATED - // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 8586cc3f3d..7c7765c226 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -134,10 +134,13 @@ protected: static void _bind_methods(); #ifndef DISABLE_DEPRECATED + void _push_font_bind_compat_79053(const Ref &p_font, int p_size); + void _set_table_column_expand_bind_compat_79053(int p_column, bool p_expand, int p_ratio); void _push_meta_bind_compat_99481(const Variant &p_meta, MetaUnderline p_underline_mode); void _push_meta_bind_compat_89024(const Variant &p_meta); void _add_image_bind_compat_80410(const Ref &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region); bool _remove_paragraph_bind_compat_91098(int p_paragraph); + void _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio); static void _bind_compatibility_methods(); #endif @@ -341,6 +344,7 @@ private: struct ItemTable : public Item { struct Column { bool expand = false; + bool shrink = true; int expand_ratio = 0; int min_width = 0; int max_width = 0; @@ -536,6 +540,7 @@ private: Item *to_item = nullptr; int to_char = 0; + bool double_click = false; // Selecting whole words? bool active = false; // anything selected? i.e. from, to, etc. valid? bool enabled = false; // allow selections? bool drag_attempt = false; @@ -725,7 +730,7 @@ public: void push_fgcolor(const Color &p_color); void push_customfx(Ref p_custom_effect, Dictionary p_environment); void push_context(); - void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1); + void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1, bool p_shrink = true); void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg); void set_cell_border_color(const Color &p_color); void set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index d12739b0a4..6fdb310a9e 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -42,7 +42,7 @@ Size2 SpinBox::get_minimum_size() const { return ms; } -void SpinBox::_update_text(bool p_keep_line_edit) { +void SpinBox::_update_text(bool p_only_update_if_value_changed) { double step = get_step(); if (use_custom_arrow_step && custom_arrow_step != 0.0) { step = custom_arrow_step; @@ -52,6 +52,11 @@ void SpinBox::_update_text(bool p_keep_line_edit) { value = TS->format_number(value); } + if (p_only_update_if_value_changed && value == last_text_value) { + return; + } + last_text_value = value; + if (!line_edit->is_editing()) { if (!prefix.is_empty()) { value = prefix + " " + value; @@ -60,17 +65,12 @@ void SpinBox::_update_text(bool p_keep_line_edit) { value += " " + suffix; } } - - if (p_keep_line_edit && value == last_updated_text && value != line_edit->get_text()) { - return; - } - line_edit->set_text_with_selection(value); - last_updated_text = value; } void SpinBox::_text_submitted(const String &p_string) { if (p_string.is_empty()) { + _update_text(); return; } @@ -290,14 +290,12 @@ void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) { line_edit->select_all(); } } else { - // Discontinue because the focus_exit was caused by canceling or the text is empty. if (Input::get_singleton()->is_action_pressed("ui_cancel") || line_edit->get_text().is_empty()) { - _update_text(); - return; + _update_text(); // Revert text if editing was canceled. + } else { + _update_text(true); // Update text in case value was changed this frame (e.g. on `focus_exited`). + _text_submitted(line_edit->get_text()); } - - line_edit->deselect(); - _text_submitted(line_edit->get_text()); } } diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 60f93a0f19..1332f7885a 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -60,13 +60,13 @@ class SpinBox : public Range { void _range_click_timeout(); void _release_mouse_from_drag_mode(); - void _update_text(bool p_keep_line_edit = false); + void _update_text(bool p_only_update_if_value_changed = false); void _text_submitted(const String &p_string); void _text_changed(const String &p_string); String prefix; String suffix; - String last_updated_text; + String last_text_value; double custom_arrow_step = 0.0; bool use_custom_arrow_step = false; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index cb0e03bdbc..c115bc5607 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1490,8 +1490,8 @@ int TabBar::get_tab_width(int p_idx) const { style = theme_cache.tab_disabled_style; } else if (current == p_idx) { style = theme_cache.tab_selected_style; - // Use the unselected style's width if the hovered one is shorter, to avoid an infinite loop when switching tabs with the mouse. - } else if (hover == p_idx && theme_cache.tab_hovered_style->get_minimum_size().width >= theme_cache.tab_unselected_style->get_minimum_size().width) { + // Always pick the widest style between hovered and unselected, to avoid an infinite loop when switching tabs with the mouse. + } else if (theme_cache.tab_hovered_style->get_minimum_size().width > theme_cache.tab_unselected_style->get_minimum_size().width) { style = theme_cache.tab_hovered_style; } else { style = theme_cache.tab_unselected_style; diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index a320ca2916..1ddc3cc565 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -428,36 +428,20 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref &p_textu void TextureProgressBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) { - if (under.is_valid()) { + if (under.is_valid()) { + if (nine_patch_stretch) { draw_nine_patch_stretched(under, mode, 1.0, tint_under); + } else { + draw_texture(under, Point2(), tint_under); } - if (progress.is_valid()) { + } + + if (progress.is_valid()) { + const bool is_radial_mode = (mode == FILL_CLOCKWISE || mode == FILL_COUNTER_CLOCKWISE || mode == FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE); + + if (nine_patch_stretch && !is_radial_mode) { draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress); - } - if (over.is_valid()) { - draw_nine_patch_stretched(over, mode, 1.0, tint_over); - } - } else { - if (under.is_valid()) { - switch (mode) { - case FILL_CLOCKWISE: - case FILL_COUNTER_CLOCKWISE: - case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { - if (nine_patch_stretch) { - Rect2 region = Rect2(Point2(), get_size()); - draw_texture_rect(under, region, false, tint_under); - } else { - draw_texture(under, Point2(), tint_under); - } - } break; - case FILL_MODE_MAX: - break; - default: - draw_texture(under, Point2(), tint_under); - } - } - if (progress.is_valid()) { + } else { Size2 s = progress->get_size(); switch (mode) { case FILL_LEFT_TO_RIGHT: { @@ -534,23 +518,6 @@ void TextureProgressBar::_notification(int p_what) { draw_polygon(points, colors, uvs, progress); } } - - // Draw a reference cross. - if (is_part_of_edited_scene()) { - Point2 p; - - if (nine_patch_stretch) { - p = get_size(); - } else { - p = progress->get_size(); - } - - p *= get_relative_center(); - p += progress_offset; - p = p.floor(); - draw_line(p - Point2(8, 0), p + Point2(8, 0), Color(0.9, 0.5, 0.5), 2); - draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); - } } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { Rect2 region = Rect2(progress_offset + Point2(s.x / 2 - s.x * get_as_ratio() / 2, 0), Size2(s.x * get_as_ratio(), s.y)); @@ -564,27 +531,32 @@ void TextureProgressBar::_notification(int p_what) { } break; case FILL_MODE_MAX: break; - default: - draw_texture_rect_region(progress, Rect2(progress_offset, Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } - if (over.is_valid()) { - switch (mode) { - case FILL_CLOCKWISE: - case FILL_COUNTER_CLOCKWISE: - case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { - if (nine_patch_stretch) { - Rect2 region = Rect2(Point2(), get_size()); - draw_texture_rect(over, region, false, tint_over); - } else { - draw_texture(over, Point2(), tint_over); - } - } break; - case FILL_MODE_MAX: - break; - default: - draw_texture(over, Point2(), tint_over); +#ifdef TOOLS_ENABLED + // Draw a reference cross for radial modes. + if (is_radial_mode && is_part_of_edited_scene()) { + Point2 p; + + if (nine_patch_stretch) { + p = get_size(); + } else { + p = progress->get_size(); } + + p *= get_relative_center(); + p += progress_offset; + draw_line(p - Point2(8, 0), p + Point2(8, 0), Color(0.9, 0.5, 0.5), 2); + draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); + } +#endif + } + + if (over.is_valid()) { + if (nine_patch_stretch) { + draw_nine_patch_stretched(over, mode, 1.0, tint_over); + } else { + draw_texture(over, Point2(), tint_over); } } } break; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index c005f9ce9c..3720e4edaf 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -286,6 +286,11 @@ #include "scene/3d/skeleton_ik_3d.h" #include "scene/3d/skeleton_modifier_3d.h" #include "scene/3d/soft_body_3d.h" +#include "scene/3d/spring_bone_collision_3d.h" +#include "scene/3d/spring_bone_collision_capsule_3d.h" +#include "scene/3d/spring_bone_collision_plane_3d.h" +#include "scene/3d/spring_bone_collision_sphere_3d.h" +#include "scene/3d/spring_bone_simulator_3d.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/3d/voxel_gi.h" @@ -602,6 +607,11 @@ void register_scene_types() { GDREGISTER_CLASS(RootMotionView); GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); GDREGISTER_CLASS(RetargetModifier3D); + GDREGISTER_CLASS(SpringBoneSimulator3D); + GDREGISTER_VIRTUAL_CLASS(SpringBoneCollision3D); + GDREGISTER_CLASS(SpringBoneCollisionSphere3D); + GDREGISTER_CLASS(SpringBoneCollisionCapsule3D); + GDREGISTER_CLASS(SpringBoneCollisionPlane3D); OS::get_singleton()->yield(); // may take time to init diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index 4732be4d4e..4b74fbf3a6 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -1124,6 +1124,10 @@ void TileSet::set_custom_data_layer_name(int p_layer_id, String p_value) { emit_changed(); } +bool TileSet::has_custom_data_layer_by_name(const String &p_value) const { + return custom_data_layers_by_name.has(p_value); +} + String TileSet::get_custom_data_layer_name(int p_layer_id) const { ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), ""); return custom_data_layers[p_layer_id].name; @@ -4356,6 +4360,7 @@ void TileSet::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); ClassDB::bind_method(D_METHOD("get_custom_data_layer_by_name", "layer_name"), &TileSet::get_custom_data_layer_by_name); ClassDB::bind_method(D_METHOD("set_custom_data_layer_name", "layer_index", "layer_name"), &TileSet::set_custom_data_layer_name); + ClassDB::bind_method(D_METHOD("has_custom_data_layer_by_name", "layer_name"), &TileSet::has_custom_data_layer_by_name); ClassDB::bind_method(D_METHOD("get_custom_data_layer_name", "layer_index"), &TileSet::get_custom_data_layer_name); ClassDB::bind_method(D_METHOD("set_custom_data_layer_type", "layer_index", "layer_type"), &TileSet::set_custom_data_layer_type); ClassDB::bind_method(D_METHOD("get_custom_data_layer_type", "layer_index"), &TileSet::get_custom_data_layer_type); @@ -6638,6 +6643,11 @@ Variant TileData::get_custom_data(String p_layer_name) const { return get_custom_data_by_layer_id(p_layer_id); } +bool TileData::has_custom_data(const String &p_layer_name) const { + ERR_FAIL_NULL_V(tile_set, false); + return tile_set->has_custom_data_layer_by_name(p_layer_name); +} + void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { ERR_FAIL_INDEX(p_layer_id, custom_data.size()); custom_data.write[p_layer_id] = p_value; @@ -7118,6 +7128,7 @@ void TileData::_bind_methods() { // Custom data. ClassDB::bind_method(D_METHOD("set_custom_data", "layer_name", "value"), &TileData::set_custom_data); ClassDB::bind_method(D_METHOD("get_custom_data", "layer_name"), &TileData::get_custom_data); + ClassDB::bind_method(D_METHOD("has_custom_data", "layer_name"), &TileData::has_custom_data); ClassDB::bind_method(D_METHOD("set_custom_data_by_layer_id", "layer_id", "value"), &TileData::set_custom_data_by_layer_id); ClassDB::bind_method(D_METHOD("get_custom_data_by_layer_id", "layer_id"), &TileData::get_custom_data_by_layer_id); diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index cb78a58a85..86d1861f3b 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -493,6 +493,7 @@ public: void remove_custom_data_layer(int p_index); int get_custom_data_layer_by_name(String p_value) const; void set_custom_data_layer_name(int p_layer_id, String p_value); + bool has_custom_data_layer_by_name(const String &p_value) const; String get_custom_data_layer_name(int p_layer_id) const; void set_custom_data_layer_type(int p_layer_id, Variant::Type p_value); Variant::Type get_custom_data_layer_type(int p_layer_id) const; @@ -1001,6 +1002,7 @@ public: // Custom data. void set_custom_data(String p_layer_name, Variant p_value); Variant get_custom_data(String p_layer_name) const; + bool has_custom_data(const String &p_layer_name) const; void set_custom_data_by_layer_id(int p_layer_id, Variant p_value); Variant get_custom_data_by_layer_id(int p_layer_id) const; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index f4ce3442b2..026eae0206 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -134,6 +134,7 @@ void ParticleProcessMaterial::init_shaders() { shader_names->sub_emitter_frequency = "sub_emitter_frequency"; shader_names->sub_emitter_amount_at_end = "sub_emitter_amount_at_end"; shader_names->sub_emitter_amount_at_collision = "sub_emitter_amount_at_collision"; + shader_names->sub_emitter_amount_at_start = "sub_emitter_amount_at_start"; shader_names->sub_emitter_keep_velocity = "sub_emitter_keep_velocity"; shader_names->collision_friction = "collision_friction"; @@ -289,6 +290,9 @@ void ParticleProcessMaterial::_update_shader() { if (sub_emitter_mode == SUB_EMITTER_AT_COLLISION) { code += "uniform int sub_emitter_amount_at_collision;\n"; } + if (sub_emitter_mode == SUB_EMITTER_AT_START) { + code += "uniform int sub_emitter_amount_at_start;\n"; + } code += "uniform bool sub_emitter_keep_velocity;\n"; } @@ -925,6 +929,10 @@ void ParticleProcessMaterial::_update_shader() { code += " float pi = 3.14159;\n"; code += " float degree_to_rad = pi / 180.0;\n\n"; + if (sub_emitter_mode == SUB_EMITTER_AT_START && !RenderingServer::get_singleton()->is_low_end()) { + code += " bool just_spawned = CUSTOM.y == 0.0;\n"; + } + code += " CUSTOM.y += DELTA / LIFETIME;\n"; code += " CUSTOM.y = mix(CUSTOM.y, 1.0, INTERPOLATE_TO_END);\n"; code += " float lifetime_percent = CUSTOM.y / params.lifetime;\n"; @@ -1141,6 +1149,11 @@ void ParticleProcessMaterial::_update_shader() { code += " emit_count = sub_emitter_amount_at_end;\n"; code += " }\n"; } break; + case SUB_EMITTER_AT_START: { + code += " if (just_spawned) {\n"; + code += " emit_count = sub_emitter_amount_at_start;\n"; + code += " }\n"; + } break; default: { } } @@ -1860,6 +1873,10 @@ void ParticleProcessMaterial::_validate_property(PropertyInfo &p_property) const p_property.usage = PROPERTY_USAGE_NONE; } + if (p_property.name == "sub_emitter_amount_at_start" && sub_emitter_mode != SUB_EMITTER_AT_START) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (!turbulence_enabled) { if (p_property.name == "turbulence_noise_strength" || p_property.name == "turbulence_noise_scale" || @@ -1934,6 +1951,15 @@ int ParticleProcessMaterial::get_sub_emitter_amount_at_collision() const { return sub_emitter_amount_at_collision; } +void ParticleProcessMaterial::set_sub_emitter_amount_at_start(int p_amount) { + sub_emitter_amount_at_start = p_amount; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_amount_at_start, p_amount); +} + +int ParticleProcessMaterial::get_sub_emitter_amount_at_start() const { + return sub_emitter_amount_at_start; +} + void ParticleProcessMaterial::set_sub_emitter_keep_velocity(bool p_enable) { sub_emitter_keep_velocity = p_enable; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->sub_emitter_keep_velocity, p_enable); @@ -2115,6 +2141,9 @@ void ParticleProcessMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_collision"), &ParticleProcessMaterial::get_sub_emitter_amount_at_collision); ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_collision", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_collision); + ClassDB::bind_method(D_METHOD("get_sub_emitter_amount_at_start"), &ParticleProcessMaterial::get_sub_emitter_amount_at_start); + ClassDB::bind_method(D_METHOD("set_sub_emitter_amount_at_start", "amount"), &ParticleProcessMaterial::set_sub_emitter_amount_at_start); + ClassDB::bind_method(D_METHOD("get_sub_emitter_keep_velocity"), &ParticleProcessMaterial::get_sub_emitter_keep_velocity); ClassDB::bind_method(D_METHOD("set_sub_emitter_keep_velocity", "enable"), &ParticleProcessMaterial::set_sub_emitter_keep_velocity); @@ -2244,10 +2273,11 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale"); ADD_GROUP("Sub Emitter", "sub_emitter_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled:0,Constant:1,At Start:4,At End:2,At Collision:3"), "set_sub_emitter_mode", "get_sub_emitter_mode"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:Hz"), "set_sub_emitter_frequency", "get_sub_emitter_frequency"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_end", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_end", "get_sub_emitter_amount_at_end"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_collision", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_collision", "get_sub_emitter_amount_at_collision"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_start", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_start", "get_sub_emitter_amount_at_start"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity"); ADD_SIGNAL(MethodInfo("emission_shape_changed")); @@ -2292,6 +2322,7 @@ void ParticleProcessMaterial::_bind_methods() { BIND_ENUM_CONSTANT(SUB_EMITTER_CONSTANT); BIND_ENUM_CONSTANT(SUB_EMITTER_AT_END); BIND_ENUM_CONSTANT(SUB_EMITTER_AT_COLLISION); + BIND_ENUM_CONSTANT(SUB_EMITTER_AT_START); BIND_ENUM_CONSTANT(SUB_EMITTER_MAX); BIND_ENUM_CONSTANT(COLLISION_DISABLED); @@ -2363,6 +2394,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : set_sub_emitter_frequency(4); set_sub_emitter_amount_at_end(1); set_sub_emitter_amount_at_collision(1); + set_sub_emitter_amount_at_start(1); set_sub_emitter_keep_velocity(false); set_attractor_interaction_enabled(true); diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 2d4828c41a..def1ca15f3 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -98,6 +98,7 @@ public: SUB_EMITTER_CONSTANT, SUB_EMITTER_AT_END, SUB_EMITTER_AT_COLLISION, + SUB_EMITTER_AT_START, SUB_EMITTER_MAX }; @@ -119,7 +120,7 @@ private: uint64_t emission_shape : 3; uint64_t invalid_key : 1; uint64_t has_emission_color : 1; - uint64_t sub_emitter : 2; + uint64_t sub_emitter : 3; uint64_t attractor_enabled : 1; uint64_t collision_mode : 2; uint64_t collision_scale : 1; @@ -284,6 +285,7 @@ private: StringName sub_emitter_frequency; StringName sub_emitter_amount_at_end; StringName sub_emitter_amount_at_collision; + StringName sub_emitter_amount_at_start; StringName sub_emitter_keep_velocity; StringName collision_friction; @@ -351,6 +353,7 @@ private: double sub_emitter_frequency = 0.0; int sub_emitter_amount_at_end = 0; int sub_emitter_amount_at_collision = 0; + int sub_emitter_amount_at_start = 0; bool sub_emitter_keep_velocity = false; //do not save emission points here @@ -489,6 +492,9 @@ public: void set_sub_emitter_amount_at_collision(int p_amount); int get_sub_emitter_amount_at_collision() const; + void set_sub_emitter_amount_at_start(int p_amount); + int get_sub_emitter_amount_at_start() const; + void set_sub_emitter_keep_velocity(bool p_enable); bool get_sub_emitter_keep_velocity() const; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 19f188e493..1e0d45dc13 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -919,7 +919,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("screen_get_usable_rect", "screen"), &DisplayServer::screen_get_usable_rect, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_dpi", "screen"), &DisplayServer::screen_get_dpi, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_scale", "screen"), &DisplayServer::screen_get_scale, DEFVAL(SCREEN_OF_MAIN_WINDOW)); - ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available, DEFVAL(SCREEN_OF_MAIN_WINDOW)); + ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available); ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale); ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_pixel", "position"), &DisplayServer::screen_get_pixel); diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index c820c5781a..b7e5873a1b 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -93,6 +93,7 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end); ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer2D::region_get_closest_point); ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer2D::region_get_random_point); + ClassDB::bind_method(D_METHOD("region_get_bounds", "region"), &NavigationServer2D::region_get_bounds); ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create); ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map); diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index 60946dcb4a..cd33a2e55e 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -156,6 +156,7 @@ public: virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const = 0; virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0; + virtual Rect2 region_get_bounds(RID p_region) const = 0; /// Creates a new link between positions in the nav map. virtual RID link_create() = 0; diff --git a/servers/navigation_server_2d_dummy.h b/servers/navigation_server_2d_dummy.h index 19f385bb0c..45bbe19d86 100644 --- a/servers/navigation_server_2d_dummy.h +++ b/servers/navigation_server_2d_dummy.h @@ -89,6 +89,7 @@ public: Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector2(); } Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override { return Vector2(); } Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector2(); } + Rect2 region_get_bounds(RID p_region) const override { return Rect2(); } RID link_create() override { return RID(); } void link_set_map(RID p_link, RID p_map) override {} diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index e8d0309381..a2091efb4e 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -108,6 +108,7 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("region_get_closest_point", "region", "to_point"), &NavigationServer3D::region_get_closest_point); ClassDB::bind_method(D_METHOD("region_get_closest_point_normal", "region", "to_point"), &NavigationServer3D::region_get_closest_point_normal); ClassDB::bind_method(D_METHOD("region_get_random_point", "region", "navigation_layers", "uniformly"), &NavigationServer3D::region_get_random_point); + ClassDB::bind_method(D_METHOD("region_get_bounds", "region"), &NavigationServer3D::region_get_bounds); ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer3D::link_create); ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer3D::link_set_map); diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 83b37bb14b..139c533302 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -178,6 +178,8 @@ public: virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const = 0; virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const = 0; + virtual AABB region_get_bounds(RID p_region) const = 0; + /// Creates a new link between positions in the nav map. virtual RID link_create() = 0; diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index 22488f7e55..84b8acfa00 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -101,6 +101,7 @@ public: Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override { return Vector3(); } Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override { return Vector3(); } Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override { return Vector3(); } + AABB region_get_bounds(RID p_region) const override { return AABB(); } RID link_create() override { return RID(); } void link_set_map(RID p_link, RID p_map) override {} diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h index 698764874c..01358eb770 100644 --- a/servers/rendering/dummy/storage/mesh_storage.h +++ b/servers/rendering/dummy/storage/mesh_storage.h @@ -153,7 +153,7 @@ public: virtual void _multimesh_initialize(RID p_rid) override; virtual void _multimesh_free(RID p_rid) override; - virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {} + virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override {} virtual int _multimesh_get_instance_count(RID p_multimesh) const override { return 0; } virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {} @@ -173,6 +173,7 @@ public: virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); } virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); } virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override; + virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override { return RID(); } virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override { return RID(); } virtual Vector _multimesh_get_buffer(RID p_multimesh) const override; diff --git a/servers/rendering/dummy/storage/particles_storage.h b/servers/rendering/dummy/storage/particles_storage.h index 9eadd517d8..b54fa1911f 100644 --- a/servers/rendering/dummy/storage/particles_storage.h +++ b/servers/rendering/dummy/storage/particles_storage.h @@ -53,8 +53,10 @@ public: virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override {} virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override {} virtual void particles_set_pre_process_time(RID p_particles, double p_time) override {} + virtual void particles_request_process_time(RID p_particles, real_t p_request_process_time) override {} virtual void particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) override {} virtual void particles_set_randomness_ratio(RID p_particles, real_t p_ratio) override {} + virtual void particles_set_seed(RID p_particles, uint32_t p_seed) override {} virtual void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) override {} virtual void particles_set_speed_scale(RID p_particles, double p_scale) override {} virtual void particles_set_use_local_coordinates(RID p_particles, bool p_enable) override {} diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 3794a38fe8..e61cb53d3a 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -601,7 +601,11 @@ void RenderForwardClustered::_render_list_template(RenderingDevice::DrawListID p instance_count /= surf->owner->trail_steps; } - RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count); + if (bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT)) { + RD::get_singleton()->draw_list_draw_indirect(draw_list, index_array_rd.is_valid(), mesh_storage->_multimesh_get_command_buffer_rd_rid(surf->owner->data->base), surf->surface_index * sizeof(uint32_t) * mesh_storage->INDIRECT_MULTIMESH_COMMAND_STRIDE, 1, 0); + } else { + RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count); + } } i += element_info.repeat - 1; //skip equal elements @@ -1085,6 +1089,7 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con } else { surf->sort.lod_index = 0; if (p_render_data->render_info) { + // This does not include primitives rendered via indirect draw calls. uint32_t to_draw = mesh_storage->mesh_surface_get_vertices_drawn_count(surf->surface); to_draw = _indices_to_primitives(surf->primitive, to_draw); to_draw *= inst->instance_count; @@ -4207,9 +4212,9 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p ginstance->base_flags = 0; bool store_transform = true; - if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH; + if (mesh_storage->multimesh_get_transform_format(ginstance->data->base) == RS::MULTIMESH_TRANSFORM_2D) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D; } @@ -4219,6 +4224,9 @@ void RenderForwardClustered::_geometry_instance_update(RenderGeometryInstance *p if (mesh_storage->multimesh_uses_custom_data(ginstance->data->base)) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA; } + if (mesh_storage->multimesh_uses_indirect(ginstance->data->base)) { + ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT; + } ginstance->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index ac8a3491ee..69552d8ed1 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -257,6 +257,7 @@ private: // When changing any of these enums, remember to change the corresponding enums in the shader files as well. enum { + INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT = 1 << 2, INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3, INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4, INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5, diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 46e7ea14f1..70b9b1ca23 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -2382,7 +2382,11 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr instance_count /= surf->owner->trail_steps; } - RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count); + if (bool(surf->owner->base_flags & INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT)) { + RD::get_singleton()->draw_list_draw_indirect(draw_list, index_array_rd.is_valid(), mesh_storage->_multimesh_get_command_buffer_rd_rid(surf->owner->data->base), surf->surface_index * sizeof(uint32_t) * mesh_storage->INDIRECT_MULTIMESH_COMMAND_STRIDE, 1, 0); + } else { + RD::get_singleton()->draw_list_draw(draw_list, index_array_rd.is_valid(), instance_count); + } } } @@ -2803,6 +2807,7 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge if (ginstance->data->base_type == RS::INSTANCE_MULTIMESH) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH; + if (mesh_storage->multimesh_get_transform_format(ginstance->data->base) == RS::MULTIMESH_TRANSFORM_2D) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_FORMAT_2D; } @@ -2812,6 +2817,9 @@ void RenderForwardMobile::_geometry_instance_update(RenderGeometryInstance *p_ge if (mesh_storage->multimesh_uses_custom_data(ginstance->data->base)) { ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_HAS_CUSTOM_DATA; } + if (mesh_storage->multimesh_uses_indirect(ginstance->data->base)) { + ginstance->base_flags |= INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT; + } ginstance->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(ginstance->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index 9b77f02626..3cf68d8a8f 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -360,6 +360,7 @@ protected: // When changing any of these enums, remember to change the corresponding enums in the shader files as well. enum { + INSTANCE_DATA_FLAG_MULTIMESH_INDIRECT = 1 << 2, INSTANCE_DATA_FLAGS_DYNAMIC = 1 << 3, INSTANCE_DATA_FLAGS_NON_UNIFORM_SCALE = 1 << 4, INSTANCE_DATA_FLAG_USE_GI_BUFFERS = 1 << 5, diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 7fcf5f6741..2413babf93 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1487,7 +1487,7 @@ void MeshStorage::_multimesh_free(RID p_rid) { multimesh_owner.free(p_rid); } -void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) { +void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL(multimesh); @@ -1523,6 +1523,9 @@ void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS: multimesh->stride_cache = multimesh->custom_data_offset_cache + (p_use_custom_data ? 4 : 0); multimesh->buffer_set = false; + multimesh->indirect = p_use_indirect; + multimesh->command_buffer = RID(); + //print_line("allocate, elements: " + itos(p_instances) + " 2D: " + itos(p_transform_format == RS::MULTIMESH_TRANSFORM_2D) + " colors " + itos(multimesh->uses_colors) + " data " + itos(multimesh->uses_custom_data) + " stride " + itos(multimesh->stride_cache) + " total size " + itos(multimesh->stride_cache * multimesh->instances)); multimesh->data_cache = Vector(); multimesh->aabb = AABB(); @@ -1611,6 +1614,30 @@ void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) { } multimesh->mesh = p_mesh; + if (multimesh->indirect) { + Mesh *mesh = mesh_owner.get_or_null(p_mesh); + ERR_FAIL_NULL(mesh); + if (mesh->surface_count > 0) { + if (multimesh->command_buffer.is_valid()) { + RD::get_singleton()->free(multimesh->command_buffer); + } + + Vector newVector; + newVector.resize_zeroed(sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE * mesh->surface_count); + + for (uint32_t i = 0; i < mesh->surface_count; i++) { + uint32_t count = mesh_surface_get_vertices_drawn_count(mesh->surfaces[i]); + newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE, static_cast(count)); + newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 1, static_cast(count >> 8)); + newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 2, static_cast(count >> 16)); + newVector.set(i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE + 3, static_cast(count >> 24)); + } + + RID newBuffer = RD::get_singleton()->storage_buffer_create(sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE * mesh->surface_count, newVector, RD::STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT); + multimesh->command_buffer = newBuffer; + } + } + if (multimesh->instances == 0) { return; } @@ -2066,6 +2093,12 @@ void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector &p_ } } +RID MeshStorage::_multimesh_get_command_buffer_rd_rid(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + ERR_FAIL_NULL_V(multimesh, RID()); + return multimesh->command_buffer; +} + RID MeshStorage::_multimesh_get_buffer_rd_rid(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); ERR_FAIL_NULL_V(multimesh, RID()); @@ -2113,6 +2146,15 @@ void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visibl multimesh->visible_instances = p_visible; + if (multimesh->indirect) { //we have to update the command buffer for the instance counts, in each stride this will be the second integer. + Mesh *mesh = mesh_owner.get_or_null(multimesh->mesh); + if (mesh != nullptr) { + for (uint32_t i = 0; i < mesh->surface_count; i++) { + RD::get_singleton()->buffer_update(multimesh->command_buffer, (i * sizeof(uint32_t) * INDIRECT_MULTIMESH_COMMAND_STRIDE) + sizeof(uint32_t), sizeof(uint32_t), &p_visible); + } + } + } + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES); } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index ac417540c8..a81c56827e 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -61,6 +61,10 @@ public: DEFAULT_RD_BUFFER_MAX, }; + enum IndirectMultiMesh : uint32_t { + INDIRECT_MULTIMESH_COMMAND_STRIDE = 5 + }; + private: static MeshStorage *singleton; @@ -228,6 +232,7 @@ private: AABB custom_aabb; bool aabb_dirty = false; bool buffer_set = false; + bool indirect = false; bool motion_vectors_enabled = false; uint32_t motion_vectors_current_offset = 0; uint32_t motion_vectors_previous_offset = 0; @@ -245,6 +250,7 @@ private: RID buffer; //storage buffer RID uniform_set_3d; RID uniform_set_2d; + RID command_buffer; //used if indirect setting is used bool dirty = false; MultiMesh *dirty_list = nullptr; @@ -639,7 +645,7 @@ public: virtual void _multimesh_initialize(RID p_multimesh) override; virtual void _multimesh_free(RID p_rid) override; - virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override; + virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) override; virtual int _multimesh_get_instance_count(RID p_multimesh) const override; virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override; @@ -656,6 +662,7 @@ public: virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override; virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) override; + virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const override; virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const override; virtual Vector _multimesh_get_buffer(RID p_multimesh) const override; @@ -674,6 +681,11 @@ public: bool _multimesh_uses_motion_vectors_offsets(RID p_multimesh); bool _multimesh_uses_motion_vectors(RID p_multimesh); + _FORCE_INLINE_ bool multimesh_uses_indirect(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); + return multimesh->indirect; + } + _FORCE_INLINE_ RS::MultimeshTransformFormat multimesh_get_transform_format(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); return multimesh->xform_format; diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index d88f4d28aa..e10cb73c55 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp @@ -369,6 +369,13 @@ void ParticlesStorage::particles_set_pre_process_time(RID p_particles, double p_ ERR_FAIL_NULL(particles); particles->pre_process_time = p_time; } + +void ParticlesStorage::particles_request_process_time(RID p_particles, real_t p_request_process_time) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_NULL(particles); + particles->request_process_time = p_request_process_time; +} + void ParticlesStorage::particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) { Particles *particles = particles_owner.get_or_null(p_particles); ERR_FAIL_NULL(particles); @@ -524,6 +531,12 @@ void ParticlesStorage::particles_restart(RID p_particles) { particles->restart_request = true; } +void ParticlesStorage::particles_set_seed(RID p_particles, uint32_t p_seed) { + Particles *particles = particles_owner.get_or_null(p_particles); + ERR_FAIL_NULL(particles); + particles->random_seed = p_seed; +} + void ParticlesStorage::_particles_allocate_emission_buffer(Particles *particles) { ERR_FAIL_COND(particles->emission_buffer != nullptr); @@ -814,7 +827,6 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta if (p_particles->clear) { p_particles->cycle_number = 0; - p_particles->random_seed = Math::rand(); } else if (new_phase < p_particles->phase) { if (p_particles->one_shot) { p_particles->emitting = false; @@ -1519,8 +1531,12 @@ void ParticlesStorage::update_particles() { } bool zero_time_scale = Engine::get_singleton()->get_time_scale() <= 0.0; + double todo = particles->request_process_time; + if (particles->clear) { + todo += particles->pre_process_time; + } - if (particles->clear && particles->pre_process_time > 0.0) { + if (todo > 0.0) { double frame_time; if (fixed_fps > 0) { frame_time = 1.0 / fixed_fps; @@ -1528,12 +1544,15 @@ void ParticlesStorage::update_particles() { frame_time = 1.0 / 30.0; } - double todo = particles->pre_process_time; - + float tmp_scale = particles->speed_scale; + // We need this otherwise the speed scale of the particle system influences the TODO. + particles->speed_scale = 1.0; while (todo >= 0) { _particles_process(particles, frame_time); todo -= frame_time; } + particles->request_process_time = 0.0; + particles->speed_scale = tmp_scale; } if (fixed_fps > 0) { @@ -1552,7 +1571,7 @@ void ParticlesStorage::update_particles() { } else if (delta <= 0.0) { //unlikely but.. delta = 0.001; } - double todo = particles->frame_remainder + delta; + todo = particles->frame_remainder + delta; while (todo >= frame_time || particles->clear) { _particles_process(particles, frame_time); diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h index 73989e4011..c7759fc6ee 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.h @@ -168,6 +168,7 @@ private: int amount = 0; double lifetime = 1.0; double pre_process_time = 0.0; + real_t request_process_time = 0.0; real_t explosiveness = 0.0; real_t randomness = 0.0; bool restart_request = false; @@ -263,6 +264,7 @@ private: Particles() : update_list(this) { + random_seed = Math::rand(); } }; @@ -443,6 +445,7 @@ public: virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override; virtual void particles_set_pre_process_time(RID p_particles, double p_time) override; + virtual void particles_request_process_time(RID p_particles, real_t p_request_process_time) override; virtual void particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) override; virtual void particles_set_randomness_ratio(RID p_particles, real_t p_ratio) override; virtual void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) override; @@ -456,6 +459,7 @@ public: virtual void particles_set_fractional_delta(RID p_particles, bool p_enable) override; virtual void particles_set_collision_base_size(RID p_particles, real_t p_size) override; virtual void particles_set_transform_align(RID p_particles, RS::ParticlesTransformAlign p_transform_align) override; + virtual void particles_set_seed(RID p_particles, uint32_t p_seed) override; virtual void particles_set_trails(RID p_particles, bool p_enable, double p_length) override; virtual void particles_set_trail_bind_poses(RID p_particles, const Vector &p_bind_poses) override; diff --git a/servers/rendering/rendering_device.compat.inc b/servers/rendering/rendering_device.compat.inc index 1b14f52f43..12443df81f 100644 --- a/servers/rendering/rendering_device.compat.inc +++ b/servers/rendering/rendering_device.compat.inc @@ -145,6 +145,18 @@ RenderingDevice::FramebufferFormatID RenderingDevice::_screen_get_framebuffer_fo return screen_get_framebuffer_format(DisplayServer::MAIN_WINDOW_ID); } +RID RenderingDevice::_uniform_buffer_create_bind_compat_100062(uint32_t p_size_bytes, const Vector &p_data) { + return uniform_buffer_create(p_size_bytes, p_data, false); +} + +RID RenderingDevice::_vertex_buffer_create_bind_compat_100062(uint32_t p_size_bytes, const Vector &p_data, bool p_use_as_storage) { + return vertex_buffer_create(p_size_bytes, p_data, p_use_as_storage, false); +} + +RID RenderingDevice::_index_buffer_create_bind_compat_100062(uint32_t p_size_indices, IndexBufferFormat p_format, const Vector &p_data, bool p_use_restart_indices) { + return index_buffer_create(p_size_indices, p_format, p_data, p_use_restart_indices, false); +} + void RenderingDevice::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("shader_create_from_bytecode", "binary_data"), &RenderingDevice::_shader_create_from_bytecode_bind_compat_79606); @@ -168,6 +180,10 @@ void RenderingDevice::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("draw_list_begin", "framebuffer", "initial_color_action", "final_color_action", "initial_depth_action", "final_depth_action", "clear_color_values", "clear_depth", "clear_stencil", "region"), &RenderingDevice::_draw_list_begin_bind_compat_90993, DEFVAL(Vector()), DEFVAL(1.0), DEFVAL(0), DEFVAL(Rect2())); ClassDB::bind_compatibility_method(D_METHOD("draw_list_begin", "framebuffer", "initial_color_action", "final_color_action", "initial_depth_action", "final_depth_action", "clear_color_values", "clear_depth", "clear_stencil", "region", "breadcrumb"), &RenderingDevice::_draw_list_begin_bind_compat_98670, DEFVAL(Vector()), DEFVAL(1.0), DEFVAL(0), DEFVAL(Rect2()), DEFVAL(0)); + + ClassDB::bind_compatibility_method(D_METHOD("uniform_buffer_create"), &RenderingDevice::_uniform_buffer_create_bind_compat_100062, DEFVAL(Vector())); + ClassDB::bind_compatibility_method(D_METHOD("vertex_buffer_create"), &RenderingDevice::_vertex_buffer_create_bind_compat_100062, DEFVAL(Vector()), DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("index_buffer_create"), &RenderingDevice::_index_buffer_create_bind_compat_100062, DEFVAL(Vector()), DEFVAL(false)); } #endif diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index ba9f05df00..129ed3dafe 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -788,6 +788,16 @@ Error RenderingDevice::buffer_get_data_async(RID p_buffer, const Callable &p_cal return OK; } +uint64_t RenderingDevice::buffer_get_device_address(RID p_buffer) { + ERR_RENDER_THREAD_GUARD_V(0); + + Buffer *buffer = _get_buffer_from_owner(p_buffer); + ERR_FAIL_NULL_V_MSG(buffer, 0, "Buffer argument is not a valid buffer of any type."); + ERR_FAIL_COND_V_MSG(!buffer->usage.has_flag(RDD::BUFFER_USAGE_DEVICE_ADDRESS_BIT), 0, "Buffer was not created with device address flag."); + + return driver->buffer_get_device_address(buffer->driver_id); +} + RID RenderingDevice::storage_buffer_create(uint32_t p_size_bytes, const Vector &p_data, BitField p_usage) { ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID()); @@ -797,6 +807,14 @@ RID RenderingDevice::storage_buffer_create(uint32_t p_size_bytes, const Vectorbuffer_create(buffer.size, buffer.usage, RDD::MEMORY_ALLOCATION_TYPE_GPU); ERR_FAIL_COND_V(!buffer.driver_id, RID()); @@ -2962,7 +2980,7 @@ bool RenderingDevice::sampler_is_format_supported_for_filter(DataFormat p_format /**** VERTEX BUFFER ****/ /***********************/ -RID RenderingDevice::vertex_buffer_create(uint32_t p_size_bytes, const Vector &p_data, bool p_use_as_storage) { +RID RenderingDevice::vertex_buffer_create(uint32_t p_size_bytes, const Vector &p_data, bool p_use_as_storage, bool p_enable_device_address) { ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID()); Buffer buffer; @@ -2971,6 +2989,9 @@ RID RenderingDevice::vertex_buffer_create(uint32_t p_size_bytes, const Vectorbuffer_create(buffer.size, buffer.usage, RDD::MEMORY_ALLOCATION_TYPE_GPU); ERR_FAIL_COND_V(!buffer.driver_id, RID()); @@ -3101,7 +3122,7 @@ RID RenderingDevice::vertex_array_create(uint32_t p_vertex_count, VertexFormatID return id; } -RID RenderingDevice::index_buffer_create(uint32_t p_index_count, IndexBufferFormat p_format, const Vector &p_data, bool p_use_restart_indices) { +RID RenderingDevice::index_buffer_create(uint32_t p_index_count, IndexBufferFormat p_format, const Vector &p_data, bool p_use_restart_indices, bool p_enable_device_address) { ERR_FAIL_COND_V(p_index_count == 0, RID()); IndexBuffer index_buffer; @@ -3140,6 +3161,9 @@ RID RenderingDevice::index_buffer_create(uint32_t p_index_count, IndexBufferForm #endif index_buffer.size = size_bytes; index_buffer.usage = (RDD::BUFFER_USAGE_TRANSFER_FROM_BIT | RDD::BUFFER_USAGE_TRANSFER_TO_BIT | RDD::BUFFER_USAGE_INDEX_BIT); + if (p_enable_device_address) { + index_buffer.usage.set_flag(RDD::BUFFER_USAGE_DEVICE_ADDRESS_BIT); + } index_buffer.driver_id = driver->buffer_create(index_buffer.size, index_buffer.usage, RDD::MEMORY_ALLOCATION_TYPE_GPU); ERR_FAIL_COND_V(!index_buffer.driver_id, RID()); @@ -3348,12 +3372,15 @@ uint64_t RenderingDevice::shader_get_vertex_input_attribute_mask(RID p_shader) { /**** UNIFORMS ****/ /******************/ -RID RenderingDevice::uniform_buffer_create(uint32_t p_size_bytes, const Vector &p_data) { +RID RenderingDevice::uniform_buffer_create(uint32_t p_size_bytes, const Vector &p_data, bool p_enable_device_address) { ERR_FAIL_COND_V(p_data.size() && (uint32_t)p_data.size() != p_size_bytes, RID()); Buffer buffer; buffer.size = p_size_bytes; buffer.usage = (RDD::BUFFER_USAGE_TRANSFER_TO_BIT | RDD::BUFFER_USAGE_UNIFORM_BIT); + if (p_enable_device_address) { + buffer.usage.set_flag(RDD::BUFFER_USAGE_DEVICE_ADDRESS_BIT); + } buffer.driver_id = driver->buffer_create(buffer.size, buffer.usage, RDD::MEMORY_ALLOCATION_TYPE_GPU); ERR_FAIL_COND_V(!buffer.driver_id, RID()); @@ -7269,11 +7296,11 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("sampler_create", "state"), &RenderingDevice::_sampler_create); ClassDB::bind_method(D_METHOD("sampler_is_format_supported_for_filter", "format", "sampler_filter"), &RenderingDevice::sampler_is_format_supported_for_filter); - ClassDB::bind_method(D_METHOD("vertex_buffer_create", "size_bytes", "data", "use_as_storage"), &RenderingDevice::vertex_buffer_create, DEFVAL(Vector()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("vertex_buffer_create", "size_bytes", "data", "use_as_storage", "enable_device_address"), &RenderingDevice::vertex_buffer_create, DEFVAL(Vector()), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("vertex_format_create", "vertex_descriptions"), &RenderingDevice::_vertex_format_create); ClassDB::bind_method(D_METHOD("vertex_array_create", "vertex_count", "vertex_format", "src_buffers", "offsets"), &RenderingDevice::_vertex_array_create, DEFVAL(Vector())); - ClassDB::bind_method(D_METHOD("index_buffer_create", "size_indices", "format", "data", "use_restart_indices"), &RenderingDevice::index_buffer_create, DEFVAL(Vector()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("index_buffer_create", "size_indices", "format", "data", "use_restart_indices", "enable_device_address"), &RenderingDevice::index_buffer_create, DEFVAL(Vector()), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("index_array_create", "index_buffer", "index_offset", "index_count"), &RenderingDevice::index_array_create); ClassDB::bind_method(D_METHOD("shader_compile_spirv_from_source", "shader_source", "allow_cache"), &RenderingDevice::_shader_compile_spirv_from_source, DEFVAL(true)); @@ -7284,7 +7311,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("shader_get_vertex_input_attribute_mask", "shader"), &RenderingDevice::shader_get_vertex_input_attribute_mask); - ClassDB::bind_method(D_METHOD("uniform_buffer_create", "size_bytes", "data"), &RenderingDevice::uniform_buffer_create, DEFVAL(Vector())); + ClassDB::bind_method(D_METHOD("uniform_buffer_create", "size_bytes", "data", "enable_device_address"), &RenderingDevice::uniform_buffer_create, DEFVAL(Vector()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("storage_buffer_create", "size_bytes", "data", "usage"), &RenderingDevice::storage_buffer_create, DEFVAL(Vector()), DEFVAL(0)); ClassDB::bind_method(D_METHOD("texture_buffer_create", "size_bytes", "format", "data"), &RenderingDevice::texture_buffer_create, DEFVAL(Vector())); @@ -7296,6 +7323,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("buffer_clear", "buffer", "offset", "size_bytes"), &RenderingDevice::buffer_clear); ClassDB::bind_method(D_METHOD("buffer_get_data", "buffer", "offset_bytes", "size_bytes"), &RenderingDevice::buffer_get_data, DEFVAL(0), DEFVAL(0)); ClassDB::bind_method(D_METHOD("buffer_get_data_async", "buffer", "callback", "offset_bytes", "size_bytes"), &RenderingDevice::buffer_get_data_async, DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("buffer_get_device_address", "buffer"), &RenderingDevice::buffer_get_device_address); ClassDB::bind_method(D_METHOD("render_pipeline_create", "shader", "framebuffer_format", "vertex_format", "primitive", "rasterization_state", "multisample_state", "stencil_state", "color_blend_state", "dynamic_state_flags", "for_render_pass", "specialization_constants"), &RenderingDevice::_render_pipeline_create, DEFVAL(0), DEFVAL(0), DEFVAL(TypedArray())); ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid); @@ -7352,6 +7380,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("get_captured_timestamp_cpu_time", "index"), &RenderingDevice::get_captured_timestamp_cpu_time); ClassDB::bind_method(D_METHOD("get_captured_timestamp_name", "index"), &RenderingDevice::get_captured_timestamp_name); + ClassDB::bind_method(D_METHOD("has_feature", "feature"), &RenderingDevice::has_feature); ClassDB::bind_method(D_METHOD("limit_get", "limit"), &RenderingDevice::limit_get); ClassDB::bind_method(D_METHOD("get_frame_delay"), &RenderingDevice::get_frame_delay); ClassDB::bind_method(D_METHOD("submit"), &RenderingDevice::submit); @@ -7726,6 +7755,7 @@ void RenderingDevice::_bind_methods() { BIND_ENUM_CONSTANT(INDEX_BUFFER_FORMAT_UINT32); BIND_BITFIELD_FLAG(STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT); + BIND_BITFIELD_FLAG(STORAGE_BUFFER_USAGE_DEVICE_ADDRESS); BIND_ENUM_CONSTANT(UNIFORM_TYPE_SAMPLER); //for sampling only (sampler GLSL type) BIND_ENUM_CONSTANT(UNIFORM_TYPE_SAMPLER_WITH_TEXTURE); // for sampling only); but includes a texture); (samplerXX GLSL type)); first a sampler then a texture @@ -7870,6 +7900,8 @@ void RenderingDevice::_bind_methods() { BIND_ENUM_CONSTANT(PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT); BIND_ENUM_CONSTANT(PIPELINE_SPECIALIZATION_CONSTANT_TYPE_FLOAT); + BIND_ENUM_CONSTANT(SUPPORTS_BUFFER_DEVICE_ADDRESS); + BIND_ENUM_CONSTANT(LIMIT_MAX_BOUND_UNIFORM_SETS); BIND_ENUM_CONSTANT(LIMIT_MAX_FRAMEBUFFER_COLOR_ATTACHMENTS); BIND_ENUM_CONSTANT(LIMIT_MAX_TEXTURES_PER_UNIFORM_SET); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 9322b6353e..fed6f0d5a8 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -224,6 +224,7 @@ public: Error buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size); Vector buffer_get_data(RID p_buffer, uint32_t p_offset = 0, uint32_t p_size = 0); // This causes stall, only use to retrieve large buffers for saving. Error buffer_get_data_async(RID p_buffer, const Callable &p_callback, uint32_t p_offset = 0, uint32_t p_size = 0); + uint64_t buffer_get_device_address(RID p_buffer); private: /******************/ @@ -755,13 +756,13 @@ private: RID_Owner index_array_owner; public: - RID vertex_buffer_create(uint32_t p_size_bytes, const Vector &p_data = Vector(), bool p_use_as_storage = false); + RID vertex_buffer_create(uint32_t p_size_bytes, const Vector &p_data = Vector(), bool p_use_as_storage = false, bool p_enable_device_address = false); // This ID is warranted to be unique for the same formats, does not need to be freed VertexFormatID vertex_format_create(const Vector &p_vertex_descriptions); RID vertex_array_create(uint32_t p_vertex_count, VertexFormatID p_vertex_format, const Vector &p_src_buffers, const Vector &p_offsets = Vector()); - RID index_buffer_create(uint32_t p_size_indices, IndexBufferFormat p_format, const Vector &p_data = Vector(), bool p_use_restart_indices = false); + RID index_buffer_create(uint32_t p_size_indices, IndexBufferFormat p_format, const Vector &p_data = Vector(), bool p_use_restart_indices = false, bool p_enable_device_address = false); RID index_array_create(RID p_index_buffer, uint32_t p_index_offset, uint32_t p_index_count); /****************/ @@ -895,6 +896,10 @@ private: DrawListID _draw_list_begin_bind_compat_90993(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector &p_clear_color_values, float p_clear_depth, uint32_t p_clear_stencil, const Rect2 &p_region); DrawListID _draw_list_begin_bind_compat_98670(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector &p_clear_color_values, float p_clear_depth, uint32_t p_clear_stencil, const Rect2 &p_region, uint32_t p_breadcrumb); + + RID _uniform_buffer_create_bind_compat_100062(uint32_t p_size_bytes, const Vector &p_data); + RID _vertex_buffer_create_bind_compat_100062(uint32_t p_size_bytes, const Vector &p_data, bool p_use_as_storage); + RID _index_buffer_create_bind_compat_100062(uint32_t p_size_indices, IndexBufferFormat p_format, const Vector &p_data, bool p_use_restart_indices); #endif public: @@ -928,14 +933,15 @@ public: String get_perf_report() const; enum StorageBufferUsage { - STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT = 1, + STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT = (1 << 0), + STORAGE_BUFFER_USAGE_DEVICE_ADDRESS = (1 << 1), }; /*****************/ /**** BUFFERS ****/ /*****************/ - RID uniform_buffer_create(uint32_t p_size_bytes, const Vector &p_data = Vector()); + RID uniform_buffer_create(uint32_t p_size_bytes, const Vector &p_data = Vector(), bool p_enable_device_address = false); RID storage_buffer_create(uint32_t p_size, const Vector &p_data = Vector(), BitField p_usage = 0); RID texture_buffer_create(uint32_t p_size_elements, DataFormat p_format, const Vector &p_data = Vector()); diff --git a/servers/rendering/rendering_device_commons.h b/servers/rendering/rendering_device_commons.h index a9240a2492..5d6c513097 100644 --- a/servers/rendering/rendering_device_commons.h +++ b/servers/rendering/rendering_device_commons.h @@ -887,6 +887,7 @@ public: SUPPORTS_METALFX_TEMPORAL, // If not supported, a fragment shader with only side effects (i.e., writes to buffers, but doesn't output to attachments), may be optimized down to no-op by the GPU driver. SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS, + SUPPORTS_BUFFER_DEVICE_ADDRESS, }; enum SubgroupOperations { diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h index f6572bbfbb..52d0547ce4 100644 --- a/servers/rendering/rendering_device_driver.h +++ b/servers/rendering/rendering_device_driver.h @@ -199,6 +199,7 @@ public: BUFFER_USAGE_INDEX_BIT = (1 << 6), BUFFER_USAGE_VERTEX_BIT = (1 << 7), BUFFER_USAGE_INDIRECT_BIT = (1 << 8), + BUFFER_USAGE_DEVICE_ADDRESS_BIT = (1 << 17), }; enum { @@ -212,6 +213,8 @@ public: virtual uint64_t buffer_get_allocation_size(BufferID p_buffer) = 0; virtual uint8_t *buffer_map(BufferID p_buffer) = 0; virtual void buffer_unmap(BufferID p_buffer) = 0; + // Only for a buffer with BUFFER_USAGE_DEVICE_ADDRESS_BIT. + virtual uint64_t buffer_get_device_address(BufferID p_buffer) = 0; /*****************/ /**** TEXTURE ****/ diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h index b9cf5f4be5..9bbed11adf 100644 --- a/servers/rendering/rendering_server_default.h +++ b/servers/rendering/rendering_server_default.h @@ -384,7 +384,7 @@ public: FUNCRIDSPLIT(multimesh) - FUNC5(multimesh_allocate_data, RID, int, MultimeshTransformFormat, bool, bool) + FUNC6(multimesh_allocate_data, RID, int, MultimeshTransformFormat, bool, bool, bool) FUNC1RC(int, multimesh_get_instance_count, RID) FUNC2(multimesh_set_mesh, RID, RID) @@ -405,6 +405,7 @@ public: FUNC2RC(Color, multimesh_instance_get_custom_data, RID, int) FUNC2(multimesh_set_buffer, RID, const Vector &) + FUNC1RC(RID, multimesh_get_command_buffer_rd_rid, RID) FUNC1RC(RID, multimesh_get_buffer_rd_rid, RID) FUNC1RC(Vector, multimesh_get_buffer, RID) @@ -573,8 +574,10 @@ public: FUNC2(particles_set_lifetime, RID, double) FUNC2(particles_set_one_shot, RID, bool) FUNC2(particles_set_pre_process_time, RID, double) + FUNC2(particles_request_process_time, RID, real_t) FUNC2(particles_set_explosiveness_ratio, RID, float) FUNC2(particles_set_randomness_ratio, RID, float) + FUNC2(particles_set_seed, RID, uint32_t) FUNC2(particles_set_custom_aabb, RID, const AABB &) FUNC2(particles_set_speed_scale, RID, double) FUNC2(particles_set_use_local_coordinates, RID, bool) diff --git a/servers/rendering/storage/mesh_storage.cpp b/servers/rendering/storage/mesh_storage.cpp index b0136fb1f2..e23f1ff9a1 100644 --- a/servers/rendering/storage/mesh_storage.cpp +++ b/servers/rendering/storage/mesh_storage.cpp @@ -50,7 +50,7 @@ void RendererMeshStorage::multimesh_free(RID p_rid) { _multimesh_free(p_rid); } -void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) { +void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data, bool p_use_indirect) { MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); if (mmi) { mmi->_transform_format = p_transform_format; @@ -70,7 +70,7 @@ void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instanc mmi->_data_interpolated.resize_zeroed(size_in_floats); } - _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data); + _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data, p_use_indirect); } int RendererMeshStorage::multimesh_get_instance_count(RID p_multimesh) const { @@ -225,6 +225,10 @@ void RendererMeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer); + virtual RID multimesh_get_command_buffer_rd_rid(RID p_multimesh) const; virtual RID multimesh_get_buffer_rd_rid(RID p_multimesh) const; virtual Vector multimesh_get_buffer(RID p_multimesh) const; @@ -161,7 +162,7 @@ public: virtual void _multimesh_initialize(RID p_rid) = 0; virtual void _multimesh_free(RID p_rid) = 0; - virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0; + virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) = 0; virtual int _multimesh_get_instance_count(RID p_multimesh) const = 0; @@ -182,6 +183,7 @@ public: virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0; virtual void _multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) = 0; + virtual RID _multimesh_get_command_buffer_rd_rid(RID p_multimesh) const = 0; virtual RID _multimesh_get_buffer_rd_rid(RID p_multimesh) const = 0; virtual Vector _multimesh_get_buffer(RID p_multimesh) const = 0; diff --git a/servers/rendering/storage/particles_storage.h b/servers/rendering/storage/particles_storage.h index 33ee79cf03..53e51265ba 100644 --- a/servers/rendering/storage/particles_storage.h +++ b/servers/rendering/storage/particles_storage.h @@ -55,6 +55,7 @@ public: virtual void particles_set_lifetime(RID p_particles, double p_lifetime) = 0; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0; virtual void particles_set_pre_process_time(RID p_particles, double p_time) = 0; + virtual void particles_request_process_time(RID p_particles, real_t p_request_process_time) = 0; virtual void particles_set_explosiveness_ratio(RID p_particles, real_t p_ratio) = 0; virtual void particles_set_randomness_ratio(RID p_particles, real_t p_ratio) = 0; virtual void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) = 0; @@ -69,6 +70,8 @@ public: virtual void particles_set_transform_align(RID p_particles, RS::ParticlesTransformAlign p_transform_align) = 0; + virtual void particles_set_seed(RID p_particles, uint32_t p_seed) = 0; + virtual void particles_set_trails(RID p_particles, bool p_enable, double p_length) = 0; virtual void particles_set_trail_bind_poses(RID p_particles, const Vector &p_bind_poses) = 0; diff --git a/servers/rendering_server.compat.inc b/servers/rendering_server.compat.inc index ae2b88bd4e..cf99e79a8e 100644 --- a/servers/rendering_server.compat.inc +++ b/servers/rendering_server.compat.inc @@ -32,6 +32,10 @@ #ifndef DISABLE_DEPRECATED +void RenderingServer::_multimesh_allocate_data_bind_compat_99455(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) { + multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data, false); +} + void RenderingServer::_environment_set_fog_bind_compat_84792(RID p_env, bool p_enable, const Color &p_light_color, float p_light_energy, float p_sun_scatter, float p_density, float p_height, float p_height_density, float p_aerial_perspective, float p_sky_affect) { environment_set_fog(p_env, p_enable, p_light_color, p_light_energy, p_sun_scatter, p_density, p_height, p_height_density, p_aerial_perspective, p_sky_affect, RS::EnvironmentFogMode::ENV_FOG_MODE_EXPONENTIAL); } @@ -49,6 +53,7 @@ void RenderingServer::_canvas_item_add_circle_bind_compat_84523(RID p_item, cons } void RenderingServer::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::_multimesh_allocate_data_bind_compat_99455, DEFVAL(false), DEFVAL(false)); ClassDB::bind_compatibility_method(D_METHOD("environment_set_fog", "env", "enable", "light_color", "light_energy", "sun_scatter", "density", "height", "height_density", "aerial_perspective", "sky_affect"), &RenderingServer::_environment_set_fog_bind_compat_84792); ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_multiline", "item", "points", "colors", "width"), &RenderingServer::_canvas_item_add_multiline_bind_compat_84523, DEFVAL(-1.0)); ClassDB::bind_compatibility_method(D_METHOD("canvas_item_add_rect", "item", "rect", "color"), &RenderingServer::_canvas_item_add_rect_bind_compat_84523); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 6b0d20d117..1ded996eb9 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2443,7 +2443,7 @@ void RenderingServer::_bind_methods() { /* MULTIMESH API */ ClassDB::bind_method(D_METHOD("multimesh_create"), &RenderingServer::multimesh_create); - ClassDB::bind_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format"), &RenderingServer::multimesh_allocate_data, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("multimesh_allocate_data", "multimesh", "instances", "transform_format", "color_format", "custom_data_format", "use_indirect"), &RenderingServer::multimesh_allocate_data, DEFVAL(false), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("multimesh_get_instance_count", "multimesh"), &RenderingServer::multimesh_get_instance_count); ClassDB::bind_method(D_METHOD("multimesh_set_mesh", "multimesh", "mesh"), &RenderingServer::multimesh_set_mesh); ClassDB::bind_method(D_METHOD("multimesh_instance_set_transform", "multimesh", "index", "transform"), &RenderingServer::multimesh_instance_set_transform); @@ -2461,6 +2461,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("multimesh_set_visible_instances", "multimesh", "visible"), &RenderingServer::multimesh_set_visible_instances); ClassDB::bind_method(D_METHOD("multimesh_get_visible_instances", "multimesh"), &RenderingServer::multimesh_get_visible_instances); ClassDB::bind_method(D_METHOD("multimesh_set_buffer", "multimesh", "buffer"), &RenderingServer::multimesh_set_buffer); + ClassDB::bind_method(D_METHOD("multimesh_get_command_buffer_rd_rid", "multimesh"), &RenderingServer::multimesh_get_command_buffer_rd_rid); ClassDB::bind_method(D_METHOD("multimesh_get_buffer_rd_rid", "multimesh"), &RenderingServer::multimesh_get_buffer_rd_rid); ClassDB::bind_method(D_METHOD("multimesh_get_buffer", "multimesh"), &RenderingServer::multimesh_get_buffer); @@ -2683,6 +2684,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("particles_set_lifetime", "particles", "lifetime"), &RenderingServer::particles_set_lifetime); ClassDB::bind_method(D_METHOD("particles_set_one_shot", "particles", "one_shot"), &RenderingServer::particles_set_one_shot); ClassDB::bind_method(D_METHOD("particles_set_pre_process_time", "particles", "time"), &RenderingServer::particles_set_pre_process_time); + ClassDB::bind_method(D_METHOD("particles_request_process_time", "particles", "time"), &RenderingServer::particles_request_process_time); ClassDB::bind_method(D_METHOD("particles_set_explosiveness_ratio", "particles", "ratio"), &RenderingServer::particles_set_explosiveness_ratio); ClassDB::bind_method(D_METHOD("particles_set_randomness_ratio", "particles", "ratio"), &RenderingServer::particles_set_randomness_ratio); ClassDB::bind_method(D_METHOD("particles_set_interp_to_end", "particles", "factor"), &RenderingServer::particles_set_interp_to_end); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index 0d1907777f..405910705b 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -459,7 +459,12 @@ public: MULTIMESH_INTERP_QUALITY_HIGH, }; - virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0; +protected: +#ifndef DISABLE_DEPRECATED + void _multimesh_allocate_data_bind_compat_99455(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data); +#endif +public: + virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false, bool p_use_indirect = false) = 0; virtual int multimesh_get_instance_count(RID p_multimesh) const = 0; virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0; @@ -480,6 +485,7 @@ public: virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0; virtual void multimesh_set_buffer(RID p_multimesh, const Vector &p_buffer) = 0; + virtual RID multimesh_get_command_buffer_rd_rid(RID p_multimesh) const = 0; virtual RID multimesh_get_buffer_rd_rid(RID p_multimesh) const = 0; virtual Vector multimesh_get_buffer(RID p_multimesh) const = 0; @@ -758,6 +764,7 @@ public: virtual void particles_set_lifetime(RID p_particles, double p_lifetime) = 0; virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0; virtual void particles_set_pre_process_time(RID p_particles, double p_time) = 0; + virtual void particles_request_process_time(RID p_particles, real_t p_request_process_time) = 0; virtual void particles_set_explosiveness_ratio(RID p_particles, float p_ratio) = 0; virtual void particles_set_randomness_ratio(RID p_particles, float p_ratio) = 0; virtual void particles_set_custom_aabb(RID p_particles, const AABB &p_aabb) = 0; @@ -768,6 +775,7 @@ public: virtual void particles_set_interpolate(RID p_particles, bool p_enable) = 0; virtual void particles_set_fractional_delta(RID p_particles, bool p_enable) = 0; virtual void particles_set_collision_base_size(RID p_particles, float p_size) = 0; + virtual void particles_set_seed(RID p_particles, uint32_t p_seed) = 0; enum ParticlesTransformAlign { PARTICLES_TRANSFORM_ALIGN_DISABLED, diff --git a/thirdparty/README.md b/thirdparty/README.md index 5d8ee5e07c..cae1e46b64 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -245,6 +245,7 @@ Files extracted from upstream source: Dither.{cpp,hpp} ForceInline.hpp Math.hpp ProcessCommon.hpp ProcessRGB.{cpp,hpp} ProcessDxtc.{cpp,hpp} Tables.{cpp,hpp} Vector.hpp ``` +- The files `DecodeRGB.{cpp.hpp}` are based on the code from the original repository. - `AUTHORS.txt` and `LICENSE.txt` ## fonts diff --git a/thirdparty/etcpak/DecodeRGB.cpp b/thirdparty/etcpak/DecodeRGB.cpp new file mode 100644 index 0000000000..1ca42c7a29 --- /dev/null +++ b/thirdparty/etcpak/DecodeRGB.cpp @@ -0,0 +1,797 @@ +#include "DecodeRGB.hpp" +#include "Tables.hpp" +#include "Math.hpp" + +#include + +#ifdef __ARM_NEON +# include +#endif + +#if defined __SSE4_1__ || defined __AVX2__ || defined _MSC_VER +# ifdef _MSC_VER +# include +# include +# define _bswap(x) _byteswap_ulong(x) +# define _bswap64(x) _byteswap_uint64(x) +# else +# include +# endif +#endif + +#ifndef _bswap +# define _bswap(x) __builtin_bswap32(x) +# define _bswap64(x) __builtin_bswap64(x) +#endif + +static uint8_t table59T58H[8] = { 3,6,11,16,23,32,41,64 }; + +namespace +{ + +static etcpak_force_inline int32_t expand6(uint32_t value) +{ + return (value << 2) | (value >> 4); +} + +static etcpak_force_inline int32_t expand7(uint32_t value) +{ + return (value << 1) | (value >> 6); +} + +static etcpak_force_inline void DecodeT( uint64_t block, uint32_t* dst, uint32_t w ) +{ + const auto r0 = ( block >> 24 ) & 0x1B; + const auto rh0 = ( r0 >> 3 ) & 0x3; + const auto rl0 = r0 & 0x3; + const auto g0 = ( block >> 20 ) & 0xF; + const auto b0 = ( block >> 16 ) & 0xF; + + const auto r1 = ( block >> 12 ) & 0xF; + const auto g1 = ( block >> 8 ) & 0xF; + const auto b1 = ( block >> 4 ) & 0xF; + + const auto cr0 = ( ( rh0 << 6 ) | ( rl0 << 4 ) | ( rh0 << 2 ) | rl0); + const auto cg0 = ( g0 << 4 ) | g0; + const auto cb0 = ( b0 << 4 ) | b0; + + const auto cr1 = ( r1 << 4 ) | r1; + const auto cg1 = ( g1 << 4 ) | g1; + const auto cb1 = ( b1 << 4 ) | b1; + + const auto codeword_hi = ( block >> 2 ) & 0x3; + const auto codeword_lo = block & 0x1; + const auto codeword = ( codeword_hi << 1 ) | codeword_lo; + + const auto c2r = clampu8( cr1 + table59T58H[codeword] ); + const auto c2g = clampu8( cg1 + table59T58H[codeword] ); + const auto c2b = clampu8( cb1 + table59T58H[codeword] ); + + const auto c3r = clampu8( cr1 - table59T58H[codeword] ); + const auto c3g = clampu8( cg1 - table59T58H[codeword] ); + const auto c3b = clampu8( cb1 - table59T58H[codeword] ); + + const uint32_t col_tab[4] = { + uint32_t( cr0 | ( cg0 << 8 ) | ( cb0 << 16 ) | 0xFF000000 ), + uint32_t( c2r | ( c2g << 8 ) | ( c2b << 16 ) | 0xFF000000 ), + uint32_t( cr1 | ( cg1 << 8 ) | ( cb1 << 16 ) | 0xFF000000 ), + uint32_t( c3r | ( c3g << 8 ) | ( c3b << 16 ) | 0xFF000000 ) + }; + + const uint32_t indexes = ( block >> 32 ) & 0xFFFFFFFF; + for( uint8_t j = 0; j < 4; j++ ) + { + for( uint8_t i = 0; i < 4; i++ ) + { + //2bit indices distributed on two lane 16bit numbers + const uint8_t index = ( ( ( indexes >> ( j + i * 4 + 16 ) ) & 0x1 ) << 1) | ( ( indexes >> ( j + i * 4 ) ) & 0x1); + dst[j * w + i] = col_tab[index]; + } + } +} + +static etcpak_force_inline void DecodeTAlpha( uint64_t block, uint64_t alpha, uint32_t* dst, uint32_t w ) +{ + const auto r0 = ( block >> 24 ) & 0x1B; + const auto rh0 = ( r0 >> 3 ) & 0x3; + const auto rl0 = r0 & 0x3; + const auto g0 = ( block >> 20 ) & 0xF; + const auto b0 = ( block >> 16 ) & 0xF; + + const auto r1 = ( block >> 12 ) & 0xF; + const auto g1 = ( block >> 8 ) & 0xF; + const auto b1 = ( block >> 4 ) & 0xF; + + const auto cr0 = ( ( rh0 << 6 ) | ( rl0 << 4 ) | ( rh0 << 2 ) | rl0); + const auto cg0 = ( g0 << 4 ) | g0; + const auto cb0 = ( b0 << 4 ) | b0; + + const auto cr1 = ( r1 << 4 ) | r1; + const auto cg1 = ( g1 << 4 ) | g1; + const auto cb1 = ( b1 << 4 ) | b1; + + const auto codeword_hi = ( block >> 2 ) & 0x3; + const auto codeword_lo = block & 0x1; + const auto codeword = (codeword_hi << 1) | codeword_lo; + + const int32_t base = alpha >> 56; + const int32_t mul = ( alpha >> 52 ) & 0xF; + const auto tbl = g_alpha[( alpha >> 48 ) & 0xF]; + + const auto c2r = clampu8( cr1 + table59T58H[codeword] ); + const auto c2g = clampu8( cg1 + table59T58H[codeword] ); + const auto c2b = clampu8( cb1 + table59T58H[codeword] ); + + const auto c3r = clampu8( cr1 - table59T58H[codeword] ); + const auto c3g = clampu8( cg1 - table59T58H[codeword] ); + const auto c3b = clampu8( cb1 - table59T58H[codeword] ); + + const uint32_t col_tab[4] = { + uint32_t( cr0 | ( cg0 << 8 ) | ( cb0 << 16 ) ), + uint32_t( c2r | ( c2g << 8 ) | ( c2b << 16 ) ), + uint32_t( cr1 | ( cg1 << 8 ) | ( cb1 << 16 ) ), + uint32_t( c3r | ( c3g << 8 ) | ( c3b << 16 ) ) + }; + + const uint32_t indexes = ( block >> 32 ) & 0xFFFFFFFF; + for( uint8_t j = 0; j < 4; j++ ) + { + for( uint8_t i = 0; i < 4; i++ ) + { + //2bit indices distributed on two lane 16bit numbers + const uint8_t index = ( ( ( indexes >> ( j + i * 4 + 16 ) ) & 0x1 ) << 1 ) | ( ( indexes >> ( j + i * 4 ) ) & 0x1 ); + const auto amod = tbl[( alpha >> ( 45 - j * 3 - i * 12 ) ) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + dst[j * w + i] = col_tab[index] | ( a << 24 ); + } + } +} + +static etcpak_force_inline void DecodeH( uint64_t block, uint32_t* dst, uint32_t w ) +{ + const uint32_t indexes = ( block >> 32 ) & 0xFFFFFFFF; + + const auto r0444 = ( block >> 27 ) & 0xF; + const auto g0444 = ( ( block >> 20 ) & 0x1 ) | ( ( ( block >> 24 ) & 0x7 ) << 1 ); + const auto b0444 = ( ( block >> 15 ) & 0x7 ) | ( ( ( block >> 19 ) & 0x1 ) << 3 ); + + const auto r1444 = ( block >> 11 ) & 0xF; + const auto g1444 = ( block >> 7 ) & 0xF; + const auto b1444 = ( block >> 3 ) & 0xF; + + const auto r0 = ( r0444 << 4 ) | r0444; + const auto g0 = ( g0444 << 4 ) | g0444; + const auto b0 = ( b0444 << 4 ) | b0444; + + const auto r1 = ( r1444 << 4 ) | r1444; + const auto g1 = ( g1444 << 4 ) | g1444; + const auto b1 = ( b1444 << 4 ) | b1444; + + const auto codeword_hi = ( ( block & 0x1 ) << 1 ) | ( ( block & 0x4 ) ); + const auto c0 = ( r0444 << 8 ) | ( g0444 << 4 ) | ( b0444 << 0 ); + const auto c1 = ( block >> 3 ) & ( ( 1 << 12 ) - 1 ); + const auto codeword_lo = ( c0 >= c1 ) ? 1 : 0; + const auto codeword = codeword_hi | codeword_lo; + + const uint32_t col_tab[] = { + uint32_t( clampu8( r0 + table59T58H[codeword] ) | ( clampu8( g0 + table59T58H[codeword] ) << 8 ) | ( clampu8( b0 + table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r0 - table59T58H[codeword] ) | ( clampu8( g0 - table59T58H[codeword] ) << 8 ) | ( clampu8( b0 - table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r1 + table59T58H[codeword] ) | ( clampu8( g1 + table59T58H[codeword] ) << 8 ) | ( clampu8( b1 + table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r1 - table59T58H[codeword] ) | ( clampu8( g1 - table59T58H[codeword] ) << 8 ) | ( clampu8( b1 - table59T58H[codeword] ) << 16 ) ) + }; + + for( uint8_t j = 0; j < 4; j++ ) + { + for( uint8_t i = 0; i < 4; i++ ) + { + const uint8_t index = ( ( ( indexes >> ( j + i * 4 + 16 ) ) & 0x1 ) << 1 ) | ( ( indexes >> ( j + i * 4 ) ) & 0x1 ); + dst[j * w + i] = col_tab[index] | 0xFF000000; + } + } +} + +static etcpak_force_inline void DecodeHAlpha( uint64_t block, uint64_t alpha, uint32_t* dst, uint32_t w ) +{ + const uint32_t indexes = ( block >> 32 ) & 0xFFFFFFFF; + + const auto r0444 = ( block >> 27 ) & 0xF; + const auto g0444 = ( ( block >> 20 ) & 0x1 ) | ( ( ( block >> 24 ) & 0x7 ) << 1 ); + const auto b0444 = ( ( block >> 15 ) & 0x7 ) | ( ( ( block >> 19 ) & 0x1 ) << 3 ); + + const auto r1444 = ( block >> 11 ) & 0xF; + const auto g1444 = ( block >> 7 ) & 0xF; + const auto b1444 = ( block >> 3 ) & 0xF; + + const auto r0 = ( r0444 << 4 ) | r0444; + const auto g0 = ( g0444 << 4 ) | g0444; + const auto b0 = ( b0444 << 4 ) | b0444; + + const auto r1 = ( r1444 << 4 ) | r1444; + const auto g1 = ( g1444 << 4 ) | g1444; + const auto b1 = ( b1444 << 4 ) | b1444; + + const auto codeword_hi = ( ( block & 0x1 ) << 1 ) | ( ( block & 0x4 ) ); + const auto c0 = ( r0444 << 8 ) | ( g0444 << 4 ) | ( b0444 << 0 ); + const auto c1 = ( block >> 3 ) & ( ( 1 << 12 ) - 1 ); + const auto codeword_lo = ( c0 >= c1 ) ? 1 : 0; + const auto codeword = codeword_hi | codeword_lo; + + const int32_t base = alpha >> 56; + const int32_t mul = ( alpha >> 52 ) & 0xF; + const auto tbl = g_alpha[(alpha >> 48) & 0xF]; + + const uint32_t col_tab[] = { + uint32_t( clampu8( r0 + table59T58H[codeword] ) | ( clampu8( g0 + table59T58H[codeword] ) << 8 ) | ( clampu8( b0 + table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r0 - table59T58H[codeword] ) | ( clampu8( g0 - table59T58H[codeword] ) << 8 ) | ( clampu8( b0 - table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r1 + table59T58H[codeword] ) | ( clampu8( g1 + table59T58H[codeword] ) << 8 ) | ( clampu8( b1 + table59T58H[codeword] ) << 16 ) ), + uint32_t( clampu8( r1 - table59T58H[codeword] ) | ( clampu8( g1 - table59T58H[codeword] ) << 8 ) | ( clampu8( b1 - table59T58H[codeword] ) << 16 ) ) + }; + + for( uint8_t j = 0; j < 4; j++ ) + { + for( uint8_t i = 0; i < 4; i++ ) + { + const uint8_t index = ( ( ( indexes >> ( j + i * 4 + 16 ) ) & 0x1 ) << 1 ) | ( ( indexes >> ( j + i * 4 ) ) & 0x1 ); + const auto amod = tbl[( alpha >> ( 45 - j * 3 - i * 12) ) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + dst[j * w + i] = col_tab[index] | ( a << 24 ); + } + } +} + +static etcpak_force_inline void DecodePlanar( uint64_t block, uint32_t* dst, uint32_t w ) +{ + const auto bv = expand6((block >> ( 0 + 32)) & 0x3F); + const auto gv = expand7((block >> ( 6 + 32)) & 0x7F); + const auto rv = expand6((block >> (13 + 32)) & 0x3F); + + const auto bh = expand6((block >> (19 + 32)) & 0x3F); + const auto gh = expand7((block >> (25 + 32)) & 0x7F); + + const auto rh0 = (block >> (32 - 32)) & 0x01; + const auto rh1 = ((block >> (34 - 32)) & 0x1F) << 1; + const auto rh = expand6(rh0 | rh1); + + const auto bo0 = (block >> (39 - 32)) & 0x07; + const auto bo1 = ((block >> (43 - 32)) & 0x3) << 3; + const auto bo2 = ((block >> (48 - 32)) & 0x1) << 5; + const auto bo = expand6(bo0 | bo1 | bo2); + const auto go0 = (block >> (49 - 32)) & 0x3F; + const auto go1 = ((block >> (56 - 32)) & 0x01) << 6; + const auto go = expand7(go0 | go1); + const auto ro = expand6((block >> (57 - 32)) & 0x3F); + +#ifdef __ARM_NEON + uint64_t init = uint64_t(uint16_t(rh-ro)) | ( uint64_t(uint16_t(gh-go)) << 16 ) | ( uint64_t(uint16_t(bh-bo)) << 32 ); + int16x8_t chco = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + init = uint64_t(uint16_t( (rv-ro) - 4 * (rh-ro) )) | ( uint64_t(uint16_t( (gv-go) - 4 * (gh-go) )) << 16 ) | ( uint64_t(uint16_t( (bv-bo) - 4 * (bh-bo) )) << 32 ); + int16x8_t cvco = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + init = uint64_t(4*ro+2) | ( uint64_t(4*go+2) << 16 ) | ( uint64_t(4*bo+2) << 32 ) | ( uint64_t(0xFFF) << 48 ); + int16x8_t col = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + uint8x8_t c = vqshrun_n_s16( col, 2 ); + vst1_lane_u32( dst+j*w+i, vreinterpret_u32_u8( c ), 0 ); + col = vaddq_s16( col, chco ); + } + col = vaddq_s16( col, cvco ); + } +#elif defined __AVX2__ + const auto R0 = 4*ro+2; + const auto G0 = 4*go+2; + const auto B0 = 4*bo+2; + const auto RHO = rh-ro; + const auto GHO = gh-go; + const auto BHO = bh-bo; + + __m256i cvco = _mm256_setr_epi16( rv - ro, gv - go, bv - bo, 0, rv - ro, gv - go, bv - bo, 0, rv - ro, gv - go, bv - bo, 0, rv - ro, gv - go, bv - bo, 0 ); + __m256i col = _mm256_setr_epi16( R0, G0, B0, 0xFFF, R0+RHO, G0+GHO, B0+BHO, 0xFFF, R0+2*RHO, G0+2*GHO, B0+2*BHO, 0xFFF, R0+3*RHO, G0+3*GHO, B0+3*BHO, 0xFFF ); + + for( int j=0; j<4; j++ ) + { + __m256i c = _mm256_srai_epi16( col, 2 ); + __m128i s = _mm_packus_epi16( _mm256_castsi256_si128( c ), _mm256_extracti128_si256( c, 1 ) ); + _mm_storeu_si128( (__m128i*)(dst+j*w), s ); + col = _mm256_add_epi16( col, cvco ); + } +#elif defined __SSE4_1__ + __m128i chco = _mm_setr_epi16( rh - ro, gh - go, bh - bo, 0, 0, 0, 0, 0 ); + __m128i cvco = _mm_setr_epi16( (rv - ro) - 4 * (rh - ro), (gv - go) - 4 * (gh - go), (bv - bo) - 4 * (bh - bo), 0, 0, 0, 0, 0 ); + __m128i col = _mm_setr_epi16( 4*ro+2, 4*go+2, 4*bo+2, 0xFFF, 0, 0, 0, 0 ); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + __m128i c = _mm_srai_epi16( col, 2 ); + __m128i s = _mm_packus_epi16( c, c ); + dst[j*w+i] = _mm_cvtsi128_si32( s ); + col = _mm_add_epi16( col, chco ); + } + col = _mm_add_epi16( col, cvco ); + } +#else + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + const uint32_t r = (i * (rh - ro) + j * (rv - ro) + 4 * ro + 2) >> 2; + const uint32_t g = (i * (gh - go) + j * (gv - go) + 4 * go + 2) >> 2; + const uint32_t b = (i * (bh - bo) + j * (bv - bo) + 4 * bo + 2) >> 2; + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | 0xFF000000; + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | 0xFF000000; + } + } + } +#endif +} + +static etcpak_force_inline void DecodePlanarAlpha( uint64_t block, uint64_t alpha, uint32_t* dst, uint32_t w ) +{ + const auto bv = expand6((block >> ( 0 + 32)) & 0x3F); + const auto gv = expand7((block >> ( 6 + 32)) & 0x7F); + const auto rv = expand6((block >> (13 + 32)) & 0x3F); + + const auto bh = expand6((block >> (19 + 32)) & 0x3F); + const auto gh = expand7((block >> (25 + 32)) & 0x7F); + + const auto rh0 = (block >> (32 - 32)) & 0x01; + const auto rh1 = ((block >> (34 - 32)) & 0x1F) << 1; + const auto rh = expand6(rh0 | rh1); + + const auto bo0 = (block >> (39 - 32)) & 0x07; + const auto bo1 = ((block >> (43 - 32)) & 0x3) << 3; + const auto bo2 = ((block >> (48 - 32)) & 0x1) << 5; + const auto bo = expand6(bo0 | bo1 | bo2); + const auto go0 = (block >> (49 - 32)) & 0x3F; + const auto go1 = ((block >> (56 - 32)) & 0x01) << 6; + const auto go = expand7(go0 | go1); + const auto ro = expand6((block >> (57 - 32)) & 0x3F); + + const int32_t base = alpha >> 56; + const int32_t mul = ( alpha >> 52 ) & 0xF; + const auto tbl = g_alpha[( alpha >> 48 ) & 0xF]; + +#ifdef __ARM_NEON + uint64_t init = uint64_t(uint16_t(rh-ro)) | ( uint64_t(uint16_t(gh-go)) << 16 ) | ( uint64_t(uint16_t(bh-bo)) << 32 ); + int16x8_t chco = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + init = uint64_t(uint16_t( (rv-ro) - 4 * (rh-ro) )) | ( uint64_t(uint16_t( (gv-go) - 4 * (gh-go) )) << 16 ) | ( uint64_t(uint16_t( (bv-bo) - 4 * (bh-bo) )) << 32 ); + int16x8_t cvco = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + init = uint64_t(4*ro+2) | ( uint64_t(4*go+2) << 16 ) | ( uint64_t(4*bo+2) << 32 ); + int16x8_t col = vreinterpretq_s16_u64( vdupq_n_u64( init ) ); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + const auto amod = tbl[(alpha >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + uint8x8_t c = vqshrun_n_s16( col, 2 ); + dst[j*w+i] = vget_lane_u32( vreinterpret_u32_u8( c ), 0 ) | ( a << 24 ); + col = vaddq_s16( col, chco ); + } + col = vaddq_s16( col, cvco ); + } +#elif defined __SSE4_1__ + __m128i chco = _mm_setr_epi16( rh - ro, gh - go, bh - bo, 0, 0, 0, 0, 0 ); + __m128i cvco = _mm_setr_epi16( (rv - ro) - 4 * (rh - ro), (gv - go) - 4 * (gh - go), (bv - bo) - 4 * (bh - bo), 0, 0, 0, 0, 0 ); + __m128i col = _mm_setr_epi16( 4*ro+2, 4*go+2, 4*bo+2, 0, 0, 0, 0, 0 ); + + for( int j=0; j<4; j++ ) + { + for( int i=0; i<4; i++ ) + { + const auto amod = tbl[(alpha >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + __m128i c = _mm_srai_epi16( col, 2 ); + __m128i s = _mm_packus_epi16( c, c ); + dst[j*w+i] = _mm_cvtsi128_si32( s ) | ( a << 24 ); + col = _mm_add_epi16( col, chco ); + } + col = _mm_add_epi16( col, cvco ); + } +#else + for (auto j = 0; j < 4; j++) + { + for (auto i = 0; i < 4; i++) + { + const uint32_t r = (i * (rh - ro) + j * (rv - ro) + 4 * ro + 2) >> 2; + const uint32_t g = (i * (gh - go) + j * (gv - go) + 4 * go + 2) >> 2; + const uint32_t b = (i * (bh - bo) + j * (bv - bo) + 4 * bo + 2) >> 2; + const auto amod = tbl[(alpha >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | ( a << 24 ); + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | ( a << 24 ); + } + } + } +#endif +} +} + +static etcpak_force_inline uint64_t ConvertByteOrder( uint64_t d ) +{ + uint32_t word[2]; + memcpy( word, &d, 8 ); + word[0] = _bswap( word[0] ); + word[1] = _bswap( word[1] ); + memcpy( &d, word, 8 ); + return d; +} + +static etcpak_force_inline void DecodeRGBPart( uint64_t d, uint32_t* dst, uint32_t w ) +{ + d = ConvertByteOrder( d ); + + uint32_t br[2], bg[2], bb[2]; + + if( d & 0x2 ) + { + int32_t dr, dg, db; + + uint32_t r0 = ( d & 0xF8000000 ) >> 27; + uint32_t g0 = ( d & 0x00F80000 ) >> 19; + uint32_t b0 = ( d & 0x0000F800 ) >> 11; + + dr = ( int32_t(d) << 5 ) >> 29; + dg = ( int32_t(d) << 13 ) >> 29; + db = ( int32_t(d) << 21 ) >> 29; + + int32_t r1 = int32_t(r0) + dr; + int32_t g1 = int32_t(g0) + dg; + int32_t b1 = int32_t(b0) + db; + + // T mode + if ( (r1 < 0) || (r1 > 31) ) + { + DecodeT( d, dst, w ); + return; + } + + // H mode + if ((g1 < 0) || (g1 > 31)) + { + DecodeH( d, dst, w ); + return; + } + + // P mode + if( (b1 < 0) || (b1 > 31) ) + { + DecodePlanar( d, dst, w ); + return; + } + + br[0] = ( r0 << 3 ) | ( r0 >> 2 ); + br[1] = ( r1 << 3 ) | ( r1 >> 2 ); + bg[0] = ( g0 << 3 ) | ( g0 >> 2 ); + bg[1] = ( g1 << 3 ) | ( g1 >> 2 ); + bb[0] = ( b0 << 3 ) | ( b0 >> 2 ); + bb[1] = ( b1 << 3 ) | ( b1 >> 2 ); + } + else + { + br[0] = ( ( d & 0xF0000000 ) >> 24 ) | ( ( d & 0xF0000000 ) >> 28 ); + br[1] = ( ( d & 0x0F000000 ) >> 20 ) | ( ( d & 0x0F000000 ) >> 24 ); + bg[0] = ( ( d & 0x00F00000 ) >> 16 ) | ( ( d & 0x00F00000 ) >> 20 ); + bg[1] = ( ( d & 0x000F0000 ) >> 12 ) | ( ( d & 0x000F0000 ) >> 16 ); + bb[0] = ( ( d & 0x0000F000 ) >> 8 ) | ( ( d & 0x0000F000 ) >> 12 ); + bb[1] = ( ( d & 0x00000F00 ) >> 4 ) | ( ( d & 0x00000F00 ) >> 8 ); + } + + unsigned int tcw[2]; + tcw[0] = ( d & 0xE0 ) >> 5; + tcw[1] = ( d & 0x1C ) >> 2; + + uint32_t b1 = ( d >> 32 ) & 0xFFFF; + uint32_t b2 = ( d >> 48 ); + + b1 = ( b1 | ( b1 << 8 ) ) & 0x00FF00FF; + b1 = ( b1 | ( b1 << 4 ) ) & 0x0F0F0F0F; + b1 = ( b1 | ( b1 << 2 ) ) & 0x33333333; + b1 = ( b1 | ( b1 << 1 ) ) & 0x55555555; + + b2 = ( b2 | ( b2 << 8 ) ) & 0x00FF00FF; + b2 = ( b2 | ( b2 << 4 ) ) & 0x0F0F0F0F; + b2 = ( b2 | ( b2 << 2 ) ) & 0x33333333; + b2 = ( b2 | ( b2 << 1 ) ) & 0x55555555; + + uint32_t idx = b1 | ( b2 << 1 ); + + if( d & 0x1 ) + { + for( int i=0; i<4; i++ ) + { + for( int j=0; j<4; j++ ) + { + const auto mod = g_table[tcw[j/2]][idx & 0x3]; + const auto r = br[j/2] + mod; + const auto g = bg[j/2] + mod; + const auto b = bb[j/2] + mod; + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | 0xFF000000; + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | 0xFF000000; + } + idx >>= 2; + } + } + } + else + { + for( int i=0; i<4; i++ ) + { + const auto tbl = g_table[tcw[i/2]]; + const auto cr = br[i/2]; + const auto cg = bg[i/2]; + const auto cb = bb[i/2]; + + for( int j=0; j<4; j++ ) + { + const auto mod = tbl[idx & 0x3]; + const auto r = cr + mod; + const auto g = cg + mod; + const auto b = cb + mod; + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | 0xFF000000; + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | 0xFF000000; + } + idx >>= 2; + } + } + } +} + +static etcpak_force_inline void DecodeRGBAPart( uint64_t d, uint64_t alpha, uint32_t* dst, uint32_t w ) +{ + d = ConvertByteOrder( d ); + alpha = _bswap64( alpha ); + + uint32_t br[2], bg[2], bb[2]; + + if( d & 0x2 ) + { + int32_t dr, dg, db; + + uint32_t r0 = ( d & 0xF8000000 ) >> 27; + uint32_t g0 = ( d & 0x00F80000 ) >> 19; + uint32_t b0 = ( d & 0x0000F800 ) >> 11; + + dr = ( int32_t(d) << 5 ) >> 29; + dg = ( int32_t(d) << 13 ) >> 29; + db = ( int32_t(d) << 21 ) >> 29; + + int32_t r1 = int32_t(r0) + dr; + int32_t g1 = int32_t(g0) + dg; + int32_t b1 = int32_t(b0) + db; + + // T mode + if ( (r1 < 0) || (r1 > 31) ) + { + DecodeTAlpha( d, alpha, dst, w ); + return; + } + + // H mode + if ( (g1 < 0) || (g1 > 31) ) + { + DecodeHAlpha( d, alpha, dst, w ); + return; + } + + // P mode + if ( (b1 < 0) || (b1 > 31) ) + { + DecodePlanarAlpha( d, alpha, dst, w ); + return; + } + + br[0] = ( r0 << 3 ) | ( r0 >> 2 ); + br[1] = ( r1 << 3 ) | ( r1 >> 2 ); + bg[0] = ( g0 << 3 ) | ( g0 >> 2 ); + bg[1] = ( g1 << 3 ) | ( g1 >> 2 ); + bb[0] = ( b0 << 3 ) | ( b0 >> 2 ); + bb[1] = ( b1 << 3 ) | ( b1 >> 2 ); + } + else + { + br[0] = ( ( d & 0xF0000000 ) >> 24 ) | ( ( d & 0xF0000000 ) >> 28 ); + br[1] = ( ( d & 0x0F000000 ) >> 20 ) | ( ( d & 0x0F000000 ) >> 24 ); + bg[0] = ( ( d & 0x00F00000 ) >> 16 ) | ( ( d & 0x00F00000 ) >> 20 ); + bg[1] = ( ( d & 0x000F0000 ) >> 12 ) | ( ( d & 0x000F0000 ) >> 16 ); + bb[0] = ( ( d & 0x0000F000 ) >> 8 ) | ( ( d & 0x0000F000 ) >> 12 ); + bb[1] = ( ( d & 0x00000F00 ) >> 4 ) | ( ( d & 0x00000F00 ) >> 8 ); + } + + unsigned int tcw[2]; + tcw[0] = ( d & 0xE0 ) >> 5; + tcw[1] = ( d & 0x1C ) >> 2; + + uint32_t b1 = ( d >> 32 ) & 0xFFFF; + uint32_t b2 = ( d >> 48 ); + + b1 = ( b1 | ( b1 << 8 ) ) & 0x00FF00FF; + b1 = ( b1 | ( b1 << 4 ) ) & 0x0F0F0F0F; + b1 = ( b1 | ( b1 << 2 ) ) & 0x33333333; + b1 = ( b1 | ( b1 << 1 ) ) & 0x55555555; + + b2 = ( b2 | ( b2 << 8 ) ) & 0x00FF00FF; + b2 = ( b2 | ( b2 << 4 ) ) & 0x0F0F0F0F; + b2 = ( b2 | ( b2 << 2 ) ) & 0x33333333; + b2 = ( b2 | ( b2 << 1 ) ) & 0x55555555; + + uint32_t idx = b1 | ( b2 << 1 ); + + const int32_t base = alpha >> 56; + const int32_t mul = ( alpha >> 52 ) & 0xF; + const auto atbl = g_alpha[( alpha >> 48 ) & 0xF]; + + if( d & 0x1 ) + { + for( int i=0; i<4; i++ ) + { + for( int j=0; j<4; j++ ) + { + const auto mod = g_table[tcw[j/2]][idx & 0x3]; + const auto r = br[j/2] + mod; + const auto g = bg[j/2] + mod; + const auto b = bb[j/2] + mod; + const auto amod = atbl[(alpha >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | ( a << 24 ); + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | ( a << 24 ); + } + idx >>= 2; + } + } + } + else + { + for( int i=0; i<4; i++ ) + { + const auto tbl = g_table[tcw[i/2]]; + const auto cr = br[i/2]; + const auto cg = bg[i/2]; + const auto cb = bb[i/2]; + + for( int j=0; j<4; j++ ) + { + const auto mod = tbl[idx & 0x3]; + const auto r = cr + mod; + const auto g = cg + mod; + const auto b = cb + mod; + const auto amod = atbl[(alpha >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t a = clampu8( base + amod * mul ); + if( ( ( r | g | b ) & ~0xFF ) == 0 ) + { + dst[j*w+i] = r | ( g << 8 ) | ( b << 16 ) | ( a << 24 ); + } + else + { + const auto rc = clampu8( r ); + const auto gc = clampu8( g ); + const auto bc = clampu8( b ); + dst[j*w+i] = rc | ( gc << 8 ) | ( bc << 16 ) | ( a << 24 ); + } + idx >>= 2; + } + } + } +} + +static etcpak_force_inline void DecodeRPart( uint64_t r, uint32_t* dst, uint32_t w ) +{ + r = _bswap64( r ); + + const int32_t base = ( r >> 56 )*8+4; + const int32_t mul = ( r >> 52 ) & 0xF; + const auto atbl = g_alpha[( r >> 48 ) & 0xF]; + + for( int i=0; i<4; i++ ) + { + for ( int j=0; j<4; j++ ) + { + const auto amod = atbl[(r >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t rc = clampu8( ( base + amod * g_alpha11Mul[mul] )/8 ); + dst[j*w+i] = rc | 0xFF000000; + } + } +} + +static etcpak_force_inline void DecodeRGPart( uint64_t r, uint64_t g, uint32_t* dst, uint32_t w ) +{ + r = _bswap64( r ); + g = _bswap64( g ); + + const int32_t rbase = ( r >> 56 )*8+4; + const int32_t rmul = ( r >> 52 ) & 0xF; + const auto rtbl = g_alpha[( r >> 48 ) & 0xF]; + + const int32_t gbase = ( g >> 56 )*8+4; + const int32_t gmul = ( g >> 52 ) & 0xF; + const auto gtbl = g_alpha[( g >> 48 ) & 0xF]; + + for( int i=0; i<4; i++ ) + { + for( int j=0; j<4; j++ ) + { + const auto rmod = rtbl[(r >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t rc = clampu8( ( rbase + rmod * g_alpha11Mul[rmul] )/8 ); + + const auto gmod = gtbl[(g >> ( 45 - j*3 - i*12 )) & 0x7]; + const uint32_t gc = clampu8( ( gbase + gmod * g_alpha11Mul[gmul] )/8 ); + + dst[j*w+i] = rc | (gc << 8) | 0xFF000000; + } + } +} + +void DecodeRBlock( const void* src, void* dst, size_t width ) +{ + uint64_t* srcPtr = (uint64_t*)src; + uint64_t r = *srcPtr++; + DecodeRPart( r, (uint32_t*)dst, width ); +} + +void DecodeRGBlock( const void* src, void* dst, size_t width ) +{ + uint64_t* srcPtr = (uint64_t*)src; + uint64_t r = *srcPtr++; + uint64_t g = *srcPtr++; + DecodeRGPart( r, g, (uint32_t*)dst, width ); +} + +void DecodeRGBBlock( const void* src, void* dst, size_t width ) +{ + uint64_t* srcPtr = (uint64_t*)src; + uint64_t d = *srcPtr++; + DecodeRGBPart( d, (uint32_t*)dst, width ); +} + +void DecodeRGBABlock( const void* src, void* dst, size_t width ) +{ + uint64_t* srcPtr = (uint64_t*)src; + uint64_t a = *srcPtr++; + uint64_t d = *srcPtr++; + DecodeRGBAPart( d, a, (uint32_t*)dst, width ); +} diff --git a/thirdparty/etcpak/DecodeRGB.hpp b/thirdparty/etcpak/DecodeRGB.hpp new file mode 100644 index 0000000000..873e803b31 --- /dev/null +++ b/thirdparty/etcpak/DecodeRGB.hpp @@ -0,0 +1,12 @@ +#ifndef __DECODERGB_HPP__ +#define __DECODERGB_HPP__ + +#include +#include + +void DecodeRBlock( const void* src, void* dst, size_t width ); +void DecodeRGBlock( const void* src, void* dst, size_t width ); +void DecodeRGBBlock( const void* src, void* dst, size_t width ); +void DecodeRGBABlock( const void* src, void* dst, size_t width ); + +#endif diff --git a/version.py b/version.py index 272218dc2c..eed272d691 100644 --- a/version.py +++ b/version.py @@ -11,4 +11,4 @@ docs = "latest" godot_major = 4 godot_minor = 4 godot_patch = 0 -godot_status = "dev" +godot_status = "beta"