diff options
79 files changed, 1185 insertions, 436 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6c8a8ef919..713c982123 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -121,6 +121,7 @@ /modules/mbedtls/tests/ @godotengine/network @godotengine/tests /modules/multiplayer/ @godotengine/network /modules/multiplayer/doc_classes/ @godotengine/network @godotengine/documentation +/modules/multiplayer/tests/ @godotengine/network @godotengine/tests /modules/upnp/ @godotengine/network /modules/upnp/doc_classes/ @godotengine/network @godotengine/documentation /modules/webrtc/ @godotengine/network diff --git a/SConstruct b/SConstruct index 7c7d3d25a0..f9499fc0b0 100644 --- a/SConstruct +++ b/SConstruct @@ -383,8 +383,7 @@ if env["platform"] not in platform_list: # Add platform-specific options. if env["platform"] in platform_opts: - for opt in platform_opts[env["platform"]]: - opts.Add(opt) + opts.AddVariables(*platform_opts[env["platform"]]) # Platform-specific flags. # These can sometimes override default options, so they need to be processed @@ -440,12 +439,11 @@ for name, path in modules_detected.items(): else: enabled = False - opts.Add(BoolVariable("module_" + name + "_enabled", "Enable module '%s'" % (name,), enabled)) + opts.Add(BoolVariable(f"module_{name}_enabled", f"Enable module '{name}'", enabled)) # Add module-specific options. try: - for opt in config.get_opts(env["platform"]): - opts.Add(opt) + opts.AddVariables(*config.get_opts(env["platform"])) except AttributeError: pass @@ -580,7 +578,7 @@ env.Append(RCFLAGS=env.get("rcflags", "").split()) # Feature build profile env.disabled_classes = [] if env["build_profile"] != "": - print('Using feature build profile: "{}"'.format(env["build_profile"])) + print(f'Using feature build profile: "{env["build_profile"]}"') import json try: @@ -592,7 +590,7 @@ if env["build_profile"] != "": for c in dbo: env[c] = dbo[c] except json.JSONDecodeError: - print_error('Failed to open feature build profile: "{}"'.format(env["build_profile"])) + print_error(f'Failed to open feature build profile: "{env["build_profile"]}"') Exit(255) # 'dev_mode' and 'production' are aliases to set default options if they haven't been @@ -854,8 +852,6 @@ else: # GCC, Clang if methods.using_gcc(env): common_warnings += ["-Wshadow", "-Wno-misleading-indentation"] - if cc_version_major == 7: # Bogus warning fixed in 8+. - common_warnings += ["-Wno-strict-overflow"] if cc_version_major < 11: # Regression in GCC 9/10, spams so much in our variadic templates # that we need to outright disable it. @@ -931,7 +927,7 @@ env.module_icons_paths = [] env.doc_class_path = platform_doc_class_path for name, path in modules_detected.items(): - if not env["module_" + name + "_enabled"]: + if not env[f"module_{name}_enabled"]: continue sys.path.insert(0, path) env.current_module = name @@ -1044,7 +1040,7 @@ if env["compiledb"]: if env["ninja"]: if env.scons_version < (4, 2, 0): - print_error("The `ninja=yes` option requires SCons 4.2 or later, but your version is %s." % scons_raw_version) + print_error(f"The `ninja=yes` option requires SCons 4.2 or later, but your version is {scons_raw_version}.") Exit(255) SetOption("experimental", "ninja") diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index a6883064d8..6c28b00f48 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -514,16 +514,19 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) { } } } - if (p_from_version <= 5) { - // Converts the device in events from -1 (emulated events) to -3 (all events). + if (p_from_version == 5) { + // Converts the device in events from -3 to -1. + // -3 was introduced in GH-97707 as a way to prevent a clash in device IDs, but as reported in GH-99243, this leads to problems. + // -3 was used during dev-releases, so this conversion helps to revert such affected projects. + // This conversion doesn't affect any other projects, since -3 is not used otherwise. for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) { if (String(E.key).begins_with("input/")) { Dictionary action = E.value.variant; Array events = action["events"]; for (int i = 0; i < events.size(); i++) { Ref<InputEvent> ev = events[i]; - if (ev.is_valid() && ev->get_device() == -1) { // -1 was the previous value (GH-97707). - ev->set_device(InputEvent::DEVICE_ID_ALL_DEVICES); + if (ev.is_valid() && ev->get_device() == -3) { + ev->set_device(-1); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 05ba114a96..258b01542e 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -154,7 +154,7 @@ public: } virtual bool is_vararg() const override { - return false; + return vararg; } #ifdef TOOLS_ENABLED diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 4733aaf220..045ac83cd8 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -35,6 +35,9 @@ #include "core/os/keyboard.h" #include "core/os/os.h" +const int InputEvent::DEVICE_ID_EMULATION = -1; +const int InputEvent::DEVICE_ID_INTERNAL = -2; + void InputEvent::set_device(int p_device) { device = p_device; emit_changed(); diff --git a/core/input/input_event.h b/core/input/input_event.h index 80bca28fbf..19176f748e 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -62,9 +62,8 @@ protected: static void _bind_methods(); public: - inline static constexpr int DEVICE_ID_EMULATION = -1; - inline static constexpr int DEVICE_ID_INTERNAL = -2; - inline static constexpr int DEVICE_ID_ALL_DEVICES = -3; // Signify that a given Action can be triggered by any device. + static const int DEVICE_ID_EMULATION; + static const int DEVICE_ID_INTERNAL; void set_device(int p_device); int get_device() const; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 54f20a0bcc..abd2c80ce1 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -39,6 +39,8 @@ InputMap *InputMap::singleton = nullptr; +int InputMap::ALL_DEVICES = -1; + void InputMap::_bind_methods() { ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action); ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions); @@ -161,7 +163,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re int i = 0; for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) { int device = E->get()->get_device(); - if (device == InputEvent::DEVICE_ID_ALL_DEVICES || device == p_event->get_device()) { + if (device == ALL_DEVICES || device == p_event->get_device()) { if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) { if (r_event_index) { *r_event_index = i; diff --git a/core/input/input_map.h b/core/input/input_map.h index 2b2a025332..0479d45c57 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -43,6 +43,11 @@ class InputMap : public Object { GDCLASS(InputMap, Object); public: + /** + * A special value used to signify that a given Action can be triggered by any device + */ + static int ALL_DEVICES; + struct Action { int id; float deadzone; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index ecd0a91aa4..1615f145db 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -295,13 +295,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin load_paths_stack.push_back(original_path); // Try all loaders and pick the first match for the type hint - bool loader_found = false; + bool found = false; Ref<Resource> res; for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(p_path, p_type_hint)) { continue; } - loader_found = true; + found = true; res = loader[i]->load(p_path, original_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); if (!res.is_null()) { break; @@ -316,24 +316,15 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin return res; } - if (!loader_found) { - if (r_error) { - *r_error = ERR_FILE_UNRECOGNIZED; - } - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); - } + ERR_FAIL_COND_V_MSG(found, Ref<Resource>(), + vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); #ifdef TOOLS_ENABLED Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES); - if (!file_check->file_exists(p_path)) { - if (r_error) { - *r_error = ERR_FILE_NOT_FOUND; - } - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); - } + ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint)); #endif - ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path)); + ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint)); } // This implementation must allow re-entrancy for a task that started awaiting in a deeper stack frame. diff --git a/core/math/projection.cpp b/core/math/projection.cpp index d0ca7c5684..4a0faef08f 100644 --- a/core/math/projection.cpp +++ b/core/math/projection.cpp @@ -596,101 +596,229 @@ Projection Projection::inverse() const { } void Projection::invert() { - int i, j, k; - int pvt_i[4], pvt_j[4]; /* Locations of pivot matrix */ - real_t pvt_val; /* Value of current pivot element */ - real_t hold; /* Temporary storage */ - real_t determinant = 1.0f; - for (k = 0; k < 4; k++) { - /** Locate k'th pivot element **/ - pvt_val = columns[k][k]; /** Initialize for search **/ - pvt_i[k] = k; - pvt_j[k] = k; - for (i = k; i < 4; i++) { - for (j = k; j < 4; j++) { - if (Math::abs(columns[i][j]) > Math::abs(pvt_val)) { - pvt_i[k] = i; - pvt_j[k] = j; - pvt_val = columns[i][j]; - } - } - } - - /** Product of pivots, gives determinant when finished **/ - determinant *= pvt_val; - if (Math::is_zero_approx(determinant)) { - return; /** Matrix is singular (zero determinant). **/ - } - - /** "Interchange" rows (with sign change stuff) **/ - i = pvt_i[k]; - if (i != k) { /** If rows are different **/ - for (j = 0; j < 4; j++) { - hold = -columns[k][j]; - columns[k][j] = columns[i][j]; - columns[i][j] = hold; - } - } - - /** "Interchange" columns **/ - j = pvt_j[k]; - if (j != k) { /** If columns are different **/ - for (i = 0; i < 4; i++) { - hold = -columns[i][k]; - columns[i][k] = columns[i][j]; - columns[i][j] = hold; - } - } - - /** Divide column by minus pivot value **/ - for (i = 0; i < 4; i++) { - if (i != k) { - columns[i][k] /= (-pvt_val); - } - } - - /** Reduce the matrix **/ - for (i = 0; i < 4; i++) { - hold = columns[i][k]; - for (j = 0; j < 4; j++) { - if (i != k && j != k) { - columns[i][j] += hold * columns[k][j]; - } - } - } - - /** Divide row by pivot **/ - for (j = 0; j < 4; j++) { - if (j != k) { - columns[k][j] /= pvt_val; - } - } - - /** Replace pivot by reciprocal (at last we can touch it). **/ - columns[k][k] = 1.0 / pvt_val; + // Adapted from Mesa's `src/util/u_math.c` `util_invert_mat4x4`. + // MIT licensed. Copyright 2008 VMware, Inc. Authored by Jacques Leroy. + Projection temp; + real_t *out = (real_t *)temp.columns; + real_t *m = (real_t *)columns; + + real_t wtmp[4][8]; + real_t m0, m1, m2, m3, s; + real_t *r0, *r1, *r2, *r3; + +#define MAT(m, r, c) (m)[(c) * 4 + (r)] + + r0 = wtmp[0]; + r1 = wtmp[1]; + r2 = wtmp[2]; + r3 = wtmp[3]; + + r0[0] = MAT(m, 0, 0); + r0[1] = MAT(m, 0, 1); + r0[2] = MAT(m, 0, 2); + r0[3] = MAT(m, 0, 3); + r0[4] = 1.0; + r0[5] = 0.0; + r0[6] = 0.0; + r0[7] = 0.0; + + r1[0] = MAT(m, 1, 0); + r1[1] = MAT(m, 1, 1); + r1[2] = MAT(m, 1, 2); + r1[3] = MAT(m, 1, 3); + r1[5] = 1.0; + r1[4] = 0.0; + r1[6] = 0.0; + r1[7] = 0.0; + + r2[0] = MAT(m, 2, 0); + r2[1] = MAT(m, 2, 1); + r2[2] = MAT(m, 2, 2); + r2[3] = MAT(m, 2, 3); + r2[6] = 1.0; + r2[4] = 0.0; + r2[5] = 0.0; + r2[7] = 0.0; + + r3[0] = MAT(m, 3, 0); + r3[1] = MAT(m, 3, 1); + r3[2] = MAT(m, 3, 2); + r3[3] = MAT(m, 3, 3); + + r3[7] = 1.0; + r3[4] = 0.0; + r3[5] = 0.0; + r3[6] = 0.0; + + /* choose pivot - or die */ + if (Math::abs(r3[0]) > Math::abs(r2[0])) { + SWAP(r3, r2); + } + if (Math::abs(r2[0]) > Math::abs(r1[0])) { + SWAP(r2, r1); + } + if (Math::abs(r1[0]) > Math::abs(r0[0])) { + SWAP(r1, r0); + } + ERR_FAIL_COND(0.0 == r0[0]); + + /* eliminate first variable */ + m1 = r1[0] / r0[0]; + m2 = r2[0] / r0[0]; + m3 = r3[0] / r0[0]; + s = r0[1]; + r1[1] -= m1 * s; + r2[1] -= m2 * s; + r3[1] -= m3 * s; + s = r0[2]; + r1[2] -= m1 * s; + r2[2] -= m2 * s; + r3[2] -= m3 * s; + s = r0[3]; + r1[3] -= m1 * s; + r2[3] -= m2 * s; + r3[3] -= m3 * s; + s = r0[4]; + if (s != 0.0) { + r1[4] -= m1 * s; + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r0[5]; + if (s != 0.0) { + r1[5] -= m1 * s; + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r0[6]; + if (s != 0.0) { + r1[6] -= m1 * s; + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r0[7]; + if (s != 0.0) { + r1[7] -= m1 * s; + r2[7] -= m2 * s; + r3[7] -= m3 * s; } - /* That was most of the work, one final pass of row/column interchange */ - /* to finish */ - for (k = 4 - 2; k >= 0; k--) { /* Don't need to work with 1 by 1 corner*/ - i = pvt_j[k]; /* Rows to swap correspond to pivot COLUMN */ - if (i != k) { /* If rows are different */ - for (j = 0; j < 4; j++) { - hold = columns[k][j]; - columns[k][j] = -columns[i][j]; - columns[i][j] = hold; - } - } + /* choose pivot - or die */ + if (Math::abs(r3[1]) > Math::abs(r2[1])) { + SWAP(r3, r2); + } + if (Math::abs(r2[1]) > Math::abs(r1[1])) { + SWAP(r2, r1); + } + ERR_FAIL_COND(0.0 == r1[1]); + + /* eliminate second variable */ + m2 = r2[1] / r1[1]; + m3 = r3[1] / r1[1]; + r2[2] -= m2 * r1[2]; + r3[2] -= m3 * r1[2]; + r2[3] -= m2 * r1[3]; + r3[3] -= m3 * r1[3]; + s = r1[4]; + if (0.0 != s) { + r2[4] -= m2 * s; + r3[4] -= m3 * s; + } + s = r1[5]; + if (0.0 != s) { + r2[5] -= m2 * s; + r3[5] -= m3 * s; + } + s = r1[6]; + if (0.0 != s) { + r2[6] -= m2 * s; + r3[6] -= m3 * s; + } + s = r1[7]; + if (0.0 != s) { + r2[7] -= m2 * s; + r3[7] -= m3 * s; + } - j = pvt_i[k]; /* Columns to swap correspond to pivot ROW */ - if (j != k) { /* If columns are different */ - for (i = 0; i < 4; i++) { - hold = columns[i][k]; - columns[i][k] = -columns[i][j]; - columns[i][j] = hold; - } - } + /* choose pivot - or die */ + if (Math::abs(r3[2]) > Math::abs(r2[2])) { + SWAP(r3, r2); } + ERR_FAIL_COND(0.0 == r2[2]); + + /* eliminate third variable */ + m3 = r3[2] / r2[2]; + r3[3] -= m3 * r2[3]; + r3[4] -= m3 * r2[4]; + r3[5] -= m3 * r2[5]; + r3[6] -= m3 * r2[6]; + r3[7] -= m3 * r2[7]; + + /* last check */ + ERR_FAIL_COND(0.0 == r3[3]); + + s = 1.0 / r3[3]; /* now back substitute row 3 */ + r3[4] *= s; + r3[5] *= s; + r3[6] *= s; + r3[7] *= s; + + m2 = r2[3]; /* now back substitute row 2 */ + s = 1.0 / r2[2]; + r2[4] = s * (r2[4] - r3[4] * m2); + r2[5] = s * (r2[5] - r3[5] * m2); + r2[6] = s * (r2[6] - r3[6] * m2); + r2[7] = s * (r2[7] - r3[7] * m2); + m1 = r1[3]; + r1[4] -= r3[4] * m1; + r1[5] -= r3[5] * m1; + r1[6] -= r3[6] * m1; + r1[7] -= r3[7] * m1; + m0 = r0[3]; + r0[4] -= r3[4] * m0; + r0[5] -= r3[5] * m0; + r0[6] -= r3[6] * m0; + r0[7] -= r3[7] * m0; + + m1 = r1[2]; /* now back substitute row 1 */ + s = 1.0 / r1[1]; + r1[4] = s * (r1[4] - r2[4] * m1); + r1[5] = s * (r1[5] - r2[5] * m1), + r1[6] = s * (r1[6] - r2[6] * m1); + r1[7] = s * (r1[7] - r2[7] * m1); + m0 = r0[2]; + r0[4] -= r2[4] * m0; + r0[5] -= r2[5] * m0; + r0[6] -= r2[6] * m0; + r0[7] -= r2[7] * m0; + + m0 = r0[1]; /* now back substitute row 0 */ + s = 1.0 / r0[0]; + r0[4] = s * (r0[4] - r1[4] * m0); + r0[5] = s * (r0[5] - r1[5] * m0), + r0[6] = s * (r0[6] - r1[6] * m0); + r0[7] = s * (r0[7] - r1[7] * m0); + + MAT(out, 0, 0) = r0[4]; + MAT(out, 0, 1) = r0[5]; + MAT(out, 0, 2) = r0[6]; + MAT(out, 0, 3) = r0[7]; + MAT(out, 1, 0) = r1[4]; + MAT(out, 1, 1) = r1[5]; + MAT(out, 1, 2) = r1[6]; + MAT(out, 1, 3) = r1[7]; + MAT(out, 2, 0) = r2[4]; + MAT(out, 2, 1) = r2[5]; + MAT(out, 2, 2) = r2[6]; + MAT(out, 2, 3) = r2[7]; + MAT(out, 3, 0) = r3[4]; + MAT(out, 3, 1) = r3[5]; + MAT(out, 3, 2) = r3[6]; + MAT(out, 3, 3) = r3[7]; + +#undef MAT + + *this = temp; } void Projection::flip_y() { diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index 93680de21e..900318e4f4 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -22,6 +22,7 @@ <description> Returns the position in the [AudioStream] of the latest sound, in seconds. Returns [code]0.0[/code] if no sounds are playing. [b]Note:[/b] The position is not always accurate, as the [AudioServer] does not mix audio every processed frame. To get more accurate results, add [method AudioServer.get_time_since_last_mix] to the returned position. + [b]Note:[/b] This method always returns [code]0.0[/code] if the [member stream] is an [AudioStreamInteractive], since it can have multiple clips playing at once. </description> </method> <method name="get_stream_playback"> diff --git a/doc/classes/EditorSpinSlider.xml b/doc/classes/EditorSpinSlider.xml index 83c65b736e..6cd375a46d 100644 --- a/doc/classes/EditorSpinSlider.xml +++ b/doc/classes/EditorSpinSlider.xml @@ -40,6 +40,11 @@ Emitted when the spinner/slider is ungrabbed. </description> </signal> + <signal name="updown_pressed"> + <description> + Emitted when the updown button is pressed. + </description> + </signal> <signal name="value_focus_entered"> <description> Emitted when the value form gains focus. diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index bba5157053..3ae3753930 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -13,21 +13,7 @@ <return type="String" /> <description> Returns the name of the CPU architecture the Godot binary was built for. Possible return values include [code]"x86_64"[/code], [code]"x86_32"[/code], [code]"arm64"[/code], [code]"arm32"[/code], [code]"rv64"[/code], [code]"riscv"[/code], [code]"ppc64"[/code], [code]"ppc"[/code], [code]"wasm64"[/code], and [code]"wasm32"[/code]. - To detect whether the current build is 64-bit, you can use the fact that all 64-bit architecture names contain [code]64[/code] in their name: - [codeblocks] - [gdscript] - if "64" in Engine.get_architecture_name(): - print("Running a 64-bit build of Godot.") - else: - print("Running a 32-bit build of Godot.") - [/gdscript] - [csharp] - if (Engine.GetArchitectureName().Contains("64")) - GD.Print("Running a 64-bit build of Godot."); - else - GD.Print("Running a 32-bit build of Godot."); - [/csharp] - [/codeblocks] + To detect whether the current build is 64-bit, or the type of architecture, don't use the architecture name. Instead, use [method OS.has_feature] to check for the [code]"64"[/code] feature tag, or tags such as [code]"x86"[/code] or [code]"arm"[/code]. See the [url=$DOCS_URL/tutorials/export/feature_tags.html]Feature Tags[/url] documentation for more details. [b]Note:[/b] This method does [i]not[/i] return the name of the system's CPU architecture (like [method OS.get_processor_name]). For example, when running an [code]x86_32[/code] Godot binary on an [code]x86_64[/code] system, the returned value will still be [code]"x86_32"[/code]. </description> </method> diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 5c89b64429..d7ca8afc2c 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -4,7 +4,7 @@ Provides methods for file reading and writing operations. </brief_description> <description> - This class can be used to permanently store data in the user device's file system and to read from it. This is useful for store game save data or player configuration files. + This class can be used to permanently store data in the user device's file system and to read from it. This is useful for storing game save data or player configuration files. Here's a sample on how to write and read from a file: [codeblocks] [gdscript] diff --git a/doc/classes/SubViewportContainer.xml b/doc/classes/SubViewportContainer.xml index b7d097cc91..310f235abf 100644 --- a/doc/classes/SubViewportContainer.xml +++ b/doc/classes/SubViewportContainer.xml @@ -20,6 +20,10 @@ </method> </methods> <members> + <member name="consume_drag_and_drop" type="bool" setter="set_consume_drag_and_drop" getter="is_consume_drag_and_drop_enabled" default="false"> + If [code]false[/code], the [SubViewportContainer] is not available as a drop target in drag-and-drop operations, and instead the [Control] nodes inside its [Viewport] children are potential drop targets. + If [code]true[/code], the [SubViewportContainer] itself will be considered as a drop target in drag-and-drop operations, preventing the [Control] nodes inside its [Viewport] children from becoming drop targets. + </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="1" /> <member name="stretch" type="bool" setter="set_stretch" getter="is_stretch_enabled" default="false"> If [code]true[/code], the sub-viewport will be automatically resized to the control's size. diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index fcfbeddb9e..2022c8ee43 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1259,7 +1259,7 @@ float SchlickFresnel(float u) { return m2 * m2 * m; // pow(m,5) } -void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_directional, float attenuation, vec3 f0, float roughness, float metallic, float specular_amount, vec3 albedo, inout float alpha, +void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_directional, float attenuation, vec3 f0, float roughness, float metallic, float specular_amount, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -1423,7 +1423,7 @@ float get_omni_spot_attenuation(float distance, float inv_range, float decay) { } #if !defined(DISABLE_LIGHT_OMNI) || defined(ADDITIVE_OMNI) -void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, +void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -1450,7 +1450,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f omni_attenuation *= shadow; - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, omni_attenuation, f0, roughness, metallic, omni_lights[idx].specular_amount, albedo, alpha, + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, omni_attenuation, f0, roughness, metallic, omni_lights[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -1469,7 +1469,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f #endif // !DISABLE_LIGHT_OMNI #if !defined(DISABLE_LIGHT_SPOT) || defined(ADDITIVE_SPOT) -void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, +void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -1506,7 +1506,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f spot_attenuation *= shadow; - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, spot_attenuation, f0, roughness, metallic, spot_lights[idx].specular_amount, albedo, alpha, + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, spot_attenuation, f0, roughness, metallic, spot_lights[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2114,7 +2114,7 @@ void main() { continue; } #endif - light_compute(normal, normalize(directional_lights[i].direction), normalize(view), directional_lights[i].size, directional_lights[i].color * directional_lights[i].energy, true, 1.0, f0, roughness, metallic, 1.0, albedo, alpha, + light_compute(normal, normalize(directional_lights[i].direction), normalize(view), directional_lights[i].size, directional_lights[i].color * directional_lights[i].energy, true, 1.0, f0, roughness, metallic, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2143,7 +2143,7 @@ void main() { continue; } #endif - light_process_omni(omni_light_indices[i], vertex, view, normal, f0, roughness, metallic, 1.0, albedo, alpha, + light_process_omni(omni_light_indices[i], vertex, view, normal, f0, roughness, metallic, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2171,7 +2171,7 @@ void main() { continue; } #endif - light_process_spot(spot_light_indices[i], vertex, view, normal, f0, roughness, metallic, 1.0, albedo, alpha, + light_process_spot(spot_light_indices[i], vertex, view, normal, f0, roughness, metallic, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2389,7 +2389,7 @@ void main() { #endif // SHADOWS_DISABLED #ifndef USE_VERTEX_LIGHTING - light_compute(normal, normalize(directional_lights[directional_shadow_index].direction), normalize(view), directional_lights[directional_shadow_index].size, directional_lights[directional_shadow_index].color * directional_lights[directional_shadow_index].energy, true, directional_shadow, f0, roughness, metallic, 1.0, albedo, alpha, + light_compute(normal, normalize(directional_lights[directional_shadow_index].direction), normalize(view), directional_lights[directional_shadow_index].size, directional_lights[directional_shadow_index].color * directional_lights[directional_shadow_index].energy, true, directional_shadow, f0, roughness, metallic, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2421,7 +2421,7 @@ void main() { #endif // SHADOWS_DISABLED #ifndef USE_VERTEX_LIGHTING - light_process_omni(omni_light_index, vertex, view, normal, f0, roughness, metallic, omni_shadow, albedo, alpha, + light_process_omni(omni_light_index, vertex, view, normal, f0, roughness, metallic, omni_shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2451,7 +2451,7 @@ void main() { #endif // SHADOWS_DISABLED #ifndef USE_VERTEX_LIGHTING - light_process_spot(spot_light_index, vertex, view, normal, f0, roughness, metallic, spot_shadow, albedo, alpha, + light_process_spot(spot_light_index, vertex, view, normal, f0, roughness, metallic, spot_shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 43ad0799ba..4b92e5f8a1 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -118,7 +118,7 @@ Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) { last_error = ERR_FILE_CANT_OPEN; return last_error; } - fchmod(fd, 0666); + fchmod(fd, 0644); path = String::utf8(cs.ptr()); f = fdopen(fd, mode_string); diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp index 0a78429dec..fd60bec9d0 100644 --- a/drivers/unix/file_access_unix_pipe.cpp +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -70,7 +70,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) struct stat st = {}; int err = stat(path.utf8().get_data(), &st); if (err) { - if (mkfifo(path.utf8().get_data(), 0666) != 0) { + if (mkfifo(path.utf8().get_data(), 0600) != 0) { last_error = ERR_FILE_CANT_OPEN; return last_error; } diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index a86f72e0b9..b6e5ed0287 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -2392,7 +2392,9 @@ RDD::CommandQueueFamilyID RenderingDeviceDriverVulkan::command_queue_family_get( } } - ERR_FAIL_COND_V_MSG(picked_family_index >= queue_family_properties.size(), CommandQueueFamilyID(), "A queue family with the requested bits could not be found."); + if (picked_family_index >= queue_family_properties.size()) { + return CommandQueueFamilyID(); + } // Since 0 is a valid index and we use 0 as the error case, we make the index start from 1 instead. return CommandQueueFamilyID(picked_family_index + 1); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 7a1296c411..19e0647fb7 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2191,7 +2191,7 @@ void AnimationTrackEdit::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = animation->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -3647,7 +3647,7 @@ void AnimationTrackEditGroup::_notification(int p_what) { offset = offset * scale + limit; Color marker_color = editor->get_current_animation()->get_marker_color(marker); marker_color.a = 0.2; - draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color); + draw_line(Point2(offset, 0), Point2(offset, get_size().height), marker_color, Math::round(EDSCALE)); } } } @@ -7364,7 +7364,6 @@ void AnimationTrackEditor::_update_snap_unit() { } if (timeline->is_using_fps()) { - _clear_selection(true); // Needs to recreate a spinbox of the KeyEdit. snap_unit = 1.0 / step->get_value(); } else { if (fps_compat->is_pressed()) { @@ -7983,6 +7982,11 @@ AnimationTrackEditor::~AnimationTrackEditor() { // AnimationTrackKeyEditEditorPlugin. +void AnimationTrackKeyEditEditor::_time_edit_spun() { + _time_edit_entered(); + _time_edit_exited(); +} + void AnimationTrackKeyEditEditor::_time_edit_entered() { int key = animation->track_find_key(track, key_ofs, Animation::FIND_MODE_APPROX); if (key == -1) { @@ -8010,7 +8014,7 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() { int existing = animation->track_find_key(track, new_time, Animation::FIND_MODE_APPROX); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Animation Change Keyframe Time"), UndoRedo::MERGE_ENDS); + undo_redo->create_action(TTR("Animation Change Keyframe Time")); if (existing != -1) { undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, animation->track_get_key_time(track, existing)); @@ -8057,6 +8061,7 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat spinner->set_min(0); spinner->set_allow_greater(true); spinner->set_allow_lesser(true); + add_child(spinner); if (use_fps) { spinner->set_step(FPS_DECIMAL); @@ -8065,14 +8070,13 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat fps = 1.0 / fps; } spinner->set_value(key_ofs * fps); + spinner->connect("updown_pressed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_spun), CONNECT_DEFERRED); } else { spinner->set_step(SECOND_DECIMAL); spinner->set_value(key_ofs); spinner->set_max(animation->get_length()); } - add_child(spinner); - spinner->connect("grabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("ungrabbed", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); spinner->connect("value_focus_entered", callable_mp(this, &AnimationTrackKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); @@ -8307,9 +8311,6 @@ void AnimationMarkerEdit::_notification(int p_what) { Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label")); Color color = get_theme_color(SceneStringName(font_color), SNAME("Label")); - int hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList")); - Color linecolor = color; - linecolor.a = 0.2; // SECTION PREVIEW // @@ -8368,31 +8369,21 @@ void AnimationMarkerEdit::_notification(int p_what) { draw_key(name, scale, int(offset), is_selected, limit, limit_end); - const int font_size = 16; + const int font_size = 12 * EDSCALE; Size2 string_size = font->get_string_size(name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size); if (int(offset) <= limit_end && int(offset) >= limit && should_show_all_marker_names) { float bottom = get_size().height + string_size.y - font->get_descent(font_size); float extrusion = MAX(0, offset + string_size.x - limit_end); // How much the string would extrude outside limit_end if unadjusted. Color marker_color = animation->get_marker_color(name); - draw_string(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); - draw_string_outline(font, Point2(offset - extrusion, bottom), name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); + float margin = 4 * EDSCALE; + Point2 pos = Point2(offset - extrusion + margin, bottom + margin); + draw_string(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, marker_color); + draw_string_outline(font, pos, name, HORIZONTAL_ALIGNMENT_LEFT, -1.0, font_size, 1, color); } } } draw_fg(limit, get_size().width - timeline->get_buttons_width()); - - // BUTTONS // - - { - int ofs = get_size().width - timeline->get_buttons_width(); - - draw_line(Point2(ofs, 0), Point2(ofs, get_size().height), linecolor, Math::round(EDSCALE)); - - ofs += hsep; - } - - draw_line(Vector2(0, get_size().height), get_size(), linecolor, Math::round(EDSCALE)); } break; case NOTIFICATION_MOUSE_ENTER: @@ -9193,9 +9184,6 @@ void AnimationMultiMarkerKeyEdit::_get_property_list(List<PropertyInfo> *p_list) // AnimationMarkerKeyEditEditorPlugin -void AnimationMarkerKeyEditEditor::_time_edit_entered() { -} - void AnimationMarkerKeyEditEditor::_time_edit_exited() { real_t new_time = spinner->get_value(); @@ -9214,7 +9202,7 @@ void AnimationMarkerKeyEditEditor::_time_edit_exited() { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Animation Change Marker Time"), UndoRedo::MERGE_ENDS); + undo_redo->create_action(TTR("Animation Change Marker Time")); Color color = animation->get_marker_color(marker_name); undo_redo->add_do_method(animation.ptr(), "add_marker", marker_name, new_time); @@ -9255,6 +9243,7 @@ AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_anim spinner->set_min(0); spinner->set_allow_greater(true); spinner->set_allow_lesser(true); + add_child(spinner); float time = animation->get_marker_time(marker_name); @@ -9265,17 +9254,14 @@ AnimationMarkerKeyEditEditor::AnimationMarkerKeyEditEditor(Ref<Animation> p_anim fps = 1.0 / fps; } spinner->set_value(time * fps); + spinner->connect("updown_pressed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); } else { spinner->set_step(SECOND_DECIMAL); spinner->set_value(time); spinner->set_max(animation->get_length()); } - add_child(spinner); - - spinner->connect("grabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("ungrabbed", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); - spinner->connect("value_focus_entered", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_entered), CONNECT_DEFERRED); spinner->connect("value_focus_exited", callable_mp(this, &AnimationMarkerKeyEditEditor::_time_edit_exited), CONNECT_DEFERRED); } diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index e7271f1941..f17386b0c9 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -977,6 +977,7 @@ class AnimationTrackKeyEditEditor : public EditorProperty { Variant value; } key_data_cache; + void _time_edit_spun(); void _time_edit_entered(); void _time_edit_exited(); @@ -996,7 +997,6 @@ class AnimationMarkerKeyEditEditor : public EditorProperty { EditorSpinSlider *spinner = nullptr; - void _time_edit_entered(); void _time_edit_exited(); public: diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index 8fde728027..a6b30233fc 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -121,7 +121,7 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo } String EventListenerLineEdit::get_device_string(int p_device) { - if (p_device == InputEvent::DEVICE_ID_ALL_DEVICES) { + if (p_device == InputMap::ALL_DEVICES) { return TTR("All Devices"); } return TTR("Device") + " " + itos(p_device); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 8ae4b856a0..0fc62416af 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -1206,6 +1206,9 @@ void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) { bool export_debug = fd_option.get(TTR("Export With Debug"), true); bool export_as_patch = fd_option.get(TTR("Export As Patch"), true); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_debug", export_debug); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_as_patch", export_as_patch); + if (p_path.ends_with(".zip")) { if (export_as_patch) { platform->export_zip_patch(current, export_debug, p_path); @@ -1305,6 +1308,8 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) { Dictionary fd_option = export_project->get_selected_options(); bool export_debug = fd_option.get(TTR("Export With Debug"), true); + EditorSettings::get_singleton()->set_project_metadata("export_options", "export_debug", export_debug); + Error err = platform->export_project(current, export_debug, current->get_export_path(), 0); result_dialog_log->clear(); if (err != ERR_SKIP) { @@ -1774,9 +1779,9 @@ ProjectExportDialog::ProjectExportDialog() { export_project->connect("file_selected", callable_mp(this, &ProjectExportDialog::_export_project_to_path)); export_project->get_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &ProjectExportDialog::_validate_export_path)); - export_project->add_option(TTR("Export With Debug"), Vector<String>(), true); - export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), true); - export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), true); + export_project->add_option(TTR("Export With Debug"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_debug", true)); + export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_debug", true)); + export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), EditorSettings::get_singleton()->get_project_metadata("export_options", "export_as_patch", true)); set_hide_on_ok(false); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 712e91faca..aa9e9f841d 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -67,6 +67,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) { } else { set_value(get_value() - get_step()); } + emit_signal("updown_pressed"); return; } _grab_start(); @@ -696,6 +697,7 @@ void EditorSpinSlider::_bind_methods() { ADD_SIGNAL(MethodInfo("grabbed")); ADD_SIGNAL(MethodInfo("ungrabbed")); + ADD_SIGNAL(MethodInfo("updown_pressed")); ADD_SIGNAL(MethodInfo("value_focus_entered")); ADD_SIGNAL(MethodInfo("value_focus_exited")); diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index a2aeeb11bd..c60197b96b 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -551,18 +551,18 @@ void InputEventConfigurationDialog::_input_list_item_selected() { } void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) { - // Option index 0 corresponds to "All Devices" (value of -3). - // Otherwise subtract 1 as option index 1 corresponds to device 0, etc... - event->set_device(p_option_button_index == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : p_option_button_index - 1); + // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) + // and option index 1 corresponds to device 0, etc... + event->set_device(p_option_button_index - 1); event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true)); } void InputEventConfigurationDialog::_set_current_device(int p_device) { - device_id_option->select(p_device == InputEvent::DEVICE_ID_ALL_DEVICES ? 0 : p_device + 1); + device_id_option->select(p_device + 1); } int InputEventConfigurationDialog::_get_current_device() const { - return device_id_option->get_selected() == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : device_id_option->get_selected() - 1; + return device_id_option->get_selected() - 1; } void InputEventConfigurationDialog::_notification(int p_what) { @@ -705,12 +705,11 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { device_id_option = memnew(OptionButton); device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL); - device_id_option->add_item(EventListenerLineEdit::get_device_string(InputEvent::DEVICE_ID_ALL_DEVICES)); - for (int i = 0; i < 8; i++) { + for (int i = -1; i < 8; i++) { device_id_option->add_item(EventListenerLineEdit::get_device_string(i)); } device_id_option->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed)); - _set_current_device(InputEvent::DEVICE_ID_ALL_DEVICES); + _set_current_device(InputMap::ALL_DEVICES); device_container->add_child(device_id_option); device_container->hide(); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 6e5dfd44d4..4cff3504f5 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -93,10 +93,14 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { file_dialog->popup_file_dialog(); } break; case LightmapGI::BAKE_ERROR_NO_MESHES: { - EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); + EditorNode::get_singleton()->show_warning( + TTR("No meshes with lightmapping support to bake. Make sure they contain UV2 data and their Global Illumination property is set to Static.") + + String::utf8("\n\n• ") + TTR("To import a scene with lightmapping support, set Meshes > Light Baking to Static Lightmaps in the Import dock.") + + String::utf8("\n• ") + TTR("To enable lightmapping support on a primitive mesh, edit the PrimitiveMesh resource in the inspector and check Add UV2.") + + String::utf8("\n• ") + TTR("To enable lightmapping support on a CSG mesh, select the root CSG node and choose CSG > Bake Mesh Instance at the top of the 3D editor viewport.\nSelect the generated MeshInstance3D node and choose Mesh > Unwrap UV2 for Lightmap/AO at the top of the 3D editor viewport.")); } break; case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: { - EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); + EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure the lightmap destination path is writable.")); } break; case LightmapGI::BAKE_ERROR_NO_SCENE_ROOT: { EditorNode::get_singleton()->show_warning(TTR("No editor scene root found.")); @@ -108,7 +112,7 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("Maximum texture size is too small for the lightmap images.\nWhile this can be fixed by increasing the maximum texture size, it is recommended you split the scene into more objects instead.")); } break; case LightmapGI::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { - EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes selected to bake have `lightmap_size_hint` value set high enough, and `texel_scale` value of LightmapGI is not too low.")); + EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes to bake have the Lightmap Size Hint property set high enough, and the LightmapGI's Texel Scale value is not too low.")); } break; case LightmapGI::BAKE_ERROR_ATLAS_TOO_SMALL: { EditorNode::get_singleton()->show_warning(TTR("Failed fitting a lightmap image into an atlas. This should never happen and should be reported.")); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 8249596045..987002f472 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -7026,7 +7026,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("NodePositionView", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_view", "NODE_POSITION_VIEW"), { "node_position_view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("NodePositionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_world", "NODE_POSITION_WORLD"), { "node_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("PointCoord", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ScreenUV", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ScreenUV", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Tangent", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "tangent", "TANGENT"), { "tangent" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Vertex", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("View", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); @@ -7044,6 +7044,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("LightIsDirectional", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_is_directional", "LIGHT_IS_DIRECTIONAL"), { "light_is_directional" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Metallic", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "metallic", "METALLIC"), { "metallic" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Roughness", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "roughness", "ROUGHNESS"), { "roughness" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ScreenUV", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Specular", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "specular", "SPECULAR_LIGHT"), { "specular" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("View", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); diff --git a/methods.py b/methods.py index 1c33b00051..203f0dd8a5 100644 --- a/methods.py +++ b/methods.py @@ -863,16 +863,21 @@ def clean_cache(cache_path: str, cache_limit: int, verbose: bool): texts = [] stats = [] for file in files: - # Failing a utf-8 decode is the easiest way to determine if a file is binary. try: - with open(file, encoding="utf-8") as out: - out.read(1024) - except UnicodeDecodeError: - stats.append((file, *os.stat(file)[6:8])) + # Save file stats to rewrite after modifying. + tmp_stat = os.stat(file) + # Failing a utf-8 decode is the easiest way to determine if a file is binary. + try: + with open(file, encoding="utf-8") as out: + out.read(1024) + except UnicodeDecodeError: + stats.append((file, *tmp_stat[6:8])) + # Restore file stats after reading. + os.utime(file, (tmp_stat[7], tmp_stat[8])) + else: + texts.append(file) except OSError: print_error(f'Failed to access cache file "{file}"; skipping.') - else: - texts.append(file) if texts: count = len(texts) @@ -1012,6 +1017,30 @@ def generate_vs_project(env, original_args, project_name="godot"): return v[0] if len(v) == 1 else f"{v[0]}={v[1]}" return v + def get_dependencies(file, env, exts, headers, sources, others): + for child in file.children(): + if isinstance(child, str): + child = env.File(x) + fname = "" + try: + fname = child.path + except AttributeError: + # It's not a file. + pass + + if fname: + parts = os.path.splitext(fname) + if len(parts) > 1: + ext = parts[1].lower() + if ext in exts["sources"]: + sources += [fname] + elif ext in exts["headers"]: + headers += [fname] + elif ext in exts["others"]: + others += [fname] + + get_dependencies(child, env, exts, headers, sources, others) + filtered_args = original_args.copy() # Ignore the "vsproj" option to not regenerate the VS project on every build @@ -1073,26 +1102,28 @@ def generate_vs_project(env, original_args, project_name="godot"): sys.path.remove(tmppath) sys.modules.pop("msvs") + extensions = {} + extensions["headers"] = [".h", ".hh", ".hpp", ".hxx", ".inc"] + extensions["sources"] = [".c", ".cc", ".cpp", ".cxx", ".m", ".mm", ".java"] + extensions["others"] = [".natvis", ".glsl", ".rc"] + headers = [] headers_dirs = [] - for file in glob_recursive_2("*.h", headers_dirs): - headers.append(str(file).replace("/", "\\")) - for file in glob_recursive_2("*.hpp", headers_dirs): - headers.append(str(file).replace("/", "\\")) + for ext in extensions["headers"]: + for file in glob_recursive_2("*" + ext, headers_dirs): + headers.append(str(file).replace("/", "\\")) sources = [] sources_dirs = [] - for file in glob_recursive_2("*.cpp", sources_dirs): - sources.append(str(file).replace("/", "\\")) - for file in glob_recursive_2("*.c", sources_dirs): - sources.append(str(file).replace("/", "\\")) + for ext in extensions["sources"]: + for file in glob_recursive_2("*" + ext, sources_dirs): + sources.append(str(file).replace("/", "\\")) others = [] others_dirs = [] - for file in glob_recursive_2("*.natvis", others_dirs): - others.append(str(file).replace("/", "\\")) - for file in glob_recursive_2("*.glsl", others_dirs): - others.append(str(file).replace("/", "\\")) + for ext in extensions["others"]: + for file in glob_recursive_2("*" + ext, others_dirs): + others.append(str(file).replace("/", "\\")) skip_filters = False import hashlib @@ -1156,58 +1187,13 @@ def generate_vs_project(env, original_args, project_name="godot"): with open(f"{project_name}.vcxproj.filters", "w", encoding="utf-8", newline="\r\n") as f: f.write(filters_template) - envsources = [] - - envsources += env.core_sources - envsources += env.drivers_sources - envsources += env.main_sources - envsources += env.modules_sources - envsources += env.scene_sources - envsources += env.servers_sources - if env.editor_build: - envsources += env.editor_sources - envsources += env.platform_sources - headers_active = [] sources_active = [] others_active = [] - for x in envsources: - fname = "" - if isinstance(x, str): - fname = env.File(x).path - else: - # Some object files might get added directly as a File object and not a list. - try: - fname = env.File(x)[0].path - except Exception: - fname = x.path - pass - if fname: - fname = fname.replace("\\\\", "/") - parts = os.path.splitext(fname) - basename = parts[0] - ext = parts[1] - idx = fname.find(env["OBJSUFFIX"]) - if ext in [".h", ".hpp"]: - headers_active += [fname] - elif ext in [".c", ".cpp"]: - sources_active += [fname] - elif idx > 0: - basename = fname[:idx] - if os.path.isfile(basename + ".h"): - headers_active += [basename + ".h"] - elif os.path.isfile(basename + ".hpp"): - headers_active += [basename + ".hpp"] - elif basename.endswith(".gen") and os.path.isfile(basename[:-4] + ".h"): - headers_active += [basename[:-4] + ".h"] - if os.path.isfile(basename + ".c"): - sources_active += [basename + ".c"] - elif os.path.isfile(basename + ".cpp"): - sources_active += [basename + ".cpp"] - else: - fname = os.path.relpath(os.path.abspath(fname), env.Dir("").abspath) - others_active += [fname] + get_dependencies( + env.File(f"#bin/godot{env['PROGSUFFIX']}"), env, extensions, headers_active, sources_active, others_active + ) all_items = [] properties = [] diff --git a/modules/SCsub b/modules/SCsub index fea2f2eeb8..09944241ea 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -63,7 +63,6 @@ register_module_types = env.CommandNoCache( ) -vs_sources = [] test_headers = [] # libmodule_<name>.a for each active module. for name, path in env.module_list.items(): @@ -75,8 +74,6 @@ for name, path in env.module_list.items(): lib = env_modules.add_library("module_%s" % name, env.modules_sources) env.Prepend(LIBS=[lib]) - if env["vsproj"]: - vs_sources += env.modules_sources if env["tests"]: # Lookup potential headers in `tests` subfolder. @@ -104,5 +101,3 @@ env.modules_sources = [] env_modules.add_source_files(env.modules_sources, register_module_types) lib = env_modules.add_library("modules", env.modules_sources) env.Prepend(LIBS=[lib]) -if env["vsproj"]: - env.modules_sources += vs_sources diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index b77c641eb5..fb4d27caab 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -585,8 +585,25 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va } void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { - // Avoid validated evaluator for modulo and division when operands are int, since there's no check for division by zero. - if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand) && ((p_operator != Variant::OP_DIVIDE && p_operator != Variant::OP_MODULE) || p_left_operand.type.builtin_type != Variant::INT || p_right_operand.type.builtin_type != Variant::INT)) { + bool valid = HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand); + + // Avoid validated evaluator for modulo and division when operands are int or integer vector, since there's no check for division by zero. + if (valid && (p_operator == Variant::OP_DIVIDE || p_operator == Variant::OP_MODULE)) { + switch (p_left_operand.type.builtin_type) { + case Variant::INT: + valid = p_right_operand.type.builtin_type != Variant::INT; + break; + case Variant::VECTOR2I: + case Variant::VECTOR3I: + case Variant::VECTOR4I: + valid = p_right_operand.type.builtin_type != Variant::INT && p_right_operand.type.builtin_type != p_left_operand.type.builtin_type; + break; + default: + break; + } + } + + if (valid) { if (p_target.mode == Address::TEMPORARY) { Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); Variant::Type temp_type = temporaries[p_target.address].type; diff --git a/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd new file mode 100644 index 0000000000..ace5397f40 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.gd @@ -0,0 +1,3 @@ +func test(): + var integer: int = 1 + integer /= 0 diff --git a/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out new file mode 100644 index 0000000000..6a9d11cd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/division_by_zero.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/division_by_zero.gd +>> 3 +>> Division by zero error in operator '/'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd new file mode 100644 index 0000000000..99792e4e32 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.gd @@ -0,0 +1,3 @@ +func test(): + var integer: int = 1 + integer %= 0 diff --git a/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out new file mode 100644 index 0000000000..79c512888f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/modulo_by_zero.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/modulo_by_zero.gd +>> 3 +>> Modulo by zero error in operator '%'. diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 99e3b02dea..0d522a0562 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1351,7 +1351,6 @@ GridMapEditor::GridMapEditor() { callable_mp(this, &GridMapEditor::_on_tool_mode_changed).unbind(1)); mode_buttons->add_child(select_mode_button); viewport_shortcut_buttons.push_back(select_mode_button); - select_mode_button->set_pressed(true); erase_mode_button = memnew(Button); erase_mode_button->set_theme_type_variation("FlatButton"); @@ -1724,6 +1723,10 @@ bool GridMapEditorPlugin::handles(Object *p_object) const { void GridMapEditorPlugin::make_visible(bool p_visible) { if (p_visible) { + BaseButton *button = grid_map_editor->mode_buttons_group->get_pressed_button(); + if (button == nullptr) { + grid_map_editor->select_mode_button->set_pressed(true); + } grid_map_editor->_on_tool_mode_changed(); panel_button->show(); EditorNode::get_bottom_panel()->make_item_visible(grid_map_editor); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs index 6425c723dd..fe511d67ef 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ExportDiagnosticsTests.cs @@ -74,4 +74,31 @@ public class ExportDiagnosticsTests } ); } + + [Fact] + public async void ExportToolButtonInNonToolClass() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0108.cs" }, + new string[] { "ExportDiagnostics_GD0108_ScriptProperties.generated.cs" } + ); + } + + [Fact] + public async void ExportAndExportToolButtonOnSameMember() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0109.cs" }, + new string[] { "ExportDiagnostics_GD0109_ScriptProperties.generated.cs" } + ); + } + + [Fact] + public async void ExportToolButtonOnNonCallable() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + new string[] { "ExportDiagnostics_GD0110.cs" }, + new string[] { "ExportDiagnostics_GD0110_ScriptProperties.generated.cs" } + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs index 724fb164e0..9693cba9ce 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/ScriptPropertiesGeneratorTests.cs @@ -66,4 +66,13 @@ public class ScriptPropertiesGeneratorTests "AbstractGenericNode(Of T)_ScriptProperties.generated.cs" ); } + + [Fact] + public async void ExportedButtons() + { + await CSharpSourceGeneratorVerifier<ScriptPropertiesGenerator>.Verify( + "ExportedToolButtons.cs", + "ExportedToolButtons_ScriptProperties.generated.cs" + ); + } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs new file mode 100644 index 0000000000..77114a7c93 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0108_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0108 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs new file mode 100644 index 0000000000..fc4547f2c1 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0109_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0109 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<global::Godot.Callable>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs new file mode 100644 index 0000000000..d1aac17557 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0110_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportDiagnostics_GD0110 +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.Node.PropertyName { + /// <summary> + /// Cached name for the 'MyButton' field. + /// </summary> + public new static readonly global::Godot.StringName @MyButton = "MyButton"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + if (name == PropertyName.@MyButton) { + this.@MyButton = global::Godot.NativeInterop.VariantUtils.ConvertTo<string>(value); + return true; + } + return base.SetGodotClassPropertyValue(name, value); + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<string>(this.@MyButton); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs new file mode 100644 index 0000000000..6e4ad1f13e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedToolButtons_ScriptProperties.generated.cs @@ -0,0 +1,48 @@ +using Godot; +using Godot.NativeInterop; + +partial class ExportedToolButtons +{ +#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword + /// <summary> + /// Cached StringNames for the properties and fields contained in this class, for fast lookup. + /// </summary> + public new class PropertyName : global::Godot.GodotObject.PropertyName { + /// <summary> + /// Cached name for the 'MyButton1' property. + /// </summary> + public new static readonly global::Godot.StringName @MyButton1 = "MyButton1"; + /// <summary> + /// Cached name for the 'MyButton2' property. + /// </summary> + public new static readonly global::Godot.StringName @MyButton2 = "MyButton2"; + } + /// <inheritdoc/> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + protected override bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + if (name == PropertyName.@MyButton1) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton1); + return true; + } + if (name == PropertyName.@MyButton2) { + value = global::Godot.NativeInterop.VariantUtils.CreateFrom<global::Godot.Callable>(this.@MyButton2); + return true; + } + return base.GetGodotClassPropertyValue(name, out value); + } + /// <summary> + /// Get the property information for all the properties declared in this class. + /// This method is used by Godot to register the available properties in the editor. + /// Do not call this method. + /// </summary> + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new static global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo> GetGodotPropertyList() + { + var properties = new global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>(); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton1, hint: (global::Godot.PropertyHint)39, hintString: "Click me!", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)25, name: PropertyName.@MyButton2, hint: (global::Godot.PropertyHint)39, hintString: "Click me!,ColorRect", usage: (global::Godot.PropertyUsageFlags)4, exported: true)); + return properties; + } +#pragma warning restore CS0109 +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs new file mode 100644 index 0000000000..6fde2e5957 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0108.cs @@ -0,0 +1,8 @@ +using Godot; +using Godot.Collections; + +public partial class ExportDiagnostics_GD0108 : Node +{ + [ExportToolButton("")] + public Callable {|GD0108:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs new file mode 100644 index 0000000000..80a04cd641 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0109.cs @@ -0,0 +1,9 @@ +using Godot; +using Godot.Collections; + +[Tool] +public partial class ExportDiagnostics_GD0109 : Node +{ + [Export, ExportToolButton("")] + public Callable {|GD0109:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs new file mode 100644 index 0000000000..586bf6c19e --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0110.cs @@ -0,0 +1,9 @@ +using Godot; +using Godot.Collections; + +[Tool] +public partial class ExportDiagnostics_GD0110 : Node +{ + [ExportToolButton("")] + public string {|GD0110:MyButton|}; +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs new file mode 100644 index 0000000000..91c14387c5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportedToolButtons.cs @@ -0,0 +1,12 @@ +using Godot; +using System; + +[Tool] +public partial class ExportedToolButtons : GodotObject +{ + [ExportToolButton("Click me!")] + public Callable MyButton1 => Callable.From(() => { GD.Print("Clicked MyButton1!"); }); + + [ExportToolButton("Click me!", Icon = "ColorRect")] + public Callable MyButton2 => Callable.From(() => { GD.Print("Clicked MyButton2!"); }); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md index 7286853f7a..90c8491177 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -3,3 +3,6 @@ Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0003.html) +GD0108 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0108.html) +GD0109 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0109.html) +GD0110 | Usage | Error | ScriptPropertiesGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0110.html) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index ad7962e7df..8da0818e42 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -107,6 +107,36 @@ namespace Godot.SourceGenerators "Types not derived from Node should not export Node members. Node export is only supported in Node-derived classes.", helpLinkUri: string.Format(_helpLinkFormat, "GD0107")); + public static readonly DiagnosticDescriptor OnlyToolClassesShouldUseExportToolButtonRule = + new DiagnosticDescriptor(id: "GD0108", + title: "The exported tool button is not in a tool class", + messageFormat: "The exported tool button '{0}' is not in a tool class", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported tool button is not in a tool class. Annotate the class with the '[Tool]' attribute, or remove the '[ExportToolButton]' attribute.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0108")); + + public static readonly DiagnosticDescriptor ExportToolButtonShouldNotBeUsedWithExportRule = + new DiagnosticDescriptor(id: "GD0109", + title: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute", + messageFormat: "The '[ExportToolButton]' attribute cannot be used with another '[Export]' attribute on '{0}'", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The '[ExportToolButton]' attribute cannot be used with the '[Export]' attribute. Remove one of the attributes.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0109")); + + public static readonly DiagnosticDescriptor ExportToolButtonIsNotCallableRule = + new DiagnosticDescriptor(id: "GD0110", + title: "The exported tool button is not a Callable", + messageFormat: "The exported tool button '{0}' is not a Callable", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The exported tool button is not a Callable. The '[ExportToolButton]' attribute is only supported on members of type Callable.", + helpLinkUri: string.Format(_helpLinkFormat, "GD0110")); + public static readonly DiagnosticDescriptor SignalDelegateMissingSuffixRule = new DiagnosticDescriptor(id: "GD0201", title: "The name of the delegate must end with 'EventHandler'", diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 62fa7b0a36..46c446169a 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -287,6 +287,12 @@ namespace Godot.SourceGenerators public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol) => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr; + public static bool IsGodotExportToolButtonAttribute(this INamedTypeSymbol symbol) + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportToolButtonAttr; + + public static bool IsGodotToolAttribute(this INamedTypeSymbol symbol) + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ToolAttr; + public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol) => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index af39a54b0b..f6de09e25b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -9,10 +9,12 @@ namespace Godot.SourceGenerators public const string ExportCategoryAttr = "Godot.ExportCategoryAttribute"; public const string ExportGroupAttr = "Godot.ExportGroupAttribute"; public const string ExportSubgroupAttr = "Godot.ExportSubgroupAttribute"; + public const string ExportToolButtonAttr = "Godot.ExportToolButtonAttribute"; public const string SignalAttr = "Godot.SignalAttribute"; public const string MustBeVariantAttr = "Godot.MustBeVariantAttribute"; public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute"; public const string GlobalClassAttr = "Godot.GlobalClassAttribute"; + public const string ToolAttr = "Godot.ToolAttribute"; public const string SystemFlagsAttr = "System.FlagsAttribute"; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index bb4c4824e7..d77ebc3cec 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -89,7 +89,8 @@ namespace Godot.SourceGenerators Password = 36, LayersAvoidance = 37, DictionaryType = 38, - Max = 39 + ToolButton = 39, + Max = 40 } [Flags] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index f192bf0fb7..ed78353f92 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -69,6 +69,7 @@ namespace Godot.SourceGenerators bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; + bool isToolClass = symbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotToolAttribute() ?? false); string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptProperties.generated"; @@ -277,6 +278,16 @@ namespace Godot.SourceGenerators if (propertyInfo == null) continue; + if (propertyInfo.Value.Hint == PropertyHint.ToolButton && !isToolClass) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyToolClassesShouldUseExportToolButtonRule, + member.Symbol.Locations.FirstLocationWithSourceTreeOrDefault(), + member.Symbol.ToDisplayString() + )); + continue; + } + AppendPropertyInfo(source, propertyInfo.Value); } @@ -418,6 +429,19 @@ namespace Godot.SourceGenerators var exportAttr = memberSymbol.GetAttributes() .FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false); + var exportToolButtonAttr = memberSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotExportToolButtonAttribute() ?? false); + + if (exportAttr != null && exportToolButtonAttr != null) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonShouldNotBeUsedWithExportRule, + memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + memberSymbol.ToDisplayString() + )); + return null; + } + var propertySymbol = memberSymbol as IPropertySymbol; var fieldSymbol = memberSymbol as IFieldSymbol; @@ -431,19 +455,56 @@ namespace Godot.SourceGenerators } } + if (exportToolButtonAttr != null && propertySymbol != null && propertySymbol.GetMethod == null) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportedPropertyIsWriteOnlyRule, + propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + propertySymbol.ToDisplayString() + )); + return null; + } + var memberType = propertySymbol?.Type ?? fieldSymbol!.Type; var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; string memberName = memberSymbol.Name; + string? hintString = null; + + if (exportToolButtonAttr != null) + { + if (memberVariantType != VariantType.Callable) + { + context.ReportDiagnostic(Diagnostic.Create( + Common.ExportToolButtonIsNotCallableRule, + memberSymbol.Locations.FirstLocationWithSourceTreeOrDefault(), + memberSymbol.ToDisplayString() + )); + return null; + } + + hintString = exportToolButtonAttr.ConstructorArguments[0].Value?.ToString() ?? ""; + foreach (var namedArgument in exportToolButtonAttr.NamedArguments) + { + if (namedArgument is { Key: "Icon", Value.Value: string { Length: > 0 } }) + { + hintString += $",{namedArgument.Value.Value}"; + } + } + + return new PropertyInfo(memberVariantType, memberName, PropertyHint.ToolButton, + hintString: hintString, PropertyUsageFlags.Editor, exported: true); + } + if (exportAttr == null) { return new PropertyInfo(memberVariantType, memberName, PropertyHint.None, - hintString: null, PropertyUsageFlags.ScriptVariable, exported: false); + hintString: hintString, PropertyUsageFlags.ScriptVariable, exported: false); } if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType, - isTypeArgument: false, out var hint, out var hintString)) + isTypeArgument: false, out var hint, out hintString)) { var constructorArguments = exportAttr.ConstructorArguments; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs new file mode 100644 index 0000000000..87a9e628f9 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportToolButtonAttribute.cs @@ -0,0 +1,33 @@ +using System; + +#nullable enable + +namespace Godot +{ + /// <summary> + /// Exports the annotated <see cref="Callable"/> as a clickable button. + /// </summary> + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public sealed class ExportToolButtonAttribute : Attribute + { + /// <summary> + /// The label of the button. + /// </summary> + public string Text { get; } + + /// <summary> + /// If defined, used to fetch an icon for the button via <see cref="Control.GetThemeIcon"/>, + /// from the <code>EditorIcons</code> theme type. + /// </summary> + public string? Icon { get; init; } + + /// <summary> + /// Exports the annotated <see cref="Callable"/> as a clickable button. + /// </summary> + /// <param name="text">The label of the button.</param> + public ExportToolButtonAttribute(string text) + { + Text = text; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 3a3134d160..5aa68559d8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -48,6 +48,7 @@ <!-- Sources --> <ItemGroup> <Compile Include="Core\Aabb.cs" /> + <Compile Include="Core\Attributes\ExportToolButtonAttribute.cs" /> <Compile Include="Core\Bridge\GodotSerializationInfo.cs" /> <Compile Include="Core\Bridge\MethodInfo.cs" /> <Compile Include="Core\Callable.generics.cs" /> diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp index 682d20022f..69a7545e51 100644 --- a/modules/multiplayer/multiplayer_spawner.cpp +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -97,7 +97,13 @@ PackedStringArray MultiplayerSpawner::get_configuration_warnings() const { void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { SpawnableScene sc; - sc.path = p_path; + if (p_path.begins_with("uid://")) { + sc.uid = p_path; + sc.path = ResourceUID::uid_to_path(p_path); + } else { + sc.uid = ResourceUID::path_to_uid(p_path); + sc.path = p_path; + } if (Engine::get_singleton()->is_editor_hint()) { ERR_FAIL_COND(!ResourceLoader::exists(p_path)); } @@ -139,7 +145,7 @@ Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const { Vector<String> ss; ss.resize(spawnable_scenes.size()); for (int i = 0; i < ss.size(); i++) { - ss.write[i] = spawnable_scenes[i].path; + ss.write[i] = spawnable_scenes[i].uid; } return ss; } diff --git a/modules/multiplayer/multiplayer_spawner.h b/modules/multiplayer/multiplayer_spawner.h index 0e94b781ea..868efb9e0b 100644 --- a/modules/multiplayer/multiplayer_spawner.h +++ b/modules/multiplayer/multiplayer_spawner.h @@ -49,6 +49,7 @@ public: private: struct SpawnableScene { String path; + String uid; Ref<PackedScene> cache; }; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 0b506e60d6..e20de99c2d 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -790,9 +790,10 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { APKExportData *ed = static_cast<APKExportData *>(p_userdata); - String dst_path = p_path.replace_first("res://", "assets/"); + const String path = ResourceUID::ensure_path(p_path); + const String dst_path = path.replace_first("res://", "assets/"); - store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0); + store_in_apk(ed, dst_path, p_data, _should_compress_asset(path, p_data) ? Z_DEFLATED : 0); return OK; } diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 3603565805..433888581f 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -171,8 +171,9 @@ Error store_string_at_path(const String &p_path, const String &p_data) { // This method will be called ONLY when gradle build is enabled. Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) { CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata); - String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/"); - print_verbose("Saving project files from " + p_path + " into " + dst_path); + const String path = ResourceUID::ensure_path(p_path); + const String dst_path = path.replace_first("res://", export_data->assets_directory + "/"); + print_verbose("Saving project files from " + path + " into " + dst_path); Error err = store_file_at_path(dst_path, p_data); return err; } diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index f551cb401c..14bc22a217 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -87,17 +87,9 @@ bool MeshInstance3D::_get(const StringName &p_name, Variant &r_ret) const { } void MeshInstance3D::_get_property_list(List<PropertyInfo> *p_list) const { - List<String> ls; - for (const KeyValue<StringName, int> &E : blend_shape_properties) { - ls.push_back(E.key); - } - - ls.sort(); - - for (const String &E : ls) { - p_list->push_back(PropertyInfo(Variant::FLOAT, E, PROPERTY_HINT_RANGE, "-1,1,0.00001")); + for (uint32_t i = 0; i < blend_shape_tracks.size(); i++) { + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("blend_shapes/%s", String(mesh->get_blend_shape_name(i))), PROPERTY_HINT_RANGE, "-1,1,0.00001")); } - if (mesh.is_valid()) { for (int i = 0; i < mesh->get_surface_count(); i++) { p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d", PNAME("surface_material_override"), i), PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial", PROPERTY_USAGE_DEFAULT)); @@ -142,6 +134,7 @@ int MeshInstance3D::get_blend_shape_count() const { } return mesh->get_blend_shape_count(); } + int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) { if (mesh.is_null()) { return -1; @@ -153,11 +146,13 @@ int MeshInstance3D::find_blend_shape_by_name(const StringName &p_name) { } return -1; } + float MeshInstance3D::get_blend_shape_value(int p_blend_shape) const { ERR_FAIL_COND_V(mesh.is_null(), 0.0); ERR_FAIL_INDEX_V(p_blend_shape, (int)blend_shape_tracks.size(), 0); return blend_shape_tracks[p_blend_shape]; } + void MeshInstance3D::set_blend_shape_value(int p_blend_shape, float p_value) { ERR_FAIL_COND(mesh.is_null()); ERR_FAIL_INDEX(p_blend_shape, (int)blend_shape_tracks.size()); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index e56a9e71ef..2928794551 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -545,6 +545,12 @@ void SpinBox::_set_step_no_signal(double p_step) { set_block_signals(false); } +void SpinBox::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "exp_edit") { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 06a0f622b9..564294649c 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -139,6 +139,7 @@ class SpinBox : public Range { protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; void _value_changed(double p_value) override; + void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index a443ae9abf..775db65c3f 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -246,6 +246,14 @@ bool SubViewportContainer::_is_propagated_in_gui_input(const Ref<InputEvent> &p_ return false; } +void SubViewportContainer::set_consume_drag_and_drop(bool p_enable) { + consume_drag_and_drop = p_enable; +} + +bool SubViewportContainer::is_consume_drag_and_drop_enabled() { + return consume_drag_and_drop; +} + void SubViewportContainer::add_child_notify(Node *p_child) { if (Object::cast_to<SubViewport>(p_child)) { queue_redraw(); @@ -286,8 +294,12 @@ void SubViewportContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stretch_shrink", "amount"), &SubViewportContainer::set_stretch_shrink); ClassDB::bind_method(D_METHOD("get_stretch_shrink"), &SubViewportContainer::get_stretch_shrink); + ClassDB::bind_method(D_METHOD("set_consume_drag_and_drop", "amount"), &SubViewportContainer::set_consume_drag_and_drop); + ClassDB::bind_method(D_METHOD("is_consume_drag_and_drop_enabled"), &SubViewportContainer::is_consume_drag_and_drop_enabled); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), "set_stretch_shrink", "get_stretch_shrink"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "consume_drag_and_drop"), "set_consume_drag_and_drop", "is_consume_drag_and_drop_enabled"); GDVIRTUAL_BIND(_propagate_input_event, "event"); } diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 06420de730..7230615771 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -38,6 +38,8 @@ class SubViewportContainer : public Container { bool stretch = false; int shrink = 1; + bool consume_drag_and_drop = false; + void _notify_viewports(int p_notification); bool _is_propagated_in_gui_input(const Ref<InputEvent> &p_event); void _send_event_to_viewports(const Ref<InputEvent> &p_event); @@ -63,6 +65,9 @@ public: int get_stretch_shrink() const; void recalc_force_viewport_sizes(); + void set_consume_drag_and_drop(bool p_enable); + bool is_consume_drag_and_drop_enabled(); + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index e70407f36e..a0f39462a0 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1931,21 +1931,19 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { } } - // If the tooltip timer isn't running, start it. - // Otherwise, only reset the timer if the mouse has moved more than 5 pixels. - if (!is_tooltip_shown && over->can_process() && - (gui.tooltip_timer.is_null() || - Math::is_zero_approx(gui.tooltip_timer->get_time_left()) || - mm->get_relative().length() > 5.0)) { - if (gui.tooltip_timer.is_valid()) { - gui.tooltip_timer->release_connections(); - gui.tooltip_timer = Ref<SceneTreeTimer>(); + // Reset the timer if the mouse has moved more than 5 pixels or has entered a new control. + if (!is_tooltip_shown && over->can_process()) { + Vector2 new_tooltip_pos = over->get_screen_transform().xform(pos); + if (over != gui.tooltip_control || gui.tooltip_pos.distance_squared_to(new_tooltip_pos) > 25) { + if (gui.tooltip_timer.is_valid()) { + gui.tooltip_timer->release_connections(); + } + gui.tooltip_control = over; + gui.tooltip_pos = new_tooltip_pos; + gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); + gui.tooltip_timer->set_ignore_time_scale(true); + gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } - gui.tooltip_control = over; - gui.tooltip_pos = over->get_screen_transform().xform(pos); - gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay); - gui.tooltip_timer->set_ignore_time_scale(true); - gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip)); } } @@ -3062,6 +3060,14 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { } v->_update_mouse_over(v->get_final_transform().affine_inverse().xform(pos)); } + + Viewport *section_root = get_section_root_viewport(); + if (section_root && c->is_consume_drag_and_drop_enabled()) { + // Evaluating `consume_drag_and_drop` and adjusting target_control needs to happen + // after `_update_mouse_over` in the SubViewports, because otherwise physics picking + // would not work inside SubViewports. + section_root->gui.target_control = over; + } } } diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 847867fa4d..7c1adeac96 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -3167,6 +3167,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_BOOLEAN, "output_is_srgb", "OUTPUT_IS_SRGB" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_TRANSFORM, "projection_matrix", "PROJECTION_MATRIX" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "specular", "SPECULAR_LIGHT" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 63956a7918..83ec52ea75 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -972,26 +972,26 @@ SkyRD::~SkyRD() { } } -void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, const PagedArray<RID> &p_lights, RID p_camera_attributes, uint32_t p_view_count, const Projection *p_view_projections, const Vector3 *p_view_eye_offsets, const Transform3D &p_cam_transform, const Projection &p_cam_projection, const Size2i p_screen_size, Vector2 p_jitter, RendererSceneRenderRD *p_scene_render) { +void SkyRD::setup_sky(const RenderDataRD *p_render_data, const Size2i p_screen_size) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton(); - ERR_FAIL_COND(p_env.is_null()); + ERR_FAIL_COND(p_render_data->environment.is_null()); - ERR_FAIL_COND(p_render_buffers.is_null()); + ERR_FAIL_COND(p_render_data->render_buffers.is_null()); // make sure we support our view count - ERR_FAIL_COND(p_view_count == 0); - ERR_FAIL_COND(p_view_count > RendererSceneRender::MAX_RENDER_VIEWS); + ERR_FAIL_COND(p_render_data->scene_data->view_count == 0); + ERR_FAIL_COND(p_render_data->scene_data->view_count > RendererSceneRender::MAX_RENDER_VIEWS); SkyMaterialData *material = nullptr; - Sky *sky = get_sky(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_env)); + Sky *sky = get_sky(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_render_data->environment)); RID sky_material; SkyShaderData *shader_data = nullptr; if (sky) { - sky_material = sky_get_material(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_env)); + sky_material = sky_get_material(RendererSceneRenderRD::get_singleton()->environment_get_sky(p_render_data->environment)); if (sky_material.is_valid()) { material = static_cast<SkyMaterialData *>(material_storage->material_get_data(sky_material, RendererRD::MaterialStorage::SHADER_TYPE_SKY)); @@ -1025,8 +1025,8 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con update_dirty_skys(); } - if (shader_data->uses_time && p_scene_render->time - sky->prev_time > 0.00001) { - sky->prev_time = p_scene_render->time; + if (shader_data->uses_time && p_render_data->scene_data->time - sky->prev_time > 0.00001) { + sky->prev_time = p_render_data->scene_data->time; sky->reflection.dirty = true; RenderingServerDefault::redraw_request(); } @@ -1041,29 +1041,30 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con sky->reflection.dirty = true; } - if (!p_cam_transform.origin.is_equal_approx(sky->prev_position) && shader_data->uses_position) { - sky->prev_position = p_cam_transform.origin; + if (!p_render_data->scene_data->cam_transform.origin.is_equal_approx(sky->prev_position) && shader_data->uses_position) { + sky->prev_position = p_render_data->scene_data->cam_transform.origin; sky->reflection.dirty = true; } } sky_scene_state.ubo.directional_light_count = 0; if (shader_data->uses_light) { + const PagedArray<RID> &lights = *p_render_data->lights; // Run through the list of lights in the scene and pick out the Directional Lights. // This can't be done in RenderSceneRenderRD::_setup lights because that needs to be called // after the depth prepass, but this runs before the depth prepass. - for (int i = 0; i < (int)p_lights.size(); i++) { - if (!light_storage->owns_light_instance(p_lights[i])) { + for (int i = 0; i < (int)lights.size(); i++) { + if (!light_storage->owns_light_instance(lights[i])) { continue; } - RID base = light_storage->light_instance_get_base_light(p_lights[i]); + RID base = light_storage->light_instance_get_base_light(lights[i]); ERR_CONTINUE(base.is_null()); RS::LightType type = light_storage->light_get_type(base); if (type == RS::LIGHT_DIRECTIONAL && light_storage->light_directional_get_sky_mode(base) != RS::LIGHT_DIRECTIONAL_SKY_MODE_LIGHT_ONLY) { SkyDirectionalLightData &sky_light_data = sky_scene_state.directional_lights[sky_scene_state.ubo.directional_light_count]; - Transform3D light_transform = light_storage->light_instance_get_base_transform(p_lights[i]); + Transform3D light_transform = light_storage->light_instance_get_base_transform(lights[i]); Vector3 world_direction = light_transform.basis.xform(Vector3(0, 0, 1)).normalized(); sky_light_data.direction[0] = world_direction.x; @@ -1073,12 +1074,12 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con float sign = light_storage->light_is_negative(base) ? -1 : 1; sky_light_data.energy = sign * light_storage->light_get_param(base, RS::LIGHT_PARAM_ENERGY); - if (p_scene_render->is_using_physical_light_units()) { + if (RendererSceneRenderRD::get_singleton()->is_using_physical_light_units()) { sky_light_data.energy *= light_storage->light_get_param(base, RS::LIGHT_PARAM_INTENSITY); } - if (p_camera_attributes.is_valid()) { - sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_camera_attributes); + if (p_render_data->camera_attributes.is_valid()) { + sky_light_data.energy *= RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes); } Color linear_col = light_storage->light_get_color(base).srgb_to_linear(); @@ -1149,43 +1150,48 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con // Setup fog variables. sky_scene_state.ubo.volumetric_fog_enabled = false; - if (p_render_buffers.is_valid()) { - if (p_render_buffers->has_custom_data(RB_SCOPE_FOG)) { - Ref<RendererRD::Fog::VolumetricFog> fog = p_render_buffers->get_custom_data(RB_SCOPE_FOG); - sky_scene_state.ubo.volumetric_fog_enabled = true; - - float fog_end = fog->length; - if (fog_end > 0.0) { - sky_scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; - } else { - sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; - } + if (p_render_data->render_buffers->has_custom_data(RB_SCOPE_FOG)) { + Ref<RendererRD::Fog::VolumetricFog> fog = p_render_data->render_buffers->get_custom_data(RB_SCOPE_FOG); + sky_scene_state.ubo.volumetric_fog_enabled = true; - float fog_detail_spread = fog->spread; // Reverse lookup. - if (fog_detail_spread > 0.0) { - sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; - } else { - sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0; - } + float fog_end = fog->length; + if (fog_end > 0.0) { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0 / fog_end; + } else { + sky_scene_state.ubo.volumetric_fog_inv_length = 1.0; + } - sky_scene_state.fog_uniform_set = fog->sky_uniform_set; + float fog_detail_spread = fog->spread; // Reverse lookup. + if (fog_detail_spread > 0.0) { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0 / fog_detail_spread; + } else { + sky_scene_state.ubo.volumetric_fog_detail_spread = 1.0; } + + sky_scene_state.fog_uniform_set = fog->sky_uniform_set; } + sky_scene_state.view_count = p_render_data->scene_data->view_count; + sky_scene_state.cam_transform = p_render_data->scene_data->cam_transform; + Projection correction; - correction.set_depth_correction(false, true); - correction.add_jitter_offset(p_jitter); + correction.set_depth_correction(p_render_data->scene_data->flip_y, true); + correction.add_jitter_offset(p_render_data->scene_data->taa_jitter); + + Projection projection = p_render_data->scene_data->cam_projection; + if (p_render_data->scene_data->cam_frustum) { + // We don't use a full projection matrix for the sky, this is enough to make up for it. + projection[2].y = -projection[2].y; + } - sky_scene_state.view_count = p_view_count; - sky_scene_state.cam_transform = p_cam_transform; - sky_scene_state.cam_projection = correction * p_cam_projection; // We only use this when rendering a single view. + sky_scene_state.cam_projection = correction * projection; // Our info in our UBO is only used if we're rendering stereo. - for (uint32_t i = 0; i < p_view_count; i++) { - Projection view_inv_projection = (correction * p_view_projections[i]).inverse(); - if (p_view_count > 1) { + for (uint32_t i = 0; i < p_render_data->scene_data->view_count; i++) { + Projection view_inv_projection = (correction * p_render_data->scene_data->view_projection[i]).inverse(); + if (p_render_data->scene_data->view_count > 1) { // Reprojection is used when we need to have things in combined space. - RendererRD::MaterialStorage::store_camera(p_cam_projection * view_inv_projection, sky_scene_state.ubo.combined_reprojection[i]); + RendererRD::MaterialStorage::store_camera(p_render_data->scene_data->cam_projection * view_inv_projection, sky_scene_state.ubo.combined_reprojection[i]); } else { // This is unused so just reset to identity. Projection ident; @@ -1193,25 +1199,25 @@ void SkyRD::setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, con } RendererRD::MaterialStorage::store_camera(view_inv_projection, sky_scene_state.ubo.view_inv_projections[i]); - sky_scene_state.ubo.view_eye_offsets[i][0] = p_view_eye_offsets[i].x; - sky_scene_state.ubo.view_eye_offsets[i][1] = p_view_eye_offsets[i].y; - sky_scene_state.ubo.view_eye_offsets[i][2] = p_view_eye_offsets[i].z; + sky_scene_state.ubo.view_eye_offsets[i][0] = p_render_data->scene_data->view_eye_offset[i].x; + sky_scene_state.ubo.view_eye_offsets[i][1] = p_render_data->scene_data->view_eye_offset[i].y; + sky_scene_state.ubo.view_eye_offsets[i][2] = p_render_data->scene_data->view_eye_offset[i].z; sky_scene_state.ubo.view_eye_offsets[i][3] = 0.0; } - sky_scene_state.ubo.z_far = p_view_projections[0].get_z_far(); // Should be the same for all projection. - sky_scene_state.ubo.fog_enabled = RendererSceneRenderRD::get_singleton()->environment_get_fog_enabled(p_env); - sky_scene_state.ubo.fog_density = RendererSceneRenderRD::get_singleton()->environment_get_fog_density(p_env); - sky_scene_state.ubo.fog_aerial_perspective = RendererSceneRenderRD::get_singleton()->environment_get_fog_aerial_perspective(p_env); - Color fog_color = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_color(p_env).srgb_to_linear(); - float fog_energy = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_energy(p_env); + sky_scene_state.ubo.z_far = p_render_data->scene_data->view_projection[0].get_z_far(); // Should be the same for all projection. + sky_scene_state.ubo.fog_enabled = RendererSceneRenderRD::get_singleton()->environment_get_fog_enabled(p_render_data->environment); + sky_scene_state.ubo.fog_density = RendererSceneRenderRD::get_singleton()->environment_get_fog_density(p_render_data->environment); + sky_scene_state.ubo.fog_aerial_perspective = RendererSceneRenderRD::get_singleton()->environment_get_fog_aerial_perspective(p_render_data->environment); + Color fog_color = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_color(p_render_data->environment).srgb_to_linear(); + float fog_energy = RendererSceneRenderRD::get_singleton()->environment_get_fog_light_energy(p_render_data->environment); sky_scene_state.ubo.fog_light_color[0] = fog_color.r * fog_energy; sky_scene_state.ubo.fog_light_color[1] = fog_color.g * fog_energy; sky_scene_state.ubo.fog_light_color[2] = fog_color.b * fog_energy; - sky_scene_state.ubo.fog_sun_scatter = RendererSceneRenderRD::get_singleton()->environment_get_fog_sun_scatter(p_env); + sky_scene_state.ubo.fog_sun_scatter = RendererSceneRenderRD::get_singleton()->environment_get_fog_sun_scatter(p_render_data->environment); - sky_scene_state.ubo.fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_fog_sky_affect(p_env); - sky_scene_state.ubo.volumetric_fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_sky_affect(p_env); + sky_scene_state.ubo.fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_fog_sky_affect(p_render_data->environment); + sky_scene_state.ubo.volumetric_fog_sky_affect = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_sky_affect(p_render_data->environment); RD::get_singleton()->buffer_update(sky_scene_state.uniform_buffer, 0, sizeof(SkySceneState::UBO), &sky_scene_state.ubo); } @@ -1292,7 +1298,7 @@ void SkyRD::update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, Projection cm; cm.set_perspective(90, 1, 0.01, 10.0); Projection correction; - correction.set_depth_correction(true); + correction.set_depth_correction(false); cm = correction * cm; // Note, we ignore environment_get_sky_orientation here as this is applied when we do our lookup in our scene shader. diff --git a/servers/rendering/renderer_rd/environment/sky.h b/servers/rendering/renderer_rd/environment/sky.h index b146a416f9..cc753efa01 100644 --- a/servers/rendering/renderer_rd/environment/sky.h +++ b/servers/rendering/renderer_rd/environment/sky.h @@ -36,6 +36,7 @@ #include "servers/rendering/renderer_rd/pipeline_cache_rd.h" #include "servers/rendering/renderer_rd/shaders/environment/sky.glsl.gen.h" #include "servers/rendering/renderer_rd/storage_rd/material_storage.h" +#include "servers/rendering/renderer_rd/storage_rd/render_data_rd.h" #include "servers/rendering/renderer_scene_render.h" #include "servers/rendering/rendering_device.h" #include "servers/rendering/shader_compiler.h" @@ -294,7 +295,7 @@ public: void set_texture_format(RD::DataFormat p_texture_format); ~SkyRD(); - void setup_sky(RID p_env, Ref<RenderSceneBuffersRD> p_render_buffers, const PagedArray<RID> &p_lights, RID p_camera_attributes, uint32_t p_view_count, const Projection *p_view_projections, const Vector3 *p_view_eye_offsets, const Transform3D &p_cam_transform, const Projection &p_cam_projection, const Size2i p_screen_size, Vector2 p_jitter, RendererSceneRenderRD *p_scene_render); + void setup_sky(const RenderDataRD *p_render_data, const Size2i p_screen_size); void update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, const Vector3 &p_global_pos, double p_time, float p_luminance_multiplier = 1.0); void update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, double p_time, float p_luminance_multiplier = 1.0); void draw_sky(RD::DrawListID p_draw_list, Ref<RenderSceneBuffersRD> p_render_buffers, RID p_env, RID p_fb, double p_time, float p_luminance_multiplier = 1.0); 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 eb73a9d7e6..b867e844c7 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1957,22 +1957,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co RD::get_singleton()->draw_command_begin_label("Setup Sky"); // Setup our sky render information for this frame/viewport - if (is_reflection_probe) { - Vector3 eye_offset; - Projection correction; - correction.set_depth_correction(true); - Projection projection = correction * p_render_data->scene_data->cam_projection; - - sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this); - } else { - Projection projection = p_render_data->scene_data->cam_projection; - if (p_render_data->scene_data->cam_frustum) { - // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip. - projection[2].y = -projection[2].y; - } - - sky.setup_sky(p_render_data->environment, rb, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this); - } + sky.setup_sky(p_render_data, screen_size); sky_energy_multiplier *= bg_energy_multiplier; 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 c9d5e51753..411f0fe6a4 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -972,23 +972,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RENDER_TIMESTAMP("Setup Sky"); RD::get_singleton()->draw_command_begin_label("Setup Sky"); - // Setup our sky render information for this frame/viewport - if (is_reflection_probe) { - Vector3 eye_offset; - Projection correction; - correction.set_depth_correction(true); - Projection projection = correction * p_render_data->scene_data->cam_projection; - - sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, 1, &projection, &eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, Vector2(0.0f, 0.0f), this); - } else { - Projection projection = p_render_data->scene_data->cam_projection; - if (p_render_data->scene_data->cam_frustum) { - // Sky is drawn upside down, the frustum offset doesn't know the image is upside down so needs a flip. - projection[2].y = -projection[2].y; - } - - sky.setup_sky(p_render_data->environment, p_render_data->render_buffers, *p_render_data->lights, p_render_data->camera_attributes, p_render_data->scene_data->view_count, &projection, p_render_data->scene_data->view_eye_offset, p_render_data->scene_data->cam_transform, projection, screen_size, p_render_data->scene_data->taa_jitter, this); - } + sky.setup_sky(p_render_data, screen_size); sky_energy_multiplier *= bg_energy_multiplier; diff --git a/servers/rendering/renderer_rd/shaders/environment/sky.glsl b/servers/rendering/renderer_rd/shaders/environment/sky.glsl index cc1c40cad1..7bb2e0a539 100644 --- a/servers/rendering/renderer_rd/shaders/environment/sky.glsl +++ b/servers/rendering/renderer_rd/shaders/environment/sky.glsl @@ -189,7 +189,7 @@ void main() { vec3 cube_normal; #ifdef USE_MULTIVIEW // In multiview our projection matrices will contain positional and rotational offsets that we need to properly unproject. - vec4 unproject = vec4(uv_interp.x, -uv_interp.y, 0.0, 1.0); // unproject at the far plane + vec4 unproject = vec4(uv_interp.x, uv_interp.y, 0.0, 1.0); // unproject at the far plane vec4 unprojected = sky_scene_data.view_inv_projections[ViewIndex] * unproject; cube_normal = unprojected.xyz / unprojected.w; @@ -198,7 +198,7 @@ void main() { #else cube_normal.z = -1.0; cube_normal.x = (cube_normal.z * (-uv_interp.x - params.projection.x)) / params.projection.y; - cube_normal.y = -(cube_normal.z * (-uv_interp.y - params.projection.z)) / params.projection.w; + cube_normal.y = -(cube_normal.z * (uv_interp.y - params.projection.z)) / params.projection.w; #endif cube_normal = mat3(params.orientation) * cube_normal; cube_normal = normalize(cube_normal); diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 81d3d87a22..1e1b6d8937 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -2299,7 +2299,7 @@ void fragment_shader(in SceneData scene_data) { #else directional_lights.data[i].color * directional_lights.data[i].energy * tint, #endif - true, shadow, f0, orms, 1.0, albedo, alpha, + true, shadow, f0, orms, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2373,7 +2373,7 @@ void fragment_shader(in SceneData scene_data) { shadow = blur_shadow(shadow); - light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, + light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -2445,7 +2445,7 @@ void fragment_shader(in SceneData scene_data) { shadow = blur_shadow(shadow); - light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, + light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index da8ad3db15..0cb34557ea 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -1591,7 +1591,7 @@ void main() { light_compute(normal, directional_lights.data[i].direction, view, size_A, directional_lights.data[i].color * directional_lights.data[i].energy * tint, - true, shadow, f0, orms, 1.0, albedo, alpha, + true, shadow, f0, orms, 1.0, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -1628,7 +1628,7 @@ void main() { shadow = blur_shadow(shadow); // Fragment lighting - light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, + light_process_omni(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -1661,7 +1661,7 @@ void main() { shadow = blur_shadow(shadow); - light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, + light_process_spot(light_index, vertex, view, normal, vertex_ddx, vertex_ddy, f0, orms, shadow, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index a1a185d0fd..1e8fc7eab4 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -39,7 +39,7 @@ vec3 F0(float metallic, float specular, vec3 albedo) { return mix(vec3(dielectric), albedo, vec3(metallic)); } -void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_directional, float attenuation, vec3 f0, uint orms, float specular_amount, vec3 albedo, inout float alpha, +void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_directional, float attenuation, vec3 f0, uint orms, float specular_amount, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -547,7 +547,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal, float taa_fr return 1.0; } -void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, +void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -675,7 +675,7 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v light_attenuation *= shadow; - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, omni_lights.data[idx].specular_amount, albedo, alpha, + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, omni_lights.data[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif @@ -793,7 +793,7 @@ vec2 normal_to_panorama(vec3 n) { return panorama_coords; } -void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, +void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 vertex_ddx, vec3 vertex_ddy, vec3 f0, uint orms, float shadow, vec3 albedo, inout float alpha, vec2 screen_uv, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, #endif @@ -884,7 +884,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v } light_attenuation *= shadow; - light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, spot_lights.data[idx].specular_amount, albedo, alpha, + light_compute(normal, normalize(light_rel_vec), eye_vec, size_A, color, false, light_attenuation, f0, orms, spot_lights.data[idx].specular_amount, albedo, alpha, screen_uv, #ifdef LIGHT_BACKLIGHT_USED backlight, #endif diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index ab5de3cb7f..e9fa38475e 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -946,6 +946,7 @@ RID RenderingDevice::texture_create_shared(const TextureView &p_view, RID p_with RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = texture.barrier_range(); tracker->texture_usage = alias_format.usage_bits; tracker->reference_count = 1; @@ -1125,6 +1126,7 @@ RID RenderingDevice::texture_create_shared_from_slice(const TextureView &p_view, RDG::ResourceTracker *tracker = RDG::resource_tracker_create(); tracker->texture_driver_id = texture.shared_fallback->texture; + tracker->texture_size = Size2i(texture.width, texture.height); tracker->texture_subresources = slice_range; tracker->texture_usage = slice_format.usage_bits; tracker->reference_count = 1; @@ -5415,6 +5417,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id draw_tracker = RDG::resource_tracker_create(); draw_tracker->parent = owner_texture->draw_tracker; draw_tracker->texture_driver_id = p_texture->driver_id; + draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); draw_tracker->texture_subresources = p_texture->barrier_range(); draw_tracker->texture_usage = p_texture->usage_flags; draw_tracker->texture_slice_or_dirty_rect = p_texture->slice_rect; @@ -5437,6 +5440,7 @@ bool RenderingDevice::_texture_make_mutable(Texture *p_texture, RID p_texture_id // Regular texture. p_texture->draw_tracker = RDG::resource_tracker_create(); p_texture->draw_tracker->texture_driver_id = p_texture->driver_id; + p_texture->draw_tracker->texture_size = Size2i(p_texture->width, p_texture->height); p_texture->draw_tracker->texture_subresources = p_texture->barrier_range(); p_texture->draw_tracker->texture_usage = p_texture->usage_flags; p_texture->draw_tracker->reference_count = 1; diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp index 86b5f80e56..ebfe328393 100644 --- a/servers/rendering/rendering_device_graph.cpp +++ b/servers/rendering/rendering_device_graph.cpp @@ -140,7 +140,7 @@ RDD::BarrierAccessBits RenderingDeviceGraph::_usage_to_access_bits(ResourceUsage #endif } -bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const { +bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const { if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { // We don't check possible intersections for usages that aren't consecutive color or depth writes. return true; @@ -155,15 +155,27 @@ bool RenderingDeviceGraph::_check_command_intersection(ResourceTracker *p_resour return true; } - if (!r_intersection_partial_coverage) { - // Indicate if this draw list only partially covers the region of the previous draw list. - r_intersection_partial_coverage = !current_draw_list_command.region.encloses(previous_draw_list_command.region); - } - // We check if the region used by both draw lists have an intersection. return previous_draw_list_command.region.intersects(current_draw_list_command.region); } +bool RenderingDeviceGraph::_check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const { + if (p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_COLOR_READ_WRITE && p_resource_tracker->usage != RESOURCE_USAGE_ATTACHMENT_DEPTH_STENCIL_READ_WRITE) { + // We don't check for partial coverage in usages that aren't attachment writes. + return true; + } + + const uint32_t command_data_offset = command_data_offsets[p_command_index]; + const RecordedDrawListCommand &draw_list_command = *reinterpret_cast<const RecordedDrawListCommand *>(&command_data[command_data_offset]); + if (draw_list_command.type != RecordedCommand::TYPE_DRAW_LIST) { + // We don't check for partial coverage on commands that aren't draw lists. + return false; + } + + Rect2i texture_region(Point2i(0, 0), p_resource_tracker->texture_size); + return !draw_list_command.region.encloses(texture_region); +} + int32_t RenderingDeviceGraph::_add_to_command_list(int32_t p_command_index, int32_t p_list_index) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(command_list_nodes.size())); @@ -199,7 +211,7 @@ int32_t RenderingDeviceGraph::_add_to_slice_read_list(int32_t p_command_index, R return next_index; } -int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index) { +int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage) { DEV_ASSERT(p_command_index < int32_t(command_count)); DEV_ASSERT(p_list_index < int32_t(write_slice_list_nodes.size())); @@ -210,6 +222,7 @@ int32_t RenderingDeviceGraph::_add_to_write_list(int32_t p_command_index, Rect2i new_node.command_index = p_command_index; new_node.next_list_index = p_list_index; new_node.subresources = p_subresources; + new_node.partial_coverage = p_partial_coverage; return next_index; } @@ -476,7 +489,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr resource_tracker->usage = new_resource_usage; } - bool intersection_partial_coverage = false; + bool write_usage_has_partial_coverage = !different_usage && _check_command_partial_coverage(resource_tracker, p_command_index); if (search_tracker->write_command_or_list_index >= 0) { if (search_tracker->write_command_list_enabled) { // Make this command adjacent to any commands that wrote to this resource and intersect with the slice if it applies. @@ -488,11 +501,11 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr if (!resource_has_parent || search_tracker_rect.intersects(write_list_node.subresources)) { if (write_list_node.command_index == p_command_index) { ERR_FAIL_COND_MSG(!resource_has_parent, "Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index, intersection_partial_coverage)) { + } else if (!write_list_node.partial_coverage || _check_command_intersection(resource_tracker, write_list_node.command_index, p_command_index)) { // Command is dependent on this command. Add this command to the adjacency list of the write command. _add_adjacent_command(write_list_node.command_index, p_command_index, r_command); - if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources)) { + if (resource_has_parent && write_usage && search_tracker_rect.encloses(write_list_node.subresources) && !write_usage_has_partial_coverage) { // Eliminate redundant writes from the list. if (previous_write_list_index >= 0) { RecordedSliceListNode &previous_list_node = write_slice_list_nodes[previous_write_list_index]; @@ -514,22 +527,23 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr // The index is just the latest command index that wrote to the resource. if (search_tracker->write_command_or_list_index == p_command_index) { ERR_FAIL_MSG("Command can't have itself as a dependency."); - } else if (_check_command_intersection(resource_tracker, search_tracker->write_command_or_list_index, p_command_index, intersection_partial_coverage)) { + } else { _add_adjacent_command(search_tracker->write_command_or_list_index, p_command_index, r_command); } } } if (write_usage) { - if (resource_has_parent || intersection_partial_coverage) { + bool use_write_list = resource_has_parent || write_usage_has_partial_coverage; + if (use_write_list) { if (!search_tracker->write_command_list_enabled && search_tracker->write_command_or_list_index >= 0) { // Write command list was not being used but there was a write command recorded. Add a new node with the entire parent resource's subresources and the recorded command index to the list. const RDD::TextureSubresourceRange &tracker_subresources = search_tracker->texture_subresources; Rect2i tracker_rect(tracker_subresources.base_mipmap, tracker_subresources.base_layer, tracker_subresources.mipmap_count, tracker_subresources.layer_count); - search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1); + search_tracker->write_command_or_list_index = _add_to_write_list(search_tracker->write_command_or_list_index, tracker_rect, -1, false); } - search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index); + search_tracker->write_command_or_list_index = _add_to_write_list(p_command_index, search_tracker_rect, search_tracker->write_command_or_list_index, write_usage_has_partial_coverage); search_tracker->write_command_list_enabled = true; } else { search_tracker->write_command_or_list_index = p_command_index; @@ -554,7 +568,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr read_full_command_list_index = read_full_next_index; } - if (!resource_has_parent) { + if (!use_write_list) { // Clear the full list if this resource is not a slice. search_tracker->read_full_command_list_index = -1; } @@ -564,7 +578,7 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr int32_t read_slice_command_list_index = search_tracker->read_slice_command_list_index; while (read_slice_command_list_index >= 0) { const RecordedSliceListNode &read_list_node = read_slice_list_nodes[read_slice_command_list_index]; - if (!resource_has_parent || search_tracker_rect.encloses(read_list_node.subresources)) { + if (!use_write_list || search_tracker_rect.encloses(read_list_node.subresources)) { if (previous_slice_command_list_index >= 0) { // Erase this element and connect the previous one to the next element. read_slice_list_nodes[previous_slice_command_list_index].next_list_index = read_list_node.next_list_index; diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h index 452e1700b6..f7d9027147 100644 --- a/servers/rendering/rendering_device_graph.h +++ b/servers/rendering/rendering_device_graph.h @@ -167,6 +167,7 @@ public: RDD::BufferID buffer_driver_id; RDD::TextureID texture_driver_id; RDD::TextureSubresourceRange texture_subresources; + Size2i texture_size; uint32_t texture_usage = 0; int32_t texture_slice_command_index = -1; ResourceTracker *parent = nullptr; @@ -271,6 +272,7 @@ private: int32_t command_index = -1; int32_t next_list_index = -1; Rect2i subresources; + bool partial_coverage = false; }; struct RecordedBufferClearCommand : RecordedCommand { @@ -649,11 +651,12 @@ private: static bool _is_write_usage(ResourceUsage p_usage); static RDD::TextureLayout _usage_to_image_layout(ResourceUsage p_usage); static RDD::BarrierAccessBits _usage_to_access_bits(ResourceUsage p_usage); - bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index, bool &r_intersection_partial_coverage) const; + bool _check_command_intersection(ResourceTracker *p_resource_tracker, int32_t p_previous_command_index, int32_t p_command_index) const; + bool _check_command_partial_coverage(ResourceTracker *p_resource_tracker, int32_t p_command_index) const; int32_t _add_to_command_list(int32_t p_command_index, int32_t p_list_index); void _add_adjacent_command(int32_t p_previous_command_index, int32_t p_command_index, RecordedCommand *r_command); int32_t _add_to_slice_read_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); - int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index); + int32_t _add_to_write_list(int32_t p_command_index, Rect2i p_subresources, int32_t p_list_index, bool p_partial_coverage); RecordedCommand *_allocate_command(uint32_t p_command_size, int32_t &r_command_index); DrawListInstruction *_allocate_draw_list_instruction(uint32_t p_instruction_size); ComputeListInstruction *_allocate_compute_list_instruction(uint32_t p_instruction_size); diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 821009f07c..4b71007ebf 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -201,6 +201,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["DIFFUSE_LIGHT"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["SPECULAR_LIGHT"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ALPHA"] = ShaderLanguage::TYPE_FLOAT; + shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["SCREEN_UV"] = constt(ShaderLanguage::TYPE_VEC2); shader_modes[RS::SHADER_SPATIAL].functions["light"].can_discard = true; shader_modes[RS::SHADER_SPATIAL].functions["light"].main_function = true; diff --git a/tests/core/math/test_projection.h b/tests/core/math/test_projection.h new file mode 100644 index 0000000000..7f41aaee3e --- /dev/null +++ b/tests/core/math/test_projection.h @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* test_projection.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef TEST_PROJECTION_H +#define TEST_PROJECTION_H + +#include "core/math/projection.h" + +#include "core/string/print_string.h" +#include "tests/test_macros.h" + +namespace TestProjection { + +TEST_CASE("[Projection] Default construct") { + Projection p; + CHECK(p.columns[0][0] == 1.0); + CHECK(p.columns[0][1] == 0.0); + CHECK(p.columns[0][2] == 0.0); + CHECK(p.columns[0][3] == 0.0); + + CHECK(p.columns[1][0] == 0.0); + CHECK(p.columns[1][1] == 1.0); + CHECK(p.columns[1][2] == 0.0); + CHECK(p.columns[1][3] == 0.0); + + CHECK(p.columns[2][0] == 0.0); + CHECK(p.columns[2][1] == 0.0); + CHECK(p.columns[2][2] == 1.0); + CHECK(p.columns[2][3] == 0.0); + + CHECK(p.columns[3][0] == 0.0); + CHECK(p.columns[3][1] == 0.0); + CHECK(p.columns[3][2] == 0.0); + CHECK(p.columns[3][3] == 1.0); +} + +bool projection_is_equal_approx(const Projection &p_a, const Projection &p_b) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + if (!Math::is_equal_approx(p_a.columns[i][j], p_b.columns[i][j])) { + return false; + } + } + } + return true; +} + +TEST_CASE("[Projection] Orthogonal projection matrix inversion") { + Projection p = Projection::create_orthogonal(-125.0f, 125.0f, -125.0f, 125.0f, 0.01f, 25.0f); + CHECK(projection_is_equal_approx(p.inverse() * p, Projection())); +} + +TEST_CASE("[Projection] Perspective projection matrix inversion") { + Projection p = Projection::create_perspective(90.0f, 1.77777f, 0.05f, 4000.0f); + CHECK(projection_is_equal_approx(p.inverse() * p, Projection())); +} + +} //namespace TestProjection + +#endif // TEST_PROJECTION_H diff --git a/tests/scene/test_fontfile.h b/tests/scene/test_fontfile.h new file mode 100644 index 0000000000..8c9ba6d6ff --- /dev/null +++ b/tests/scene/test_fontfile.h @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* test_fontfile.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef TEST_FONTFILE_H +#define TEST_FONTFILE_H + +#include "modules/modules_enabled.gen.h" + +#include "scene/resources/font.h" +#include "tests/test_macros.h" + +namespace TestFontfile { + +TEST_CASE("[FontFile] Create font file and check data") { + // Create test instance. + Ref<FontFile> font_file; + font_file.instantiate(); + +#ifdef MODULE_FREETYPE_ENABLED + // Try to load non-existent files. + ERR_PRINT_OFF + CHECK(font_file->load_dynamic_font("") == OK); + CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded."); + + CHECK(font_file->load_dynamic_font("thirdparty/fonts/nofonthasthisname.woff2") == OK); + CHECK_MESSAGE(font_file->get_data().is_empty() == true, "Invalid fontfile should not be loaded."); + ERR_PRINT_ON + + // Load a valid file. + CHECK(font_file->load_dynamic_font("thirdparty/fonts/NotoSans_Regular.woff2") == OK); + + // Check fontfile data. + CHECK_MESSAGE(font_file->get_data().is_empty() == false, "Fontfile should have been loaded."); + CHECK_MESSAGE(font_file->get_font_name() == "Noto Sans", "Loaded correct font name."); + CHECK_MESSAGE(font_file->get_font_style_name() == "Regular", "Loaded correct font style."); + CHECK_MESSAGE(font_file->get_data().size() == 148480llu, "Whole fontfile was loaded."); + + // Valid glyphs. + CHECK_MESSAGE(font_file->get_glyph_index(2, 'a', 0) != 0, "Glyph index for 'a' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 'b', 0) != 0, "Glyph index for 'b' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0103, 0) != 0, "Glyph index for 'latin small letter a with breve' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x03a8, 0) != 0, "Glyph index for 'Greek psi' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x0416, 0) != 0, "Glyph index for 'Cyrillic zhe' is valid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, '&', 0) != 0, "Glyph index for '&' is valid."); + + // Invalid glyphs. + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x4416, 0) == 0, "Glyph index is invalid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x5555, 0) == 0, "Glyph index is invalid."); + CHECK_MESSAGE(font_file->get_glyph_index(2, 0x2901, 0) == 0, "Glyph index is invalid."); +#endif +} + +} // namespace TestFontfile + +#endif // TEST_FONTFILE_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index d860f7b843..9acdc98b1d 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -65,6 +65,7 @@ #include "tests/core/math/test_geometry_3d.h" #include "tests/core/math/test_math_funcs.h" #include "tests/core/math/test_plane.h" +#include "tests/core/math/test_projection.h" #include "tests/core/math/test_quaternion.h" #include "tests/core/math/test_random_number_generator.h" #include "tests/core/math/test_rect2.h" @@ -116,6 +117,7 @@ #include "tests/scene/test_curve.h" #include "tests/scene/test_curve_2d.h" #include "tests/scene/test_curve_3d.h" +#include "tests/scene/test_fontfile.h" #include "tests/scene/test_gradient.h" #include "tests/scene/test_gradient_texture.h" #include "tests/scene/test_image_texture.h" |