diff options
276 files changed, 2901 insertions, 1321 deletions
diff --git a/.github/actions/godot-cache/action.yml b/.github/actions/godot-cache-restore/action.yml index 13142e7ed1..eb955affef 100644 --- a/.github/actions/godot-cache/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -1,5 +1,5 @@ -name: Setup Godot build cache -description: Setup Godot build cache. +name: Restore Godot build cache +description: Restore Godot build cache. inputs: cache-name: description: The cache base name (job name by default). @@ -10,9 +10,8 @@ inputs: runs: using: "composite" steps: - # Upload cache on completion and check it out now. - - name: Load SCons cache directory - uses: actions/cache@v4 + - name: Restore SCons cache directory + uses: actions/cache/restore@v4 with: path: ${{inputs.scons-cache}} key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml new file mode 100644 index 0000000000..b7cbf91f94 --- /dev/null +++ b/.github/actions/godot-cache-save/action.yml @@ -0,0 +1,17 @@ +name: Save Godot build cache +description: Save Godot build cache. +inputs: + cache-name: + description: The cache base name (job name by default). + default: "${{github.job}}" + scons-cache: + description: The SCons cache path. + default: "${{github.workspace}}/.scons-cache/" +runs: + using: "composite" + steps: + - name: Save SCons cache directory + uses: actions/cache/save@v4 + with: + path: ${{inputs.scons-cache}} + key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index f99a31179e..ee75d53282 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -49,8 +49,8 @@ jobs: distribution: temurin java-version: 17 - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -66,6 +66,12 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Generate Godot templates if: matrix.target == 'template_release' run: | diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml index 0546f43acc..8da6d1311e 100644 --- a/.github/workflows/ios_builds.yml +++ b/.github/workflows/ios_builds.yml @@ -22,8 +22,8 @@ jobs: with: submodules: recursive - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore continue-on-error: true - name: Setup Python and SCons @@ -37,5 +37,9 @@ jobs: target: template_release tests: false + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + continue-on-error: true + - name: Upload artifact uses: ./.github/actions/upload-artifact diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 6b98256110..10389dc9da 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -74,16 +74,18 @@ jobs: - name: Template w/ Mono (target=template_release) cache-name: linux-template-mono target: template_release - sconsflags: module_mono_enabled=yes + sconsflags: module_mono_enabled=yes tests=yes + bin: "./bin/godot.linuxbsd.template_release.x86_64.mono" build-mono: false - tests: false + tests: true artifact: true - name: Minimal template (target=template_release, everything disabled) cache-name: linux-template-minimal target: template_release - sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no - tests: false + sconsflags: modules_enabled_by_default=no disable_3d=yes disable_advanced_gui=yes deprecated=no minizip=no tests=yes + bin: "./bin/godot.linuxbsd.template_release.x86_64" + tests: true artifact: true steps: @@ -111,8 +113,8 @@ jobs: sudo rm -rf /usr/local/lib/android echo "Disk usage after:" && df -h - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -140,6 +142,12 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Generate C# glue if: ${{ matrix.build-mono }} run: | @@ -168,7 +176,6 @@ jobs: with: bin: ${{ matrix.bin }} - # Execute unit tests for the editor - name: Unit tests if: ${{ matrix.tests }} run: | diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index badcb688d1..4db7462b3a 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -29,16 +29,17 @@ jobs: - name: Template (target=template_release) cache-name: macos-template target: template_release - tests: false - sconsflags: debug_symbols=no + tests: true + sconsflags: debug_symbols=no tests=yes + bin: "./bin/godot.macos.template_release.universal" steps: - uses: actions/checkout@v4 with: submodules: recursive - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -66,6 +67,12 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Prepare artifact run: | lipo -create ./bin/godot.macos.${{ matrix.target }}.x86_64 ./bin/godot.macos.${{ matrix.target }}.arm64 -output ./bin/godot.macos.${{ matrix.target }}.universal @@ -78,7 +85,6 @@ jobs: with: name: ${{ matrix.cache-name }} - # Execute unit tests for the editor - name: Unit tests if: ${{ matrix.tests }} run: | diff --git a/.github/workflows/web_builds.yml b/.github/workflows/web_builds.yml index 1eb7b901cd..de8339bf1b 100644 --- a/.github/workflows/web_builds.yml +++ b/.github/workflows/web_builds.yml @@ -52,8 +52,8 @@ jobs: run: | emcc -v - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -69,6 +69,12 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Upload artifact uses: ./.github/actions/upload-artifact if: ${{ matrix.artifact }} diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 7a3347d49c..90629204e6 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -34,16 +34,17 @@ jobs: - name: Template (target=template_release) cache-name: windows-template target: template_release - tests: false - sconsflags: debug_symbols=no + tests: true + sconsflags: debug_symbols=no tests=yes + bin: "./bin/godot.windows.template_release.x86_64.console.exe" steps: - uses: actions/checkout@v4 with: submodules: recursive - - name: Setup Godot build cache - uses: ./.github/actions/godot-cache + - name: Restore Godot build cache + uses: ./.github/actions/godot-cache-restore with: cache-name: ${{ matrix.cache-name }} continue-on-error: true @@ -76,6 +77,12 @@ jobs: target: ${{ matrix.target }} tests: ${{ matrix.tests }} + - name: Save Godot build cache + uses: ./.github/actions/godot-cache-save + with: + cache-name: ${{ matrix.cache-name }} + continue-on-error: true + - name: Prepare artifact run: | Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force @@ -85,7 +92,6 @@ jobs: with: name: ${{ matrix.cache-name }} - # Execute unit tests for the editor - name: Unit tests if: ${{ matrix.tests }} run: | diff --git a/SConstruct b/SConstruct index 3fabc4706f..f3331f3b0d 100644 --- a/SConstruct +++ b/SConstruct @@ -200,7 +200,10 @@ opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectur opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) opts.Add( EnumVariable( - "optimize", "Optimization level", "speed_trace", ("none", "custom", "debug", "speed", "speed_trace", "size") + "optimize", + "Optimization level (by default inferred from 'target' and 'dev_build')", + "auto", + ("auto", "none", "custom", "debug", "speed", "speed_trace", "size"), ) ) opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", False)) @@ -466,14 +469,15 @@ env.editor_build = env["target"] == "editor" env.dev_build = env["dev_build"] env.debug_features = env["target"] in ["editor", "template_debug"] -if env.dev_build: - opt_level = "none" -elif env.debug_features: - opt_level = "speed_trace" -else: # Release - opt_level = "speed" +if env["optimize"] == "auto": + if env.dev_build: + opt_level = "none" + elif env.debug_features: + opt_level = "speed_trace" + else: # Release + opt_level = "speed" + env["optimize"] = ARGUMENTS.get("optimize", opt_level) -env["optimize"] = ARGUMENTS.get("optimize", opt_level) env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", env.dev_build) if env.editor_build: @@ -696,12 +700,11 @@ if env.msvc: else: env.Append(LINKFLAGS=["/DEBUG:NONE"]) - if env["optimize"] == "speed": + if env["optimize"].startswith("speed"): env.Append(CCFLAGS=["/O2"]) env.Append(LINKFLAGS=["/OPT:REF"]) - elif env["optimize"] == "speed_trace": - env.Append(CCFLAGS=["/O2"]) - env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"]) + if env["optimize"] == "speed_trace": + env.Append(LINKFLAGS=["/OPT:NOICF"]) elif env["optimize"] == "size": env.Append(CCFLAGS=["/O1"]) env.Append(LINKFLAGS=["/OPT:REF"]) @@ -712,7 +715,13 @@ else: # Adding dwarf-4 explicitly makes stacktraces work with clang builds, # otherwise addr2line doesn't understand them env.Append(CCFLAGS=["-gdwarf-4"]) - if env.dev_build: + if methods.using_emcc(env): + # Emscripten only produces dwarf symbols when using "-g3". + env.Append(CCFLAGS=["-g3"]) + # Emscripten linker needs debug symbols options too. + env.Append(LINKFLAGS=["-gdwarf-4"]) + env.Append(LINKFLAGS=["-g3"]) + elif env.dev_build: env.Append(CCFLAGS=["-g3"]) else: env.Append(CCFLAGS=["-g2"]) @@ -727,17 +736,25 @@ else: else: env.Append(LINKFLAGS=["-s"]) + # Linker needs optimization flags too, at least for Emscripten. + # For other toolchains, this _may_ be useful for LTO too to disambiguate. + if env["optimize"] == "speed": env.Append(CCFLAGS=["-O3"]) + env.Append(LINKFLAGS=["-O3"]) # `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces. elif env["optimize"] == "speed_trace": env.Append(CCFLAGS=["-O2"]) + env.Append(LINKFLAGS=["-O2"]) elif env["optimize"] == "size": env.Append(CCFLAGS=["-Os"]) + env.Append(LINKFLAGS=["-Os"]) elif env["optimize"] == "debug": env.Append(CCFLAGS=["-Og"]) + env.Append(LINKFLAGS=["-Og"]) elif env["optimize"] == "none": env.Append(CCFLAGS=["-O0"]) + env.Append(LINKFLAGS=["-O0"]) # Needs to happen after configure to handle "auto". if env["lto"] != "none": diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index eac1a66be7..e59f79fcc8 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -329,9 +329,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { String path = p_value; if (path.begins_with("*")) { autoload.is_singleton = true; - autoload.path = path.substr(1); + autoload.path = path.substr(1).simplify_path(); } else { - autoload.path = path; + autoload.path = path.simplify_path(); } add_autoload(autoload); } else if (p_name.operator String().begins_with("global_group/")) { diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index bd30da3047..e2ed7245a2 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -39,6 +39,7 @@ #include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/os.h" +#include "servers/display_server.h" class RemoteDebugger::PerformanceProfiler : public EngineProfiler { Object *performance = nullptr; @@ -539,7 +540,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { OS::get_singleton()->delay_usec(10000); if (Thread::get_caller_id() == Thread::get_main_id()) { // If this is a busy loop on the main thread, events still need to be processed. - OS::get_singleton()->process_and_drop_events(); + DisplayServer::get_singleton()->force_process_and_drop_events(); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 47628e4ea0..8e2366fc95 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -795,7 +795,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb // because that's what we want to check to see if it's changed. library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); } else { - library_path = p_path; + library_path = actual_lib_path; } ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index d6c1df9c00..fce377f967 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2800,12 +2800,16 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(G * * Registers an integer constant on an extension class in the ClassDB. * + * Note about registering bitfield values (if p_is_bitfield is true): even though p_constant_value is signed, language bindings are + * advised to treat bitfields as uint64_t, since this is generally clearer and can prevent mistakes like using -1 for setting all bits. + * Language APIs should thus provide an abstraction that registers bitfields (uint64_t) separately from regular constants (int64_t). + * * @param p_library A pointer the library received by the GDExtension's entry point function. * @param p_class_name A pointer to a StringName with the class name. * @param p_enum_name A pointer to a StringName with the enum name. * @param p_constant_name A pointer to a StringName with the constant name. * @param p_constant_value The constant value. - * @param p_is_bitfield Whether or not this is a bit field. + * @param p_is_bitfield Whether or not this constant is part of a bitfield. */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); diff --git a/core/input/input.cpp b/core/input/input.cpp index ec0303df06..91378591b0 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -890,8 +890,9 @@ void Input::action_press(const StringName &p_action, float p_strength) { // Create or retrieve existing action. ActionState &action_state = action_states[p_action]; + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (!action_state.cache.pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } action_state.exact = true; @@ -908,7 +909,8 @@ void Input::action_release(const StringName &p_action) { action_state.cache.pressed = 0; action_state.cache.strength = 0.0; action_state.cache.raw_strength = 0.0; - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.device_states.clear(); action_state.exact = true; @@ -1023,7 +1025,7 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) { if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) { buffered_events.push_back(p_event); } - } else if (use_input_buffering) { + } else if (agile_input_event_flushing) { buffered_events.push_back(p_event); } else { _parse_input_event_impl(p_event, false); @@ -1054,12 +1056,12 @@ void Input::flush_buffered_events() { } } -bool Input::is_using_input_buffering() { - return use_input_buffering; +bool Input::is_agile_input_event_flushing() { + return agile_input_event_flushing; } -void Input::set_use_input_buffering(bool p_enable) { - use_input_buffering = p_enable; +void Input::set_agile_input_event_flushing(bool p_enable) { + agile_input_event_flushing = p_enable; } void Input::set_use_accumulated_input(bool p_enable) { diff --git a/core/input/input.h b/core/input/input.h index 4daea0c9e8..89e48f53d7 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -128,7 +128,7 @@ private: bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; - bool use_input_buffering = false; + bool agile_input_event_flushing = false; bool use_accumulated_input = true; int mouse_from_touch_index = -1; @@ -367,8 +367,8 @@ public: void flush_frame_parsed_events(); #endif void flush_buffered_events(); - bool is_using_input_buffering(); - void set_use_input_buffering(bool p_enable); + bool is_agile_input_event_flushing(); + void set_agile_input_event_flushing(bool p_enable); void set_use_accumulated_input(bool p_enable); bool is_using_accumulated_input(); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 178d02b987..ddeee9d765 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -636,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME)); default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -645,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END)); default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs); // Text Caret Movement Page Up/Down @@ -665,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -673,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs); // Text Caret Addition Below/Above diff --git a/core/io/image.cpp b/core/io/image.cpp index 4b1188ad47..0af0bf7d6a 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -300,10 +300,10 @@ int Image::get_format_block_size(Format p_format) { return 1; } -void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { +void Image::_get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const { int w = width; int h = height; - int ofs = 0; + int64_t ofs = 0; int pixel_size = get_format_pixel_size(format); int pixel_rshift = get_format_pixel_rshift(format); @@ -315,7 +315,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixel_size; s >>= pixel_rshift; @@ -329,37 +329,30 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt r_height = h; } -int Image::get_mipmap_offset(int p_mipmap) const { +int64_t Image::get_mipmap_offset(int p_mipmap) const { ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); return ofs; } -int Image::get_mipmap_byte_size(int p_mipmap) const { - ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - - int ofs, w, h; - _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; - _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); - return ofs2 - ofs; -} - -void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const { - int ofs, w, h; +void Image::get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const { + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; + int64_t ofs2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); r_ofs = ofs; r_size = ofs2 - ofs; } -void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { - int ofs; +void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const { + int64_t ofs; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2, w2, h2; + int64_t ofs2; + int w2, h2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); r_ofs = ofs; r_size = ofs2 - ofs; @@ -538,8 +531,8 @@ void Image::convert(Format p_new_format) { } } - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size); @@ -555,8 +548,8 @@ void Image::convert(Format p_new_format) { int conversion_type = format | p_new_format << 8; for (int mip = 0; mip < mipmap_count; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; int mip_width = 0; int mip_height = 0; get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); @@ -1151,7 +1144,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { if (i == 0) { // Read from the first mipmap that will be interpolated // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); src_ptr = r_ptr + offs; } else if (!interpolate_mipmaps) { @@ -1159,7 +1152,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { break; } else { // Switch to read from the second mipmap that will be interpolated - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); src_ptr = r_ptr + offs; // Switch to write to the second destination image @@ -1599,9 +1592,9 @@ void Image::flip_x() { } /// Get mipmap size and offset. -int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { +int64_t Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { // Data offset in mipmaps (including the original texture). - int size = 0; + int64_t size = 0; int w = p_width; int h = p_height; @@ -1623,7 +1616,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int & int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixsize; s >>= pixshift; @@ -1837,7 +1830,8 @@ Error Image::generate_mipmaps(bool p_renormalize) { int prev_w = width; for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); switch (format) { @@ -1993,7 +1987,8 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con uint8_t *base_ptr = data.ptrw(); for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); uint8_t *ptr = &base_ptr[ofs]; @@ -2102,21 +2097,6 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con _set_color_at_ofs(ptr, pixel_ofs, c); } } -#if 0 - { - int size = get_mipmap_byte_size(i); - print_line("size for mimpap " + itos(i) + ": " + itos(size)); - Vector<uint8_t> imgdata; - imgdata.resize(size); - - - uint8_t* wr = imgdata.ptrw(); - memcpy(wr.ptr(), ptr, size); - wr = uint8_t*(); - Ref<Image> im = Image::create_from_data(w, h, false, format, imgdata); - im->save_png("res://mipmap_" + itos(i) + ".png"); - } -#endif } return OK; @@ -2131,7 +2111,8 @@ void Image::clear_mipmaps() { return; } - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(1, ofs, w, h); data.resize(ofs); @@ -2176,7 +2157,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm = 0; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); data.resize(size); { @@ -2202,7 +2183,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); if (unlikely(p_data.size() != size)) { String description_mipmaps = get_format_name(p_format) + " "; @@ -2405,7 +2386,7 @@ bool Image::is_invisible() const { return false; } - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return true; @@ -2445,7 +2426,7 @@ bool Image::is_invisible() const { } Image::AlphaMode Image::detect_alpha() const { - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return ALPHA_NONE; @@ -2597,7 +2578,7 @@ Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format, return ret; } -int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { +int64_t Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { if (p_mipmap <= 0) { return 0; } @@ -2605,7 +2586,7 @@ int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, i return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); } -int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { +int64_t Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { if (p_mipmap <= 0) { r_w = p_width; r_h = p_height; @@ -3642,9 +3623,10 @@ Ref<Image> Image::rgbe_to_srgb() { return new_image; } -Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { - int ofs, size, w, h; - get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h); +Ref<Image> Image::get_image_from_mipmap(int p_mipmap) const { + int64_t ofs, size; + int w, h; + get_mipmap_offset_size_and_dimensions(p_mipmap, ofs, size, w, h); Vector<uint8_t> new_data; new_data.resize(size); diff --git a/core/io/image.h b/core/io/image.h index d3ae99954f..745bb140bd 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -195,9 +195,9 @@ private: data = p_image.data; } - _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data + _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data - static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); + static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); bool _can_modify(Format p_format) const; _FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const; @@ -238,10 +238,12 @@ public: */ Format get_format() const; - int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data - int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data - void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data - void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data + /** + * Get where the mipmap begins in data. + */ + int64_t get_mipmap_offset(int p_mipmap) const; + void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const; + void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const; enum Image3DValidateError { VALIDATE_3D_OK, @@ -354,8 +356,8 @@ public: static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); static int get_image_required_mipmaps(int p_width, int p_height, Format p_format); static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); + static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); + static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); enum CompressMode { COMPRESS_S3TC, @@ -383,7 +385,7 @@ public: void srgb_to_linear(); void normal_map_to_xy(); Ref<Image> rgbe_to_srgb(); - Ref<Image> get_image_from_mipmap(int p_mipamp) const; + Ref<Image> get_image_from_mipmap(int p_mipmap) const; void bump_map_to_normal_map(float bump_scale = 1.0); void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index c0d18d0120..67469de5cc 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1315,10 +1315,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (array.is_typed()) { Ref<Script> script = array.get_typed_script(); if (script.is_valid()) { - header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT; + header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else if (array.get_typed_class_name() != StringName()) { header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN; } } @@ -1783,12 +1785,18 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Variant variant = array.get_typed_script(); Ref<Script> script = variant; if (script.is_valid()) { - String path = script->get_path(); - ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); - _encode_string(path, buf, r_len); + if (p_full_objects) { + String path = script->get_path(); + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); + _encode_string(path, buf, r_len); + } else { + _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len); + } } else if (array.get_typed_class_name() != StringName()) { - _encode_string(array.get_typed_class_name(), buf, r_len); + _encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len); } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. if (buf) { encode_uint32(array.get_typed_builtin(), buf); buf += 4; diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index 32030146bb..fae3de2a98 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -106,7 +106,7 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } uint32_t size = 0; - uint8_t ipv6[16]; + uint8_t ipv6[16] = {}; rb.read(ipv6, 16, true); packet_ip.set_ipv6(ipv6); rb.read((uint8_t *)&packet_port, 4, true); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index c045c0fc74..432adb88da 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -40,7 +40,12 @@ #include <stdio.h> void Resource::emit_changed() { - emit_signal(CoreStringName(changed)); + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the connection happen on the call queue, later, since signals are not thread-safe. + call_deferred("emit_signal", CoreStringName(changed)); + } else { + emit_signal(CoreStringName(changed)); + } } void Resource::_resource_path_changed() { @@ -161,12 +166,22 @@ bool Resource::editor_can_reload_from_file() { } void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the check and connection happen on the call queue, later, since signals are not thread-safe. + callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags); + return; + } if (!is_connected(CoreStringName(changed), p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) { connect(CoreStringName(changed), p_callable, p_flags); } } void Resource::disconnect_changed(const Callable &p_callable) { + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the check and disconnection happen on the call queue, later, since signals are not thread-safe. + callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable); + return; + } if (is_connected(CoreStringName(changed), p_callable)) { disconnect(CoreStringName(changed), p_callable); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index ab263d9485..d606db620c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -304,31 +304,24 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { thread_load_mutex.unlock(); // Thread-safe either if it's the current thread or a brand new one. - thread_local bool mq_override_present = false; CallQueue *own_mq_override = nullptr; if (load_nesting == 0) { - mq_override_present = false; load_paths_stack = memnew(Vector<String>); - if (!load_task.dependent_path.is_empty()) { - load_paths_stack->push_back(load_task.dependent_path); - } if (!Thread::is_main_thread()) { // Let the caller thread use its own, for added flexibility. Provide one otherwise. if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) { own_mq_override = memnew(CallQueue); MessageQueue::set_thread_singleton_override(own_mq_override); } - mq_override_present = true; set_current_thread_safe_for_nodes(true); } - } else { - DEV_ASSERT(load_task.dependent_path.is_empty()); } // -- - Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); - if (mq_override_present) { + Error load_err = OK; + Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress); + if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) { MessageQueue::get_singleton()->flush(); } @@ -336,7 +329,8 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.resource = res; - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + load_task.progress = 1.0; // It was fully loaded at this point, so force progress to 1.0. + load_task.error = load_err; if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { @@ -473,12 +467,13 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, if (!ignoring_cache && thread_load_tasks.has(local_path)) { load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token); - if (!load_token.is_valid()) { + if (load_token.is_valid()) { + return load_token; + } else { // The token is dying (reached 0 on another thread). // Ensure it's killed now so the path can be safely reused right away. thread_load_tasks[local_path].load_token->clear(); } - return load_token; } load_token.instantiate(); @@ -560,39 +555,46 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - MutexLock thread_load_lock(thread_load_mutex); + bool ensure_progress = false; + ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; + { + MutexLock thread_load_lock(thread_load_mutex); - if (!user_load_tokens.has(p_path)) { - print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); - return THREAD_LOAD_INVALID_RESOURCE; - } + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - String local_path = _validate_local_path(p_path); - if (!thread_load_tasks.has(local_path)) { + String local_path = _validate_local_path(p_path); + if (!thread_load_tasks.has(local_path)) { #ifdef DEV_ENABLED - CRASH_NOW(); + CRASH_NOW(); #endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). - return THREAD_LOAD_INVALID_RESOURCE; - } + // On non-dev, be defensive and at least avoid crashing (at this point at least). + return THREAD_LOAD_INVALID_RESOURCE; + } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; - ThreadLoadStatus status; - status = load_task.status; - if (r_progress) { - *r_progress = _dependency_get_progress(local_path); - } + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + status = load_task.status; + if (r_progress) { + *r_progress = _dependency_get_progress(local_path); + } - // Support userland polling in a loop on the main thread. - if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { - uint64_t frame = Engine::get_singleton()->get_process_frames(); - if (frame == load_task.last_progress_check_main_thread_frame) { - _ensure_load_progress(); - } else { - load_task.last_progress_check_main_thread_frame = frame; + // Support userland polling in a loop on the main thread. + if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { + uint64_t frame = Engine::get_singleton()->get_process_frames(); + if (frame == load_task.last_progress_check_main_thread_frame) { + ensure_progress = true; + } else { + load_task.last_progress_check_main_thread_frame = frame; + } } } + if (ensure_progress) { + _ensure_load_progress(); + } + return status; } @@ -626,13 +628,13 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e if (Thread::is_main_thread() && !load_token->local_path.is_empty()) { const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path]; while (load_task.status == THREAD_LOAD_IN_PROGRESS) { - if (!_ensure_load_progress()) { - // This local poll loop is not needed. - break; - } thread_load_lock.~MutexLock(); + bool exit = !_ensure_load_progress(); OS::get_singleton()->delay_usec(1000); new (&thread_load_lock) MutexLock(thread_load_mutex); + if (exit) { + break; + } } } @@ -688,6 +690,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro Error wtp_task_err = FAILED; if (loader_is_wtp) { // Loading thread is in the worker pool. + load_task.awaited = true; thread_load_mutex.unlock(); wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); } @@ -712,7 +715,6 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } else { DEV_ASSERT(wtp_task_err == OK); thread_load_mutex.lock(); - load_task.awaited = true; } } else { // Loading thread is main or user thread. diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 46df79ea22..5f1831f0d9 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -170,7 +170,6 @@ private: LoadToken *load_token = nullptr; String local_path; String remapped_path; - String dependent_path; String type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index fe4345aa0d..ceeb04b8ea 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -76,6 +76,21 @@ class PlaceholderExtensionInstance { StringName class_name; HashMap<StringName, Variant> properties; + // Checks if a property is from a runtime class, and not a non-runtime base class. + bool is_runtime_property(const StringName &p_property_name) { + StringName current_class_name = class_name; + + while (ClassDB::is_class_runtime(current_class_name)) { + if (ClassDB::has_property(current_class_name, p_property_name, true)) { + return true; + } + + current_class_name = ClassDB::get_parent_class(current_class_name); + } + + return false; + } + public: PlaceholderExtensionInstance(const StringName &p_class_name) { class_name = p_class_name; @@ -83,27 +98,24 @@ public: ~PlaceholderExtensionInstance() {} - void set(const StringName &p_name, const Variant &p_value) { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - - // If there's a default value, then we know it's a valid property. - if (is_default_valid) { + void set(const StringName &p_name, const Variant &p_value, bool &r_valid) { + r_valid = is_runtime_property(p_name); + if (r_valid) { properties[p_name] = p_value; } } - Variant get(const StringName &p_name) { + Variant get(const StringName &p_name, bool &r_valid) { const Variant *value = properties.getptr(p_name); Variant ret; if (value) { ret = *value; + r_valid = true; } else { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - if (is_default_valid) { - ret = default_value; + r_valid = is_runtime_property(p_name); + if (r_valid) { + ret = ClassDB::class_get_default_property_value(class_name, p_name); } } @@ -115,10 +127,10 @@ public: const StringName &name = *(StringName *)p_name; const Variant &value = *(const Variant *)p_value; - self->set(name, value); + bool valid = false; + self->set(name, value, valid); - // We have to return true so Godot doesn't try to call the real setter function. - return true; + return valid; } static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { @@ -126,10 +138,10 @@ public: const StringName &name = *(StringName *)p_name; Variant *value = (Variant *)r_ret; - *value = self->get(name); + bool valid = false; + *value = self->get(name, valid); - // We have to return true so Godot doesn't try to call the real getter function. - return true; + return valid; } static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { @@ -172,9 +184,9 @@ public: static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; - // Find the closest native parent. + // Find the closest native parent, that isn't a runtime class. ClassDB::ClassInfo *native_parent = ti->inherits_ptr; - while (native_parent->gdextension) { + while (native_parent->gdextension || native_parent->is_runtime) { native_parent = native_parent->inherits_ptr; } ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); @@ -671,6 +683,21 @@ bool ClassDB::can_instantiate(const StringName &p_class) { return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance)); } +bool ClassDB::is_abstract(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + if (!ti) { + if (!ScriptServer::is_global_class(p_class)) { + ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); + } + String path = ScriptServer::get_global_class_path(p_class); + Ref<Script> scr = ResourceLoader::load(path); + return scr.is_valid() && scr->is_valid() && scr->is_abstract(); + } + return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr); +} + bool ClassDB::is_virtual(const StringName &p_class) { OBJTYPE_RLOCK; @@ -1952,6 +1979,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) { return ti->reloadable; } +bool ClassDB::is_class_runtime(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->is_runtime; +} + void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { if (resource_base_extensions.has(p_extension)) { return; @@ -2063,6 +2098,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) { ClassInfo *parent = classes.getptr(p_extension->parent_class_name); +#ifdef TOOLS_ENABLED + // @todo This is a limitation of the current implementation, but it should be possible to remove. + ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class"); +#endif + ClassInfo c; c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; c.gdextension = p_extension; diff --git a/core/object/class_db.h b/core/object/class_db.h index 37a864c109..228b82b588 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -287,6 +287,7 @@ public: static bool class_exists(const StringName &p_class); static bool is_parent_class(const StringName &p_class, const StringName &p_inherits); static bool can_instantiate(const StringName &p_class); + static bool is_abstract(const StringName &p_class); static bool is_virtual(const StringName &p_class); static Object *instantiate(const StringName &p_class); static Object *instantiate_no_placeholders(const StringName &p_class); @@ -460,6 +461,7 @@ public: static bool is_class_exposed(const StringName &p_class); static bool is_class_reloadable(const StringName &p_class); + static bool is_class_runtime(const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void get_resource_base_extensions(List<String> *p_extensions); diff --git a/core/object/object.cpp b/core/object/object.cpp index 97a3a405b9..e4d1a8fc9a 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -763,7 +763,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ } if (is_ref_counted()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference."); + ERR_FAIL_V_MSG(Variant(), "Can't free a RefCounted object."); } if (_lock_index.get() > 1) { diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index caf4ed3835..a873bc1f09 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -59,8 +59,9 @@ void WorkerThreadPool::_process_task(Task *p_task) { CallQueue *call_queue_backup = MessageQueue::get_singleton() != MessageQueue::get_main_singleton() ? MessageQueue::get_singleton() : nullptr; { - // Tasks must start with this unset. They are free to set-and-forget otherwise. + // Tasks must start with these at default values. They are free to set-and-forget otherwise. set_current_thread_safe_for_nodes(false); + MessageQueue::set_thread_singleton_override(nullptr); // Since the WorkerThreadPool is started before the script server, // its pre-created threads can't have ScriptServer::thread_enter() called on them early. // Therefore, we do it late at the first opportunity, so in case the task @@ -397,16 +398,17 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { task->waiting_user++; } - task_mutex.unlock(); - if (caller_pool_thread) { + task_mutex.unlock(); _wait_collaboratively(caller_pool_thread, task); + task_mutex.lock(); task->waiting_pool--; if (task->waiting_pool == 0 && task->waiting_user == 0) { tasks.erase(p_task_id); task_allocator.free(task); } } else { + task_mutex.unlock(); task->done_semaphore.wait(); task_mutex.lock(); task->waiting_user--; @@ -414,9 +416,9 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { tasks.erase(p_task_id); task_allocator.free(task); } - task_mutex.unlock(); } + task_mutex.unlock(); return OK; } @@ -670,7 +672,7 @@ uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mut uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) { for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { - if (unlikely(unlockable_mutexes[i] == (uintptr_t)p_mutex)) { + if (unlikely((unlockable_mutexes[i] & ~1) == (uintptr_t)p_mutex)) { // Already registered in the current thread. return UINT32_MAX; } diff --git a/core/os/os.h b/core/os/os.h index 63cc6ed50e..91e0ce9379 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -328,8 +328,6 @@ public: virtual void benchmark_end_measure(const String &p_context, const String &p_what); virtual void benchmark_dump(); - virtual void process_and_drop_events() {} - virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path); enum PreferredTextureFormat { diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index 0748e9cb83..1e6c6e42a9 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -370,15 +370,19 @@ class CommandQueueMT { flush_read_ptr += 8; CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); cmd->call(); + + // Handle potential realloc due to the command and unlock allowance. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); + if (unlikely(cmd->sync)) { sync_head++; unlock(); // Give an opportunity to awaiters right away. sync_cond_var.notify_all(); lock(); + // Handle potential realloc happened during unlock. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); } - // If the command involved reallocating the buffer, the address may have changed. - cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); cmd->~CommandBase(); flush_read_ptr += size; diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 3685515db5..54cd1eda2f 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) { for (int i = 0; i < size; i++) { const Variant &element = source[i]; if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { - ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } } _p->array = p_array._p->array; @@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) { continue; } if (!Variant::can_convert_strict(value->get_type(), typed.type)) { - ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'."); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { // from primitives to different convertible primitives @@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) { const Variant *value = source + i; Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else { ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); diff --git a/core/variant/variant.h b/core/variant/variant.h index f352af24da..1cb3580c01 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -857,7 +857,7 @@ String vformat(const String &p_text, const VarArgs... p_args) { bool error = false; String fmt = p_text.sprintf(args_array, &error); - ERR_FAIL_COND_V_MSG(error, String(), fmt); + ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + "."); return fmt; } diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index bcab80ea94..2cd3a51722 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2939,10 +2939,10 @@ The property is not stored, and does not display in the editor. This is the default for non-exported properties. </constant> <constant name="PROPERTY_USAGE_STORAGE" value="2" enum="PropertyUsageFlags" is_bitfield="true"> - The property is serialized and saved in the scene file (default). + The property is serialized and saved in the scene file (default for exported properties). </constant> <constant name="PROPERTY_USAGE_EDITOR" value="4" enum="PropertyUsageFlags" is_bitfield="true"> - The property is shown in the [EditorInspector] (default). + The property is shown in the [EditorInspector] (default for exported properties). </constant> <constant name="PROPERTY_USAGE_INTERNAL" value="8" enum="PropertyUsageFlags" is_bitfield="true"> The property is excluded from the class reference. diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index 427d38d421..77c74e17da 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -170,7 +170,7 @@ <method name="get_shortest_axis" qualifiers="const"> <return type="Vector3" /> <description> - Returns the shortest normaalized axis of this bounding box's [member size], as a [Vector3] ([constant Vector3.RIGHT], [constant Vector3.UP], or [constant Vector3.BACK]). + Returns the shortest normalized axis of this bounding box's [member size], as a [Vector3] ([constant Vector3.RIGHT], [constant Vector3.UP], or [constant Vector3.BACK]). [codeblocks] [gdscript] var box = AABB(Vector3(0, 0, 0), Vector3(2, 4, 8)) diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index 302ca314bb..1ca8ac2fa5 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -135,7 +135,7 @@ <return type="void" /> <param index="0" name="name" type="StringName" /> <description> - Queues an animation for playback once the current one is done. + Queues an animation for playback once the current animation and all previously queued animations are done. [b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow. </description> </method> diff --git a/doc/classes/ArrayMesh.xml b/doc/classes/ArrayMesh.xml index 0f6bf89cd7..c80dabb8c0 100644 --- a/doc/classes/ArrayMesh.xml +++ b/doc/classes/ArrayMesh.xml @@ -206,7 +206,8 @@ Overrides the [AABB] with one defined by user for use with frustum culling. Especially useful to avoid unexpected culling when using a shader to offset vertices. </member> <member name="shadow_mesh" type="ArrayMesh" setter="set_shadow_mesh" getter="get_shadow_mesh"> - An optional mesh which is used for rendering shadows and can be used for the depth prepass. Can be used to increase performance of shadow rendering by using a mesh that only contains vertex position data (without normals, UVs, colors, etc.). + An optional mesh which can be used for rendering shadows and the depth prepass. Can be used to increase performance by supplying a mesh with fused vertices and only vertex position data (without normals, UVs, colors, etc.). + [b]Note:[/b] This mesh must have exactly the same vertex positions as the source mesh (including the source mesh's LODs, if present). If vertex positions differ, then the mesh will not draw correctly. </member> </members> </class> diff --git a/doc/classes/AtlasTexture.xml b/doc/classes/AtlasTexture.xml index cea8e13f4c..45877f4003 100644 --- a/doc/classes/AtlasTexture.xml +++ b/doc/classes/AtlasTexture.xml @@ -21,7 +21,7 @@ The margin around the [member region]. Useful for small adjustments. If the [member Rect2.size] of this property ("w" and "h" in the editor) is set, the drawn texture is resized to fit within the margin. </member> <member name="region" type="Rect2" setter="set_region" getter="get_region" default="Rect2(0, 0, 0, 0)"> - The region used to draw the [member atlas]. + The region used to draw the [member atlas]. If either dimension of the region's size is [code]0[/code], the value from [member atlas] size will be used for that axis instead. </member> <member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> </members> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 8bee6c3470..186ee1b9c4 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -525,6 +525,7 @@ <return type="bool" /> <description> Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its ancestors are also visible. If any ancestor is hidden, this node will not be visible in the scene tree, and is therefore not drawn (see [method _draw]). + Visibility is checked only in parent nodes that inherit from [CanvasItem], [CanvasLayer], and [Window]. If the parent is of any other type (such as [Node], [AnimationPlayer], or [Node3D]), it is assumed to be visible. </description> </method> <method name="make_canvas_position_local" qualifiers="const"> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index ff1c390b3c..0bed5288bd 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -20,6 +20,7 @@ <return type="Image" /> <description> Returns the user's clipboard as an image if possible. + [b]Note:[/b] This method uses the copied pixel data, e.g. from a image editing software or a web browser, not an image file copied from file explorer. </description> </method> <method name="clipboard_get_primary" qualifiers="const"> @@ -879,6 +880,12 @@ Registers callables to emit when the menu is respectively about to show or closed. Callback methods should have zero arguments. </description> </method> + <method name="has_additional_outputs" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if any additional outputs have been registered via [method register_additional_output]. + </description> + </method> <method name="has_feature" qualifiers="const"> <return type="bool" /> <param index="0" name="feature" type="int" enum="DisplayServer.Feature" /> @@ -1022,6 +1029,14 @@ Perform window manager processing, including input flushing. See also [method force_process_and_drop_events], [method Input.flush_buffered_events] and [member Input.use_accumulated_input]. </description> </method> + <method name="register_additional_output"> + <return type="void" /> + <param index="0" name="object" type="Object" /> + <description> + Registers an [Object] which represents an additional output that will be rendered too, beyond normal windows. The [Object] is only used as an identifier, which can be later passed to [method unregister_additional_output]. + This can be used to prevent Godot from skipping rendering when no normal windows are visible. + </description> + </method> <method name="screen_get_dpi" qualifiers="const"> <return type="int" /> <param index="0" name="screen" type="int" default="-1" /> @@ -1352,6 +1367,13 @@ [b]Note:[/b] [member ProjectSettings.audio/general/text_to_speech] should be [code]true[/code] to use text-to-speech. </description> </method> + <method name="unregister_additional_output"> + <return type="void" /> + <param index="0" name="object" type="Object" /> + <description> + Unregisters an [Object] representing an additional output, that was registered via [method register_additional_output]. + </description> + </method> <method name="virtual_keyboard_get_height" qualifiers="const"> <return type="int" /> <description> diff --git a/doc/classes/EditorFileSystem.xml b/doc/classes/EditorFileSystem.xml index 08b40c7800..06fa4be2c8 100644 --- a/doc/classes/EditorFileSystem.xml +++ b/doc/classes/EditorFileSystem.xml @@ -84,6 +84,12 @@ Emitted if a resource is reimported. </description> </signal> + <signal name="resources_reimporting"> + <param index="0" name="resources" type="PackedStringArray" /> + <description> + Emitted before a resource is reimported. + </description> + </signal> <signal name="resources_reload"> <param index="0" name="resources" type="PackedStringArray" /> <description> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index c7ff543b66..e4dab85038 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -596,6 +596,16 @@ The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url]. To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser]. </member> + <member name="input/buffering/agile_event_flushing" type="bool" setter="" getter=""> + If [code]true[/code], input events will be flushed just before every idle and physics frame. + If [code]false[/code], these events will be flushed only once per process frame, between iterations of the engine. + Enabling this setting can greatly improve input responsiveness, especially in devices that struggle to run at the project's intended frame rate. + </member> + <member name="input/buffering/use_accumulated_input" type="bool" setter="" getter=""> + If [code]true[/code], similar input events sent by the operating system are accumulated. When input accumulation is enabled, all input events generated during a frame will be merged and emitted when the frame is done rendering. Therefore, this limits the number of input method calls per second to the rendering FPS. + Input accumulation can be disabled to get slightly more precise/reactive input at the cost of increased CPU usage. + [b]Note:[/b] Input accumulation is [i]enabled[/i] by default. + </member> <member name="interface/editor/accept_dialog_cancel_ok_buttons" type="int" setter="" getter=""> How to position the Cancel and OK buttons in the editor's [AcceptDialog]s. Different platforms have different standard behaviors for this, which can be overridden using this setting. This is useful if you use Godot both on Windows and macOS/Linux and your Godot muscle memory is stronger than your OS specific one. - [b]Auto[/b] follows the platform convention: Cancel first on macOS and Linux, OK first on Windows. diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index 6fb15e4d21..e7d44411ef 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -137,6 +137,12 @@ <constant name="BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL" value="9" enum="BakeError"> Lightmap baking failed as the maximum texture size is too small to fit some of the meshes marked for baking. </constant> + <constant name="BAKE_ERROR_LIGHTMAP_TOO_SMALL" value="10" enum="BakeError"> + Lightmap baking failed as the lightmap is too small. + </constant> + <constant name="BAKE_ERROR_ATLAS_TOO_SMALL" value="11" enum="BakeError"> + Lightmap baking failed as the lightmap was unable to fit into an atlas. + </constant> <constant name="ENVIRONMENT_MODE_DISABLED" value="0" enum="EnvironmentMode"> Ignore environment lighting when baking lightmaps. </constant> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 2662b34aec..c54219c056 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -1187,7 +1187,7 @@ </constant> <constant name="NOTIFICATION_WM_GO_BACK_REQUEST" value="1007"> Notification received from the OS when a go back request is sent (e.g. pressing the "Back" button on Android). - Implemented only on iOS. + Implemented only on Android. </constant> <constant name="NOTIFICATION_WM_SIZE_CHANGED" value="1008"> Notification received when the window is resized. diff --git a/doc/classes/Parallax2D.xml b/doc/classes/Parallax2D.xml index 472aeb0bd3..cfd282a723 100644 --- a/doc/classes/Parallax2D.xml +++ b/doc/classes/Parallax2D.xml @@ -8,6 +8,7 @@ [b]Note:[/b] Any changes to this node's position made after it enters the scene tree will be overridden if [member ignore_camera_scroll] is [code]false[/code] or [member screen_offset] is modified. </description> <tutorials> + <link title="2D Parallax">$DOCS_URL/tutorials/2d/2d_parallax.html</link> </tutorials> <members> <member name="autoscroll" type="Vector2" setter="set_autoscroll" getter="get_autoscroll" default="Vector2(0, 0)"> diff --git a/doc/classes/PhysicsPointQueryParameters2D.xml b/doc/classes/PhysicsPointQueryParameters2D.xml index 521e584173..642e87947a 100644 --- a/doc/classes/PhysicsPointQueryParameters2D.xml +++ b/doc/classes/PhysicsPointQueryParameters2D.xml @@ -24,6 +24,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)"> The position being queried for, in global coordinates. diff --git a/doc/classes/PhysicsPointQueryParameters3D.xml b/doc/classes/PhysicsPointQueryParameters3D.xml index 1cbc11bd03..a53300c78d 100644 --- a/doc/classes/PhysicsPointQueryParameters3D.xml +++ b/doc/classes/PhysicsPointQueryParameters3D.xml @@ -20,6 +20,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)"> The position being queried for, in global coordinates. diff --git a/doc/classes/PhysicsRayQueryParameters2D.xml b/doc/classes/PhysicsRayQueryParameters2D.xml index 3d69e092f6..a9738b4690 100644 --- a/doc/classes/PhysicsRayQueryParameters2D.xml +++ b/doc/classes/PhysicsRayQueryParameters2D.xml @@ -36,6 +36,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="from" type="Vector2" setter="set_from" getter="get_from" default="Vector2(0, 0)"> The starting point of the ray being queried for, in global coordinates. diff --git a/doc/classes/PhysicsRayQueryParameters3D.xml b/doc/classes/PhysicsRayQueryParameters3D.xml index b203b8f555..175b594fb0 100644 --- a/doc/classes/PhysicsRayQueryParameters3D.xml +++ b/doc/classes/PhysicsRayQueryParameters3D.xml @@ -36,6 +36,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="from" type="Vector3" setter="set_from" getter="get_from" default="Vector3(0, 0, 0)"> The starting point of the ray being queried for, in global coordinates. diff --git a/doc/classes/PhysicsShapeQueryParameters2D.xml b/doc/classes/PhysicsShapeQueryParameters2D.xml index 915d94a54c..3687a4dc5a 100644 --- a/doc/classes/PhysicsShapeQueryParameters2D.xml +++ b/doc/classes/PhysicsShapeQueryParameters2D.xml @@ -20,6 +20,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0"> The collision margin for the shape. diff --git a/doc/classes/PhysicsShapeQueryParameters3D.xml b/doc/classes/PhysicsShapeQueryParameters3D.xml index eba2b8287f..f05322f1ab 100644 --- a/doc/classes/PhysicsShapeQueryParameters3D.xml +++ b/doc/classes/PhysicsShapeQueryParameters3D.xml @@ -20,6 +20,7 @@ </member> <member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]"> The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node. + [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again. </member> <member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0"> The collision margin for the shape. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index b0f421e932..4f7f372864 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1395,12 +1395,6 @@ Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate. [b]Note:[/b] Currently implemented only on Android. </member> - <member name="input_devices/buffering/android/use_accumulated_input" type="bool" setter="" getter="" default="true"> - If [code]true[/code], multiple input events will be accumulated into a single input event when possible. - </member> - <member name="input_devices/buffering/android/use_input_buffering" type="bool" setter="" getter="" default="true"> - If [code]true[/code], input events will be buffered prior to being dispatched. - </member> <member name="input_devices/compatibility/legacy_just_pressed_behavior" type="bool" setter="" getter="" default="false"> If [code]true[/code], [method Input.is_action_just_pressed] and [method Input.is_action_just_released] will only return [code]true[/code] if the action is still in the respective state, i.e. an action that is pressed [i]and[/i] released on the same frame will be missed. If [code]false[/code], no input will be lost. diff --git a/doc/classes/RenderDataExtension.xml b/doc/classes/RenderDataExtension.xml index 9bdab8e101..5c2b75ed1b 100644 --- a/doc/classes/RenderDataExtension.xml +++ b/doc/classes/RenderDataExtension.xml @@ -12,24 +12,25 @@ <method name="_get_camera_attributes" qualifiers="virtual const"> <return type="RID" /> <description> - Implement this in GDExtension to return the [RID] for the implementations camera attributes object. + Implement this in GDExtension to return the [RID] for the implementation's camera attributes object. </description> </method> <method name="_get_environment" qualifiers="virtual const"> <return type="RID" /> <description> + Implement this in GDExtension to return the [RID] of the implementation's environment object. </description> </method> <method name="_get_render_scene_buffers" qualifiers="virtual const"> <return type="RenderSceneBuffers" /> <description> - Implement this in GDExtension to return the [RID] of the implementations environment object. + Implement this in GDExtension to return the implementation's [RenderSceneBuffers] object. </description> </method> <method name="_get_render_scene_data" qualifiers="virtual const"> <return type="RenderSceneData" /> <description> - Implement this in GDExtension to return the implementations [RenderSceneDataExtension] object. + Implement this in GDExtension to return the implementation's [RenderSceneDataExtension] object. </description> </method> </methods> diff --git a/doc/classes/RenderSceneBuffersRD.xml b/doc/classes/RenderSceneBuffersRD.xml index 359f0b33a8..212a65337e 100644 --- a/doc/classes/RenderSceneBuffersRD.xml +++ b/doc/classes/RenderSceneBuffersRD.xml @@ -89,6 +89,12 @@ If [param msaa] is [b]true[/b] and MSAA is enabled, this returns the MSAA variant of the buffer. </description> </method> + <method name="get_fsr_sharpness" qualifiers="const"> + <return type="float" /> + <description> + Returns the FSR sharpness value used while rendering the 3D content (if [method get_scaling_3d_mode] is an FSR mode). + </description> + </method> <method name="get_internal_size" qualifiers="const"> <return type="Vector2i" /> <description> @@ -107,6 +113,24 @@ Returns the render target associated with this buffers object. </description> </method> + <method name="get_scaling_3d_mode" qualifiers="const"> + <return type="int" enum="RenderingServer.ViewportScaling3DMode" /> + <description> + Returns the scaling mode used for upscaling. + </description> + </method> + <method name="get_screen_space_aa" qualifiers="const"> + <return type="int" enum="RenderingServer.ViewportScreenSpaceAA" /> + <description> + Returns the screen-space antialiasing method applied. + </description> + </method> + <method name="get_target_size" qualifiers="const"> + <return type="Vector2i" /> + <description> + Returns the target size of the render buffer (size after upscaling). + </description> + </method> <method name="get_texture" qualifiers="const"> <return type="RID" /> <param index="0" name="context" type="StringName" /> @@ -123,6 +147,12 @@ Returns the texture format information with which a cached texture was created. </description> </method> + <method name="get_texture_samples" qualifiers="const"> + <return type="int" enum="RenderingDevice.TextureSamples" /> + <description> + Returns the number of MSAA samples used. + </description> + </method> <method name="get_texture_slice"> <return type="RID" /> <param index="0" name="context" type="StringName" /> @@ -157,6 +187,12 @@ Returns a specific view of a slice (layer or mipmap) for a cached texture. </description> </method> + <method name="get_use_debanding" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if debanding is enabled. + </description> + </method> <method name="get_use_taa" qualifiers="const"> <return type="bool" /> <description> diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 3c1061dee9..876f229d0c 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -1444,10 +1444,10 @@ VRAM-compressed signed red/green channel data format with normalized value. Values are in the [code][-1.0, 1.0][/code] range. The format's precision is 8 bits of red channel and 8 bits of green channel. Using BC5 texture compression (also known as S3TC RGTC). </constant> <constant name="DATA_FORMAT_BC6H_UFLOAT_BLOCK" value="142" enum="DataFormat"> - VRAM-compressed unsigned red/green/blue channel data format with the floating-point value stored as-is. The format's precision is 8 bits of red channel and 8 bits of green channel. Using BC6H texture compression (also known as BPTC HDR). + VRAM-compressed unsigned red/green/blue channel data format with the floating-point value stored as-is. The format's precision is between 10 and 13 bits for the red/green/blue channels. Using BC6H texture compression (also known as BPTC HDR). </constant> <constant name="DATA_FORMAT_BC6H_SFLOAT_BLOCK" value="143" enum="DataFormat"> - VRAM-compressed signed red/green/blue channel data format with the floating-point value stored as-is. The format's precision is between 4 and 7 bits for the red/green/blue channels and between 0 and 8 bits for the alpha channel. Using BC7 texture compression (also known as BPTC HDR). + VRAM-compressed signed red/green/blue channel data format with the floating-point value stored as-is. The format's precision is between 10 and 13 bits for the red/green/blue channels. Using BC6H texture compression (also known as BPTC HDR). </constant> <constant name="DATA_FORMAT_BC7_UNORM_BLOCK" value="144" enum="DataFormat"> VRAM-compressed unsigned red/green/blue/alpha channel data format with normalized value. Values are in the [code][0.0, 1.0][/code] range. The format's precision is between 4 and 7 bits for the red/green/blue channels and between 0 and 8 bits for the alpha channel. Also known as BPTC LDR. @@ -1477,13 +1477,13 @@ 11-bit VRAM-compressed unsigned red channel data format with normalized value. Values are in the [code][0.0, 1.0][/code] range. Using ETC2 texture compression. </constant> <constant name="DATA_FORMAT_EAC_R11_SNORM_BLOCK" value="153" enum="DataFormat"> - 11-bit VRAM-compressed signed red channel data format with normalized value. Values are in the [code][0.0, 1.0][/code] range. Using ETC2 texture compression. + 11-bit VRAM-compressed signed red channel data format with normalized value. Values are in the [code][-1.0, 1.0][/code] range. Using ETC2 texture compression. </constant> <constant name="DATA_FORMAT_EAC_R11G11_UNORM_BLOCK" value="154" enum="DataFormat"> 11-bit VRAM-compressed unsigned red/green channel data format with normalized value. Values are in the [code][0.0, 1.0][/code] range. Using ETC2 texture compression. </constant> <constant name="DATA_FORMAT_EAC_R11G11_SNORM_BLOCK" value="155" enum="DataFormat"> - 11-bit VRAM-compressed signed red/green channel data format with normalized value. Values are in the [code][0.0, 1.0][/code] range. Using ETC2 texture compression. + 11-bit VRAM-compressed signed red/green channel data format with normalized value. Values are in the [code][-1.0, 1.0][/code] range. Using ETC2 texture compression. </constant> <constant name="DATA_FORMAT_ASTC_4x4_UNORM_BLOCK" value="156" enum="DataFormat"> VRAM-compressed unsigned floating-point data format with normalized value, packed in 4×4 blocks (highest quality). Values are in the [code][0.0, 1.0][/code] range. Using ASTC compression. diff --git a/doc/classes/Resource.xml b/doc/classes/Resource.xml index cec936ac3e..74d083594f 100644 --- a/doc/classes/Resource.xml +++ b/doc/classes/Resource.xml @@ -40,8 +40,11 @@ <param index="0" name="subresources" type="bool" default="false" /> <description> Duplicates this resource, returning a new resource with its [code]export[/code]ed or [constant PROPERTY_USAGE_STORAGE] properties copied from the original. - If [param subresources] is [code]false[/code], a shallow copy is returned; nested resources within subresources are not duplicated and are shared from the original resource. If [param subresources] is [code]true[/code], a deep copy is returned; nested subresources will be duplicated and are not shared. - Subresource properties with the [constant PROPERTY_USAGE_ALWAYS_DUPLICATE] flag are always duplicated even with [param subresources] set to [code]false[/code], and properties with the [constant PROPERTY_USAGE_NEVER_DUPLICATE] flag are never duplicated even with [param subresources] set to [code]true[/code]. + If [param subresources] is [code]false[/code], a shallow copy is returned; nested resources within subresources are not duplicated and are shared with the original resource (with one exception; see below). If [param subresources] is [code]true[/code], a deep copy is returned; nested subresources will be duplicated and are not shared (with two exceptions; see below). + [param subresources] is usually respected, with the following exceptions: + - Subresource properties with the [constant PROPERTY_USAGE_ALWAYS_DUPLICATE] flag are always duplicated. + - Subresource properties with the [constant PROPERTY_USAGE_NEVER_DUPLICATE] flag are never duplicated. + - Subresources inside [Array] and [Dictionary] properties are never duplicated. [b]Note:[/b] For custom resources, this method will fail if [method Object._init] has been defined with required parameters. </description> </method> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 24d2d26beb..7e0c39ac7c 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -432,7 +432,7 @@ Adds [code skip-lint][ol][/code] or [code skip-lint][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length. </description> </method> - <method name="push_meta"> + <method name="push_meta" keywords="push_url"> <return type="void" /> <param index="0" name="data" type="Variant" /> <param index="1" name="underline_mode" type="int" enum="RichTextLabel.MetaUnderline" default="1" /> @@ -628,7 +628,7 @@ <member name="language" type="String" setter="set_language" getter="get_language" default=""""> Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. </member> - <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true"> + <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true" keywords="url_underlined"> If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code]. These tags can call a function when clicked if [signal meta_clicked] is connected to a function. </member> <member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000"> diff --git a/doc/classes/Script.xml b/doc/classes/Script.xml index fa8e4ef5f2..45f0bbb8aa 100644 --- a/doc/classes/Script.xml +++ b/doc/classes/Script.xml @@ -93,6 +93,7 @@ <return type="bool" /> <description> Returns [code]true[/code] if the script contains non-empty source code. + [b]Note:[/b] If a script does not have source code, this does not mean that it is invalid or unusable. For example, a [GDScript] that was exported with binary tokenization has no source code, but still behaves as expected and could be instantiated. This can be checked with [method can_instantiate]. </description> </method> <method name="instance_has" qualifiers="const"> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 5829a787a1..cc3f61e1b2 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -57,11 +57,6 @@ Force updates the bone transform for the bone at [param bone_idx] and all of its children. </description> </method> - <method name="get_animate_physical_bones" qualifiers="const" deprecated=""> - <return type="bool" /> - <description> - </description> - </method> <method name="get_bone_children" qualifiers="const"> <return type="PackedInt32Array" /> <param index="0" name="bone_idx" type="int" /> @@ -239,14 +234,6 @@ Sets all bone poses to rests. </description> </method> - <method name="set_animate_physical_bones" deprecated=""> - <return type="void" /> - <param index="0" name="enabled" type="bool" /> - <description> - This method exists for compatibility with old structures in which the [Skeleton3D] does not have a [PhysicalBoneSimulator3D] as a child, but directly has [PhysicalBone3D]s as children. - In case you need to raycast to it without running [method physical_bones_start_simulation], call this method with [code]enabled == true[/code]. - </description> - </method> <method name="set_bone_enabled"> <return type="void" /> <param index="0" name="bone_idx" type="int" /> @@ -342,6 +329,10 @@ </method> </methods> <members> + <member name="animate_physical_bones" type="bool" setter="set_animate_physical_bones" getter="get_animate_physical_bones" default="true" deprecated=""> + If you follow the recommended workflow and explicitly have [PhysicalBoneSimulator3D] as a child of [Skeleton3D], you can control whether it is affected by raycasting without running [method physical_bones_start_simulation], by its [member SkeletonModifier3D.active]. + However, for old (deprecated) configurations, [Skeleton3D] has an internal virtual [PhysicalBoneSimulator3D] for compatibility. This property controls the internal virtual [PhysicalBoneSimulator3D]'s [member SkeletonModifier3D.active]. + </member> <member name="modifier_callback_mode_process" type="int" setter="set_modifier_callback_mode_process" getter="get_modifier_callback_mode_process" enum="Skeleton3D.ModifierCallbackModeProcess" default="1"> Sets the processing timing for the Modifier. </member> diff --git a/doc/classes/TileSetScenesCollectionSource.xml b/doc/classes/TileSetScenesCollectionSource.xml index 9d2742b844..40ad269e83 100644 --- a/doc/classes/TileSetScenesCollectionSource.xml +++ b/doc/classes/TileSetScenesCollectionSource.xml @@ -6,6 +6,32 @@ <description> When placed on a [TileMap], tiles from [TileSetScenesCollectionSource] will automatically instantiate an associated scene at the cell's position in the TileMap. Scenes are instantiated as children of the [TileMap] when it enters the tree. If you add/remove a scene tile in the [TileMap] that is already inside the tree, the [TileMap] will automatically instantiate/free the scene accordingly. + [b]Note:[/b] Scene tiles all occupy one tile slot and instead use alternate tile ID to identify scene index. [method TileSetSource.get_tiles_count] will always return [code]1[/code]. Use [method get_scene_tiles_count] to get a number of scenes in a [TileSetScenesCollectionSource]. + Use this code if you want to find the scene path at a given tile in [TileMapLayer]: + [codeblocks] + [gdscript] + var source_id = tile_map_layer.get_cell_source_id(Vector2i(x, y)) + if source_id > -1: + var scene_source = tile_map_layer.tile_set.get_source(source_id) + if scene_source is TileSetScenesCollectionSource: + var alt_id = tile_map_layer.get_cell_alternative_tile(Vector2i(x, y)) + # The assigned PackedScene. + var scene = scene_source.get_scene_tile_scene(alt_id) + [/gdscript] + [csharp] + int sourceId = tileMapLayer.GetCellSourceId(new Vector2I(x, y)); + if (sourceId > -1) + { + TileSetSource source = tileMapLayer.TileSet.GetSource(sourceId); + if (source is TileSetScenesCollectionSource sceneSource) + { + int altId = tileMapLayer.GetCellAlternativeTile(new Vector2I(x, y)); + // The assigned PackedScene. + PackedScene scene = sceneSource.GetSceneTileScene(altId); + } + } + [/csharp] + [/codeblocks] </description> <tutorials> </tutorials> diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index 345d0512ff..4158fbe710 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -4,8 +4,10 @@ A 2×3 matrix representing a 2D transformation. </brief_description> <description> - A 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations. It can represent transformations such as translation, rotation, and scaling. It consists of three [Vector2] values: [member x], [member y], and the [member origin]. + The [Transform2D] built-in [Variant] type is a 2×3 [url=https://en.wikipedia.org/wiki/Matrix_(mathematics)]matrix[/url] representing a transformation in 2D space. It contains three [Vector2] values: [member x], [member y], and [member origin]. Together, they can represent translation, rotation, scale, and skew. + The [member x] and [member y] axes form a 2×2 matrix, known as the transform's [b]basis[/b]. The length of each axis ([method Vector2.length]) influences the transform's scale, while the direction of all axes influence the rotation. Usually, both axes are perpendicular to one another. However, when you rotate one axis individually, the transform becomes skewed. Applying a skewed transform to a 2D sprite will make the sprite appear distorted. For a general introduction, see the [url=$DOCS_URL/tutorials/math/matrices_and_transforms.html]Matrices and transforms[/url] tutorial. + [b]Note:[/b] Unlike [Transform3D], there is no 2D equivalent to the [Basis] type. All mentions of "basis" refer to the [member x] and [member y] components of [Transform2D]. </description> <tutorials> <link title="Math documentation index">$DOCS_URL/tutorials/math/index.html</link> @@ -17,7 +19,7 @@ <constructor name="Transform2D"> <return type="Transform2D" /> <description> - Constructs a default-initialized [Transform2D] set to [constant IDENTITY]. + Constructs a [Transform2D] identical to [constant IDENTITY]. </description> </constructor> <constructor name="Transform2D"> @@ -32,7 +34,7 @@ <param index="0" name="rotation" type="float" /> <param index="1" name="position" type="Vector2" /> <description> - Constructs the transform from a given angle (in radians) and position. + Constructs a [Transform2D] from a given angle (in radians) and position. </description> </constructor> <constructor name="Transform2D"> @@ -42,7 +44,7 @@ <param index="2" name="skew" type="float" /> <param index="3" name="position" type="Vector2" /> <description> - Constructs the transform from a given angle (in radians), scale, skew (in radians) and position. + Constructs a [Transform2D] from a given angle (in radians), scale, skew (in radians), and position. </description> </constructor> <constructor name="Transform2D"> @@ -51,7 +53,7 @@ <param index="1" name="y_axis" type="Vector2" /> <param index="2" name="origin" type="Vector2" /> <description> - Constructs the transform from 3 [Vector2] values representing [member x], [member y], and the [member origin] (the three column vectors). + Constructs a [Transform2D] from 3 [Vector2] values representing [member x], [member y], and the [member origin] (the three matrix columns). </description> </constructor> </constructors> @@ -59,56 +61,81 @@ <method name="affine_inverse" qualifiers="const"> <return type="Transform2D" /> <description> - Returns the inverse of the transform, under the assumption that the basis is invertible (must have non-zero determinant). + Returns the inverted version of this transform. Unlike [method inverse], this method works with almost any basis, including non-uniform ones, but is slower. See also [method inverse]. + [b]Note:[/b] For this method to return correctly, the transform's basis needs to have a determinant that is not exactly [code]0[/code] (see [method determinant]). </description> </method> <method name="basis_xform" qualifiers="const"> <return type="Vector2" /> <param index="0" name="v" type="Vector2" /> <description> - Returns a vector transformed (multiplied) by the basis matrix. - This method does not account for translation (the [member origin] vector). + Returns a copy of the [param v] vector, transformed (multiplied) by the transform basis's matrix. Unlike the multiplication operator ([code]*[/code]), this method ignores the [member origin]. </description> </method> <method name="basis_xform_inv" qualifiers="const"> <return type="Vector2" /> <param index="0" name="v" type="Vector2" /> <description> - Returns a vector transformed (multiplied) by the inverse basis matrix, under the assumption that the basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not). - This method does not account for translation (the [member origin] vector). - [code]transform.basis_xform_inv(vector)[/code] is equivalent to [code]transform.inverse().basis_xform(vector)[/code]. See [method inverse]. - For non-orthonormal transforms (e.g. with scaling) [code]transform.affine_inverse().basis_xform(vector)[/code] can be used instead. See [method affine_inverse]. + Returns a copy of the [param v] vector, transformed (multiplied) by the inverse transform basis's matrix (see [method inverse]). This method ignores the [member origin]. + [b]Note:[/b] This method assumes that this transform's basis is [i]orthonormal[/i] (see [method orthonormalized]). If the basis is not orthonormal, [code]transform.affine_inverse().basis_xform(vector)[/code] should be used instead (see [method affine_inverse]). </description> </method> <method name="determinant" qualifiers="const"> <return type="float" /> <description> - Returns the determinant of the basis matrix. If the basis is uniformly scaled, then its determinant equals the square of the scale factor. - A negative determinant means the basis was flipped, so one part of the scale is negative. A zero determinant means the basis isn't invertible, and is usually considered invalid. + Returns the [url=https://en.wikipedia.org/wiki/Determinant]determinant[/url] of this transform basis's matrix. For advanced math, this number can be used to determine a few attributes: + - If the determinant is exactly [code]0[/code], the basis is not invertible (see [method inverse]). + - If the determinant is a negative number, the basis represents a negative scale. + [b]Note:[/b] If the basis's scale is the same for every axis, its determinant is always that scale by the power of 2. </description> </method> <method name="get_origin" qualifiers="const"> <return type="Vector2" /> <description> - Returns the transform's origin (translation). + Returns this transform's translation. Equivalent to [member origin]. </description> </method> <method name="get_rotation" qualifiers="const"> <return type="float" /> <description> - Returns the transform's rotation (in radians). + Returns this transform's rotation (in radians). This is equivalent to [member x]'s angle (see [method Vector2.angle]). </description> </method> <method name="get_scale" qualifiers="const"> <return type="Vector2" /> <description> - Returns the scale. + Returns the length of both [member x] and [member y], as a [Vector2]. If this transform's basis is not skewed, this value is the scaling factor. It is not affected by rotation. + [codeblocks] + [gdscript] + var my_transform = Transform2D( + Vector2(2, 0), + Vector2(0, 4), + Vector2(0, 0) + ) + # Rotating the Transform2D in any way preserves its scale. + my_transform = my_transform.rotated(TAU / 2) + + print(my_transform.get_scale()) # Prints (2, 4). + [/gdscript] + [csharp] + var myTransform = new Transform2D( + Vector3(2.0f, 0.0f), + Vector3(0.0f, 4.0f), + Vector3(0.0f, 0.0f) + ); + // Rotating the Transform2D in any way preserves its scale. + myTransform = myTransform.Rotated(Mathf.Tau / 2.0f); + + GD.Print(myTransform.GetScale()); // Prints (2, 4, 8). + [/csharp] + [/codeblocks] + [b]Note:[/b] If the value returned by [method determinant] is negative, the scale is also negative. </description> </method> <method name="get_skew" qualifiers="const"> <return type="float" /> <description> - Returns the transform's skew (in radians). + Returns this transform's skew (in radians). </description> </method> <method name="interpolate_with" qualifiers="const"> @@ -116,19 +143,21 @@ <param index="0" name="xform" type="Transform2D" /> <param index="1" name="weight" type="float" /> <description> - Returns a transform interpolated between this transform and another by a given [param weight] (on the range of 0.0 to 1.0). + Returns the result of the linear interpolation between this transform and [param xform] by the given [param weight]. + The [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). Values outside this range are allowed and can be used to perform [i]extrapolation[/i] instead. </description> </method> <method name="inverse" qualifiers="const"> <return type="Transform2D" /> <description> - Returns the inverse of the transform, under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not). Use [method affine_inverse] for non-orthonormal transforms (e.g. with scaling). + Returns the [url=https://en.wikipedia.org/wiki/Invertible_matrix]inverted version of this transform[/url]. + [b]Note:[/b] For this method to return correctly, the transform's basis needs to be [i]orthonormal[/i] (see [method orthonormalized]). That means, the basis should only represent a rotation. If it does not, use [method affine_inverse] instead. </description> </method> <method name="is_conformal" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the transform's basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the transform's basis has non-uniform scale or shear/skew. This can be used to validate if the transform is non-distorted, which is important for physics and other use cases. + Returns [code]true[/code] if this transform's basis is conformal. A conformal basis is both [i]orthogonal[/i] (the axes are perpendicular to each other) and [i]uniform[/i] (the axes share the same length). This method can be especially useful during physics calculations. </description> </method> <method name="is_equal_approx" qualifiers="const"> @@ -148,14 +177,13 @@ <return type="Transform2D" /> <param index="0" name="target" type="Vector2" default="Vector2(0, 0)" /> <description> - Returns a copy of the transform rotated such that the rotated X-axis points towards the [param target] position. - Operations take place in global space. + Returns a copy of the transform rotated such that the rotated X-axis points towards the [param target] position, in global space. </description> </method> <method name="orthonormalized" qualifiers="const"> <return type="Transform2D" /> <description> - Returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors (scale of 1 or -1). + Returns a copy of this transform with its basis orthonormalized. An orthonormal basis is both [i]orthogonal[/i] (the axes are perpendicular to each other) and [i]normalized[/i] (the axes have a length of [code]1[/code]), which also means it can only represent rotation. </description> </method> <method name="rotated" qualifiers="const"> @@ -215,24 +243,41 @@ </methods> <members> <member name="origin" type="Vector2" setter="" getter="" default="Vector2(0, 0)"> - The origin vector (column 2, the third column). Equivalent to array index [code]2[/code]. The origin vector represents translation. + The translation offset of this transform, and the column [code]2[/code] of the matrix. In 2D space, this can be seen as the position. </member> <member name="x" type="Vector2" setter="" getter="" default="Vector2(1, 0)"> - The basis matrix's X vector (column 0). Equivalent to array index [code]0[/code]. + The transform basis's X axis, and the column [code]0[/code] of the matrix. Combined with [member y], this represents the transform's rotation, scale, and skew. + On the identity transform, this vector points right ([constant Vector2.RIGHT]). </member> <member name="y" type="Vector2" setter="" getter="" default="Vector2(0, 1)"> - The basis matrix's Y vector (column 1). Equivalent to array index [code]1[/code]. + The transform basis's Y axis, and the column [code]1[/code] of the matrix. Combined with [member x], this represents the transform's rotation, scale, and skew. + On the identity transform, this vector points up ([constant Vector2.UP]). </member> </members> <constants> <constant name="IDENTITY" value="Transform2D(1, 0, 0, 1, 0, 0)"> - The identity [Transform2D] with no translation, rotation or scaling applied. When applied to other data structures, [constant IDENTITY] performs no transformation. + The identity [Transform2D]. A transform with no translation, no rotation, and its scale being [code]1[/code]. When multiplied by another [Variant] such as [Rect2] or another [Transform2D], no transformation occurs. This means that: + - The [member x] points right ([constant Vector2.RIGHT]); + - The [member y] points up ([constant Vector2.UP]). + [codeblock] + var transform = Transform2D.IDENTITY + print("| X | Y | Origin") + print("| %s | %s | %s" % [transform.x.x, transform.y.x, transform.origin.x]) + print("| %s | %s | %s" % [transform.x.y, transform.y.y, transform.origin.y]) + # Prints: + # | X | Y | Origin + # | 1 | 0 | 0 + # | 0 | 1 | 0 + [/codeblock] + This is identical to creating [constructor Transform2D] without any parameters. This constant can be used to make your code clearer, and for consistency with C#. </constant> <constant name="FLIP_X" value="Transform2D(-1, 0, 0, 1, 0, 0)"> - The [Transform2D] that will flip something along the X axis. + When any transform is multiplied by [constant FLIP_X], it negates all components of the [member x] axis (the X column). + When [constant FLIP_X] is multiplied by any basis, it negates the [member Vector2.x] component of all axes (the X row). </constant> <constant name="FLIP_Y" value="Transform2D(1, 0, 0, -1, 0, 0)"> - The [Transform2D] that will flip something along the Y axis. + When any transform is multiplied by [constant FLIP_Y], it negates all components of the [member y] axis (the Y column). + When [constant FLIP_Y] is multiplied by any basis, it negates the [member Vector2.y] component of all axes (the Y row). </constant> </constants> <operators> @@ -240,7 +285,7 @@ <return type="bool" /> <param index="0" name="right" type="Transform2D" /> <description> - Returns [code]true[/code] if the transforms are not equal. + Returns [code]true[/code] if the components of both transforms are not equal. [b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable. </description> </operator> @@ -248,63 +293,69 @@ <return type="PackedVector2Array" /> <param index="0" name="right" type="PackedVector2Array" /> <description> - Transforms (multiplies) each element of the [Vector2] array by the given [Transform2D] matrix. + Transforms (multiplies) every [Vector2] element of the given [PackedVector2Array] by this transformation matrix. + On larger arrays, this operation is much faster than transforming each [Vector2] individually. </description> </operator> <operator name="operator *"> <return type="Rect2" /> <param index="0" name="right" type="Rect2" /> <description> - Transforms (multiplies) the [Rect2] by the given [Transform2D] matrix. + Transforms (multiplies) the [Rect2] by this transformation matrix. </description> </operator> <operator name="operator *"> <return type="Transform2D" /> <param index="0" name="right" type="Transform2D" /> <description> - Composes these two transformation matrices by multiplying them together. This has the effect of transforming the second transform (the child) by the first transform (the parent). + Transforms (multiplies) this transform by the [param right] transform. + This is the operation performed between parent and child [CanvasItem] nodes. + [b]Note:[/b] If you need to only modify one attribute of this transform, consider using one of the following methods, instead: + - For translation, see [method translated] or [method translated_local]. + - For rotation, see [method rotated] or [method rotated_local]. + - For scale, see [method scaled] or [method scaled_local]. </description> </operator> <operator name="operator *"> <return type="Vector2" /> <param index="0" name="right" type="Vector2" /> <description> - Transforms (multiplies) the [Vector2] by the given [Transform2D] matrix. + Transforms (multiplies) the [Vector2] by this transformation matrix. </description> </operator> <operator name="operator *"> <return type="Transform2D" /> <param index="0" name="right" type="float" /> <description> - This operator multiplies all components of the [Transform2D], including the [member origin] vector, which scales it uniformly. + Multiplies all components of the [Transform2D] by the given [float], including the [member origin]. This affects the transform's scale uniformly. </description> </operator> <operator name="operator *"> <return type="Transform2D" /> <param index="0" name="right" type="int" /> <description> - This operator multiplies all components of the [Transform2D], including the [member origin] vector, which scales it uniformly. + Multiplies all components of the [Transform2D] by the given [int], including the [member origin]. This affects the transform's scale uniformly. </description> </operator> <operator name="operator /"> <return type="Transform2D" /> <param index="0" name="right" type="float" /> <description> - This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly. + Divides all components of the [Transform2D] by the given [float], including the [member origin]. This affects the transform's scale uniformly. </description> </operator> <operator name="operator /"> <return type="Transform2D" /> <param index="0" name="right" type="int" /> <description> - This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly. + Divides all components of the [Transform2D] by the given [int], including the [member origin]. This affects the transform's scale uniformly. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="Transform2D" /> <description> - Returns [code]true[/code] if the transforms are exactly equal. + Returns [code]true[/code] if the components of both transforms are exactly equal. [b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable. </description> </operator> @@ -312,7 +363,7 @@ <return type="Vector2" /> <param index="0" name="index" type="int" /> <description> - Access transform components using their index. [code]t[0][/code] is equivalent to [code]t.x[/code], [code]t[1][/code] is equivalent to [code]t.y[/code], and [code]t[2][/code] is equivalent to [code]t.origin[/code]. + Accesses each axis (column) of this transform by their index. Index [code]0[/code] is the same as [member x], index [code]1[/code] is the same as [member y], and index [code]2[/code] is the same as [member origin]. </description> </operator> </operators> diff --git a/doc/classes/XRInterface.xml b/doc/classes/XRInterface.xml index 175caca598..68d055ad8d 100644 --- a/doc/classes/XRInterface.xml +++ b/doc/classes/XRInterface.xml @@ -176,6 +176,10 @@ Triggers a haptic pulse on a device associated with this interface. [param action_name] is the name of the action for this pulse. [param tracker_name] is optional and can be used to direct the pulse to a specific device provided that device is bound to this haptic. + [param frequency] is the frequency of the pulse, set to [code]0.0[/code] to have the system use a default frequency. + [param amplitude] is the amplitude of the pulse between [code]0.0[/code] and [code]1.0[/code]. + [param duration_sec] is the duration of the pulse in seconds. + [param delay_sec] is a delay in seconds before the pulse is given. </description> </method> <method name="uninitialize"> diff --git a/doc/classes/XRNode3D.xml b/doc/classes/XRNode3D.xml index dfe5600fcc..82f4fa4ab9 100644 --- a/doc/classes/XRNode3D.xml +++ b/doc/classes/XRNode3D.xml @@ -38,6 +38,10 @@ <description> Triggers a haptic pulse on a device associated with this interface. [param action_name] is the name of the action for this pulse. + [param frequency] is the frequency of the pulse, set to [code]0.0[/code] to have the system use a default frequency. + [param amplitude] is the amplitude of the pulse between [code]0.0[/code] and [code]1.0[/code]. + [param duration_sec] is the duration of the pulse in seconds. + [param delay_sec] is a delay in seconds before the pulse is given. </description> </method> </methods> diff --git a/drivers/d3d12/dxil_hash.cpp b/drivers/d3d12/dxil_hash.cpp new file mode 100644 index 0000000000..f94a4a30df --- /dev/null +++ b/drivers/d3d12/dxil_hash.cpp @@ -0,0 +1,209 @@ +/**************************************************************************/ +/* dxil_hash.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +// Based on the patched public domain implementation released by Microsoft here: +// https://github.com/microsoft/hlsl-specs/blob/main/proposals/infra/INF-0004-validator-hashing.md + +#include "dxil_hash.h" + +#include <memory.h> + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static const BYTE padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void FF(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) { + a += ((b & c) | (~b & d)) + x + ac; + a = ((a << s) | (a >> (32 - s))) + b; +} + +static void GG(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) { + a += ((b & d) | (c & ~d)) + x + ac; + a = ((a << s) | (a >> (32 - s))) + b; +} + +static void HH(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) { + a += (b ^ c ^ d) + x + ac; + a = ((a << s) | (a >> (32 - s))) + b; +} + +static void II(UINT &a, UINT b, UINT c, UINT d, UINT x, UINT8 s, UINT ac) { + a += (c ^ (b | ~d)) + x + ac; + a = ((a << s) | (a >> (32 - s))) + b; +} + +void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash) { + UINT leftOver = byteCount & 0x3f; + UINT padAmount; + bool bTwoRowsPadding = false; + if (leftOver < 56) { + padAmount = 56 - leftOver; + } else { + padAmount = 120 - leftOver; + bTwoRowsPadding = true; + } + UINT padAmountPlusSize = padAmount + 8; + UINT state[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }; + UINT N = (byteCount + padAmountPlusSize) >> 6; + UINT offset = 0; + UINT NextEndState = bTwoRowsPadding ? N - 2 : N - 1; + const BYTE *pCurrData = pData; + for (UINT i = 0; i < N; i++, offset += 64, pCurrData += 64) { + UINT x[16]; + const UINT *pX; + if (i == NextEndState) { + if (!bTwoRowsPadding && i == N - 1) { + UINT remainder = byteCount - offset; + x[0] = byteCount << 3; + memcpy((BYTE *)x + 4, pCurrData, remainder); + memcpy((BYTE *)x + 4 + remainder, padding, padAmount); + x[15] = 1 | (byteCount << 1); + } else if (bTwoRowsPadding) { + if (i == N - 2) { + UINT remainder = byteCount - offset; + memcpy(x, pCurrData, remainder); + memcpy((BYTE *)x + remainder, padding, padAmount - 56); + NextEndState = N - 1; + } else if (i == N - 1) { + x[0] = byteCount << 3; + memcpy((BYTE *)x + 4, padding + padAmount - 56, 56); + x[15] = 1 | (byteCount << 1); + } + } + pX = x; + } else { + pX = (const UINT *)pCurrData; + } + + UINT a = state[0]; + UINT b = state[1]; + UINT c = state[2]; + UINT d = state[3]; + + /* Round 1 */ + FF(a, b, c, d, pX[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, pX[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, pX[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, pX[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, pX[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, pX[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, pX[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, pX[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, pX[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, pX[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, pX[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, pX[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, pX[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, pX[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, pX[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, pX[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, pX[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, pX[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, pX[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, pX[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, pX[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, pX[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, pX[15], S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, pX[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, pX[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, pX[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, pX[3], S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, pX[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, pX[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, pX[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, pX[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, pX[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, pX[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, pX[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, pX[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, pX[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, pX[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, pX[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, pX[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, pX[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, pX[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, pX[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, pX[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, pX[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, pX[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, pX[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, pX[15], S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, pX[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, pX[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, pX[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, pX[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, pX[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, pX[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, pX[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, pX[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, pX[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, pX[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, pX[15], S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, pX[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, pX[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, pX[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, pX[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, pX[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, pX[9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + } + + memcpy(pOutHash, state, 16); +} diff --git a/drivers/d3d12/dxil_hash.h b/drivers/d3d12/dxil_hash.h new file mode 100644 index 0000000000..db8ee85a0d --- /dev/null +++ b/drivers/d3d12/dxil_hash.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* dxil_hash.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 DXIL_HASH_H +#define DXIL_HASH_H + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +void compute_dxil_hash(const BYTE *pData, UINT byteCount, BYTE *pOutHash); + +#endif // DXIL_HASH_H diff --git a/drivers/d3d12/rendering_context_driver_d3d12.cpp b/drivers/d3d12/rendering_context_driver_d3d12.cpp index c4cb99fcaa..d01ae5a73f 100644 --- a/drivers/d3d12/rendering_context_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_context_driver_d3d12.cpp @@ -71,10 +71,6 @@ const GUID CLSID_D3D12DeviceFactoryGodot = { 0x114863bf, 0xc386, 0x4aee, { 0xb3, const GUID CLSID_D3D12DebugGodot = { 0xf2352aeb, 0xdd84, 0x49fe, { 0xb9, 0x7b, 0xa9, 0xdc, 0xfd, 0xcc, 0x1b, 0x4f } }; const GUID CLSID_D3D12SDKConfigurationGodot = { 0x7cda6aca, 0xa03e, 0x49c8, { 0x94, 0x58, 0x03, 0x34, 0xd2, 0x0e, 0x07, 0xce } }; -extern "C" { -char godot_nir_arch_name[32]; -} - #ifdef PIX_ENABLED #if defined(__GNUC__) #define _MSC_VER 1800 @@ -86,10 +82,7 @@ char godot_nir_arch_name[32]; #endif #endif -RenderingContextDriverD3D12::RenderingContextDriverD3D12() { - CharString cs = Engine::get_singleton()->get_architecture_name().ascii(); - memcpy(godot_nir_arch_name, (const char *)cs.get_data(), cs.size()); -} +RenderingContextDriverD3D12::RenderingContextDriverD3D12() {} RenderingContextDriverD3D12::~RenderingContextDriverD3D12() { if (lib_d3d12) { diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index a6bce1d79a..122585e595 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -36,6 +36,7 @@ #include "thirdparty/zlib/zlib.h" #include "d3d12_godot_nir_bridge.h" +#include "dxil_hash.h" #include "rendering_context_driver_d3d12.h" // No point in fighting warnings in Mesa. @@ -59,7 +60,6 @@ #pragma clang diagnostic ignored "-Wmissing-field-initializers" #endif -#include "dxil_validator.h" #include "nir_spirv.h" #include "nir_to_dxil.h" #include "spirv_to_dxil.h" @@ -2867,23 +2867,6 @@ static uint32_t SHADER_STAGES_BIT_OFFSET_INDICES[RenderingDevice::SHADER_STAGE_M /* SHADER_STAGE_COMPUTE */ 2, }; -dxil_validator *RenderingDeviceDriverD3D12::_get_dxil_validator_for_current_thread() { - MutexLock lock(dxil_mutex); - - int thread_idx = WorkerThreadPool::get_singleton()->get_thread_index(); - if (dxil_validators.has(thread_idx)) { - return dxil_validators[thread_idx]; - } - -#ifdef DEV_ENABLED - print_verbose("Creating DXIL validator for worker thread index " + itos(thread_idx)); -#endif - - dxil_validator *dxil_validator = dxil_create_validator(nullptr); - dxil_validators.insert(thread_idx, dxil_validator); - return dxil_validator; -} - uint32_t RenderingDeviceDriverD3D12::_shader_patch_dxil_specialization_constant( PipelineSpecializationConstantType p_type, const void *p_value, @@ -3006,40 +2989,20 @@ bool RenderingDeviceDriverD3D12::_shader_apply_specialization_constants( ShaderStage stage = E.key; if ((stages_re_sign_mask & (1 << stage))) { Vector<uint8_t> &bytecode = E.value; - bool sign_ok = _shader_sign_dxil_bytecode(stage, bytecode); - ERR_FAIL_COND_V(!sign_ok, false); + _shader_sign_dxil_bytecode(stage, bytecode); } } return true; } -bool RenderingDeviceDriverD3D12::_shader_sign_dxil_bytecode(ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob) { - dxil_validator *validator = _get_dxil_validator_for_current_thread(); - if (!validator) { - if (is_in_developer_mode()) { - return true; - } else { - OS::get_singleton()->alert("Shader validation failed: DXIL.dll was not found, and developer mode is disabled.\n\nClick OK to exit."); - CRASH_NOW(); - } - } - - char *err = nullptr; - bool res = dxil_validate_module(validator, r_dxil_blob.ptrw(), r_dxil_blob.size(), &err); - if (!res) { - if (err) { - ERR_FAIL_COND_V_MSG(!res, false, "Shader signing invocation at stage " + String(SHADER_STAGE_NAMES[p_stage]) + " failed:\n" + String(err)); - } else { - ERR_FAIL_COND_V_MSG(!res, false, "Shader signing invocation at stage " + String(SHADER_STAGE_NAMES[p_stage]) + " failed."); - } - } - - return true; +void RenderingDeviceDriverD3D12::_shader_sign_dxil_bytecode(ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob) { + uint8_t *w = r_dxil_blob.ptrw(); + compute_dxil_hash(w + 20, r_dxil_blob.size() - 20, w + 4); } String RenderingDeviceDriverD3D12::shader_get_binary_cache_key() { - return "D3D12-SV" + uitos(ShaderBinary::VERSION) + "-" + itos(shader_capabilities.shader_model) + (is_in_developer_mode() ? "dev" : ""); + return "D3D12-SV" + uitos(ShaderBinary::VERSION) + "-" + itos(shader_capabilities.shader_model); } Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(VectorView<ShaderStageSPIRVData> p_spirv, const String &p_shader_name) { @@ -3307,10 +3270,7 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec nir_to_dxil_options nir_to_dxil_options = {}; nir_to_dxil_options.environment = DXIL_ENVIRONMENT_VULKAN; nir_to_dxil_options.shader_model_max = shader_model_d3d_to_dxil(shader_capabilities.shader_model); - dxil_validator *validator = _get_dxil_validator_for_current_thread(); - if (validator) { - nir_to_dxil_options.validator_version_max = dxil_get_validator_version(validator); - } + nir_to_dxil_options.validator_version_max = NO_DXIL_VALIDATION; nir_to_dxil_options.godot_nir_callbacks = &godot_nir_callbacks; dxil_logger logger = {}; @@ -3361,8 +3321,7 @@ Vector<uint8_t> RenderingDeviceDriverD3D12::shader_compile_binary_from_spirv(Vec for (KeyValue<ShaderStage, Vector<uint8_t>> &E : dxil_blobs) { ShaderStage stage = E.key; Vector<uint8_t> &dxil_blob = E.value; - bool sign_ok = _shader_sign_dxil_bytecode(stage, dxil_blob); - ERR_FAIL_COND_V(!sign_ok, Vector<uint8_t>()); + _shader_sign_dxil_bytecode(stage, dxil_blob); } // Build the root signature. @@ -5376,10 +5335,12 @@ void RenderingDeviceDriverD3D12::command_bind_render_pipeline(CommandBufferID p_ cmd_buf_info->cmd_list->OMSetBlendFactor(pso_extra_info.dyn_params.blend_constant.components); cmd_buf_info->cmd_list->OMSetStencilRef(pso_extra_info.dyn_params.stencil_reference); - ComPtr<ID3D12GraphicsCommandList1> command_list_1; - cmd_buf_info->cmd_list->QueryInterface(command_list_1.GetAddressOf()); - if (command_list_1) { - command_list_1->OMSetDepthBounds(pso_extra_info.dyn_params.depth_bounds_min, pso_extra_info.dyn_params.depth_bounds_max); + if (misc_features_support.depth_bounds_supported) { + ComPtr<ID3D12GraphicsCommandList1> command_list_1; + cmd_buf_info->cmd_list->QueryInterface(command_list_1.GetAddressOf()); + if (command_list_1) { + command_list_1->OMSetDepthBounds(pso_extra_info.dyn_params.depth_bounds_min, pso_extra_info.dyn_params.depth_bounds_max); + } } cmd_buf_info->render_pass_state.vf_info = pso_extra_info.vf_info; @@ -5769,8 +5730,15 @@ RDD::PipelineID RenderingDeviceDriverD3D12::render_pipeline_create( (&pipeline_desc.DepthStencilState)->BackFace.StencilDepthFailOp = RD_TO_D3D12_STENCIL_OP[p_depth_stencil_state.back_op.depth_fail]; (&pipeline_desc.DepthStencilState)->BackFace.StencilFunc = RD_TO_D3D12_COMPARE_OP[p_depth_stencil_state.back_op.compare]; - pso_extra_info.dyn_params.depth_bounds_min = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_min : 0.0f; - pso_extra_info.dyn_params.depth_bounds_max = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_max : 1.0f; + if (misc_features_support.depth_bounds_supported) { + pso_extra_info.dyn_params.depth_bounds_min = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_min : 0.0f; + pso_extra_info.dyn_params.depth_bounds_max = p_depth_stencil_state.enable_depth_range ? p_depth_stencil_state.depth_range_max : 1.0f; + } else { + if (p_depth_stencil_state.enable_depth_range) { + WARN_PRINT_ONCE("Depth bounds test is not supported by the GPU driver."); + } + } + pso_extra_info.dyn_params.stencil_reference = p_depth_stencil_state.front_op.reference; } @@ -6287,15 +6255,6 @@ RenderingDeviceDriverD3D12::RenderingDeviceDriverD3D12(RenderingContextDriverD3D } RenderingDeviceDriverD3D12::~RenderingDeviceDriverD3D12() { - { - MutexLock lock(dxil_mutex); - for (const KeyValue<int, dxil_validator *> &E : dxil_validators) { - if (E.value) { - dxil_destroy_validator(E.value); - } - } - } - glsl_type_singleton_decref(); } @@ -6491,6 +6450,12 @@ Error RenderingDeviceDriverD3D12::_check_capabilities() { subgroup_capabilities.wave_ops_supported = options1.WaveOps; } + D3D12_FEATURE_DATA_D3D12_OPTIONS2 options2 = {}; + res = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS2, &options2, sizeof(options2)); + if (SUCCEEDED(res)) { + misc_features_support.depth_bounds_supported = options2.DepthBoundsTestSupported; + } + D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {}; res = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3)); if (SUCCEEDED(res)) { @@ -6576,6 +6541,12 @@ Error RenderingDeviceDriverD3D12::_check_capabilities() { print_verbose(String("- D3D12 16-bit ops supported: ") + (shader_capabilities.native_16bit_ops ? "yes" : "no")); + if (misc_features_support.depth_bounds_supported) { + print_verbose("- Depth bounds test supported"); + } else { + print_verbose("- Depth bounds test not supported"); + } + return OK; } diff --git a/drivers/d3d12/rendering_device_driver_d3d12.h b/drivers/d3d12/rendering_device_driver_d3d12.h index 92e8e494d4..61b1498755 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.h +++ b/drivers/d3d12/rendering_device_driver_d3d12.h @@ -79,7 +79,6 @@ using Microsoft::WRL::ComPtr; #define CUSTOM_INFO_QUEUE_ENABLED 0 #endif -struct dxil_validator; class RenderingContextDriverD3D12; // Design principles: @@ -140,6 +139,10 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver { bool enhanced_barriers_supported = false; }; + struct MiscFeaturesSupport { + bool depth_bounds_supported = false; + }; + RenderingContextDriverD3D12 *context_driver = nullptr; RenderingContextDriver::Device context_device; ComPtr<IDXGIAdapter> adapter; @@ -155,6 +158,7 @@ class RenderingDeviceDriverD3D12 : public RenderingDeviceDriver { StorageBufferCapabilities storage_buffer_capabilities; FormatCapabilities format_capabilities; BarrierCapabilities barrier_capabilities; + MiscFeaturesSupport misc_features_support; String pipeline_cache_id; class DescriptorsHeap { @@ -692,10 +696,6 @@ private: uint32_t root_signature_crc = 0; }; - Mutex dxil_mutex; - HashMap<int, dxil_validator *> dxil_validators; // One per WorkerThreadPool thread used for shader compilation, plus one (-1) for all the other. - - dxil_validator *_get_dxil_validator_for_current_thread(); uint32_t _shader_patch_dxil_specialization_constant( PipelineSpecializationConstantType p_type, const void *p_value, @@ -706,7 +706,7 @@ private: const ShaderInfo *p_shader_info, VectorView<PipelineSpecializationConstant> p_specialization_constants, HashMap<ShaderStage, Vector<uint8_t>> &r_final_stages_bytecode); - bool _shader_sign_dxil_bytecode(ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob); + void _shader_sign_dxil_bytecode(ShaderStage p_stage, Vector<uint8_t> &r_dxil_blob); public: virtual String shader_get_binary_cache_key() override final; diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 8e89889fd1..84b6ab4bd8 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1420,7 +1420,7 @@ void RasterizerSceneGLES3::_fill_render_list(RenderListType p_render_list, const #else bool force_alpha = false; #endif - if (!force_alpha && (surf->flags & GeometryInstanceSurface::FLAG_PASS_OPAQUE)) { + if (!force_alpha && (surf->flags & (GeometryInstanceSurface::FLAG_PASS_DEPTH | GeometryInstanceSurface::FLAG_PASS_OPAQUE))) { rl->add_element(surf); } if (force_alpha || (surf->flags & GeometryInstanceSurface::FLAG_PASS_ALPHA)) { @@ -2499,7 +2499,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); RasterizerGLES3::clear_depth(0.0); glClear(GL_DEPTH_BUFFER_BIT); - glDrawBuffers(0, nullptr); + // Some desktop GL implementations fall apart when using Multiview with GL_NONE. + GLuint db = p_camera_data->view_count > 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE; + glDrawBuffers(1, &db); uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 21790d29b5..1aaf840b30 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1280,7 +1280,7 @@ MaterialStorage::MaterialStorage() { actions.renames["NODE_POSITION_WORLD"] = "model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; - actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.inv_view_matrix[2].xyz"; actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(scene_data.view_matrix * model_matrix)[3].xyz"; diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 2dcf623995..3b1373c928 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1030,10 +1030,8 @@ Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const { if (texture->compressed) { glPixelStorei(GL_PACK_ALIGNMENT, 4); glGetCompressedTexImage(texture->target, i, &w[ofs]); - } else { glPixelStorei(GL_PACK_ALIGNMENT, 1); - glGetTexImage(texture->target, i, texture->gl_format_cache, texture->gl_type_cache, &w[ofs]); } } @@ -1493,7 +1491,7 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image, int tsize = 0; for (int i = 0; i < mipmaps; i++) { - int size, ofs; + int64_t size, ofs; img->get_mipmap_offset_and_size(i, ofs, size); if (compressed) { glPixelStorei(GL_UNPACK_ALIGNMENT, 4); @@ -2123,7 +2121,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { texture->layers = 1; } texture->gl_format_cache = rt->color_format; - texture->gl_type_cache = GL_UNSIGNED_BYTE; + texture->gl_type_cache = !rt->hdr ? GL_UNSIGNED_BYTE : GL_FLOAT; // to set HDR format size to 8 and keep 4 for LDR format texture->gl_internal_format_cache = rt->color_internal_format; texture->tex_id = rt->color; texture->width = rt->size.x; diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 8a03d72b9b..1b83efee32 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -169,7 +169,7 @@ struct Texture { TYPE_3D }; - Type type; + Type type = TYPE_2D; RS::TextureLayeredType layered_type = RS::TEXTURE_LAYERED_2D_ARRAY; GLenum target = GL_TEXTURE_2D; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index baf2c6502f..b6636ca576 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -4429,7 +4429,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD for (int i = 0; i < subindices.size(); i++) { InsertData id = p_id; id.type = Animation::TYPE_BEZIER; - id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length())); + id.value = subindices[i].is_empty() ? p_id.value : p_id.value.get(subindices[i].substr(1, subindices[i].length())); id.path = String(p_id.path) + subindices[i]; p_next_tracks = _confirm_insert(id, p_next_tracks, p_reset_wanted, p_reset_anim, false); } diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index a9d37a6a99..d24b1edd70 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1431,12 +1431,21 @@ Point2i CodeTextEditor::get_error_pos() const { void CodeTextEditor::goto_error() { if (!error->get_text().is_empty()) { + int corrected_column = error_column; + + const String line_text = text_editor->get_line(error_line); + const int indent_size = text_editor->get_indent_size(); + if (indent_size > 1) { + const int tab_count = line_text.length() - line_text.lstrip("\t").length(); + corrected_column -= tab_count * (indent_size - 1); + } + if (text_editor->get_line_count() != error_line) { text_editor->unfold_line(error_line); } text_editor->remove_secondary_carets(); text_editor->set_caret_line(error_line); - text_editor->set_caret_column(error_column); + text_editor->set_caret_column(corrected_column); text_editor->center_viewport_to_caret(); } } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 65ae25e046..a163833e50 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -150,6 +150,11 @@ uint64_t EditorFileSystemDirectory::get_file_modified_time(int p_idx) const { return files[p_idx]->modified_time; } +uint64_t EditorFileSystemDirectory::get_file_import_modified_time(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, files.size(), 0); + return files[p_idx]->import_modified_time; +} + String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const { return files[p_idx]->script_class_name; } @@ -430,11 +435,6 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo return true; } - if (!ResourceFormatImporter::get_singleton()->are_import_settings_valid(p_path)) { - //reimport settings are not valid, reimport - return true; - } - Error err; Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err); @@ -720,12 +720,22 @@ bool EditorFileSystem::_update_scan_actions() { int idx = ia.dir->find_file_index(ia.file); ERR_CONTINUE(idx == -1); String full_path = ia.dir->get_file_path(idx); - if (_test_for_reimport(full_path, false)) { + + bool need_reimport = _test_for_reimport(full_path, false); + if (!need_reimport && FileAccess::exists(full_path + ".import")) { + uint64_t import_mt = ia.dir->get_file_import_modified_time(idx); + if (import_mt != FileAccess::get_modified_time(full_path + ".import")) { + need_reimport = true; + } + } + + if (need_reimport) { //must reimport reimports.push_back(full_path); Vector<String> dependencies = _get_dependencies(full_path); - for (const String &dependency_path : dependencies) { - if (import_extensions.has(dependency_path.get_extension())) { + for (const String &dep : dependencies) { + const String &dependency_path = dep.contains("::") ? dep.get_slice("::", 0) : dep; + if (import_extensions.has(dep.get_extension())) { reimports.push_back(dependency_path); } } @@ -1579,7 +1589,10 @@ bool EditorFileSystem::_find_file(const String &p_file, EditorFileSystemDirector } if (idx == -1) { - //does not exist, create i guess? + // Only create a missing directory in memory when it exists on disk. + if (!dir->dir_exists(fs->get_path().path_join(path[i]))) { + return false; + } EditorFileSystemDirectory *efsd = memnew(EditorFileSystemDirectory); efsd->name = path[i]; @@ -1748,7 +1761,8 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) { String icon_path; if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) { - const String &script_path = file_info->deps[0]; // Assuming the first dependency is a script. + const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script. + const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep; if (!script_path.is_empty()) { String *cached = file_icon_cache.getptr(script_path); if (cached) { @@ -2429,16 +2443,14 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin Variant meta; Error err = importer->import(p_file, base_path, params, &import_variants, &gen_files, &meta); - ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + p_file + "'."); - - //as import is complete, save the .import file + // As import is complete, save the .import file. Vector<String> dest_paths; { Ref<FileAccess> f = FileAccess::open(p_file + ".import", FileAccess::WRITE); ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open file from path '" + p_file + ".import'."); - //write manually, as order matters ([remap] has to go first for performance). + // Write manually, as order matters ([remap] has to go first for performance). f->store_line("[remap]"); f->store_line(""); f->store_line("importer=\"" + importer->get_importer_name() + "\""); @@ -2454,7 +2466,7 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin uid = ResourceUID::get_singleton()->create_id(); } - f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); //store in readable format + f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); // Store in readable format. if (err == OK) { if (importer->get_save_extension().is_empty()) { @@ -2509,13 +2521,14 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin for (int i = 0; i < dest_paths.size(); i++) { dp.push_back(dest_paths[i]); } - f->store_line("dest_files=" + Variant(dp).get_construct_string() + "\n"); + f->store_line("dest_files=" + Variant(dp).get_construct_string()); } + f->store_line(""); f->store_line("[params]"); f->store_line(""); - //store options in provided order, to avoid file changing. Order is also important because first match is accepted first. + // Store options in provided order, to avoid file changing. Order is also important because first match is accepted first. for (const ResourceImporter::ImportOption &E : opts) { String base = E.option.name; @@ -2539,7 +2552,7 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin // Update cpos, newly created files could've changed the index of the reimported p_file. _find_file(p_file, &fs, cpos); - //update modified times, to avoid reimport + // Update modified times, to avoid reimport. fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); fs->files[cpos]->deps = _get_dependencies(p_file); @@ -2553,8 +2566,8 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin ResourceUID::get_singleton()->add_id(uid, p_file); } - //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it - //to reload properly + // If file is currently up, maybe the source it was loaded from changed, so import math must be updated for it + // to reload properly. Ref<Resource> r = ResourceCache::get_ref(p_file); if (r.is_valid()) { if (!r->get_import_path().is_empty()) { @@ -2568,6 +2581,7 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin print_verbose(vformat("EditorFileSystem: \"%s\" import took %d ms.", p_file, OS::get_singleton()->get_ticks_msec() - start_time)); + ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + p_file + "'."); return OK; } @@ -2589,11 +2603,15 @@ void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap } void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params) { + Vector<String> reloads; + reloads.append(p_file); + + // Emit the resource_reimporting signal for the single file before the actual importation. + emit_signal(SNAME("resources_reimporting"), reloads); + _reimport_file(p_file, p_custom_params, p_importer); // Emit the resource_reimported signal for the single file we just reimported. - Vector<String> reloads; - reloads.append(p_file); emit_signal(SNAME("resources_reimported"), reloads); } @@ -2655,6 +2673,9 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { reimport_files.sort(); + // Emit the resource_reimporting signal for the single file before the actual importation. + emit_signal(SNAME("resources_reimporting"), reloads); + #ifdef THREADS_ENABLED bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads"); #else @@ -2749,11 +2770,15 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { Error EditorFileSystem::reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) { ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process."); + Vector<String> reloads; + reloads.append(p_file); + + // Emit the resource_reimporting signal for the single file before the actual importation. + emit_signal(SNAME("resources_reimporting"), reloads); + Error ret = _reimport_file(p_file, p_custom_options, p_custom_importer, &p_generator_parameters); // Emit the resource_reimported signal for the single file we just reimported. - Vector<String> reloads; - reloads.append(p_file); emit_signal(SNAME("resources_reimported"), reloads); return ret; } @@ -2953,6 +2978,7 @@ void EditorFileSystem::_bind_methods() { ADD_SIGNAL(MethodInfo("filesystem_changed")); ADD_SIGNAL(MethodInfo("script_classes_updated")); ADD_SIGNAL(MethodInfo("sources_changed", PropertyInfo(Variant::BOOL, "exist"))); + ADD_SIGNAL(MethodInfo("resources_reimporting", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources"))); ADD_SIGNAL(MethodInfo("resources_reimported", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources"))); ADD_SIGNAL(MethodInfo("resources_reload", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources"))); } diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index aee40ed23d..1bc24416eb 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -89,6 +89,7 @@ public: Vector<String> get_file_deps(int p_idx) const; bool get_file_import_is_valid(int p_idx) const; uint64_t get_file_modified_time(int p_idx) const; + uint64_t get_file_import_modified_time(int p_idx) const; String get_file_script_class_name(int p_idx) const; //used for scripts String get_file_script_class_extends(int p_idx) const; //used for scripts String get_file_script_class_icon_path(int p_idx) const; //used for scripts diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 467a19ebb1..199383c391 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3579,7 +3579,9 @@ void EditorInspector::edit(Object *p_object) { next_object = p_object; // Some plugins need to know the next edited object when clearing the inspector. if (object) { - object->disconnect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback)); + if (likely(Variant(object).get_validated_object())) { + object->disconnect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback)); + } _clear(); } per_array_page.clear(); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 6a016c217a..0dfbcd0e0d 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -58,8 +58,8 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR; - if (self->current != Thread::get_caller_id()) { - callable_mp(self, &EditorLog::add_message).call_deferred(err_str, message_type); + if (!Thread::is_main_thread()) { + MessageQueue::get_main_singleton()->push_callable(callable_mp(self, &EditorLog::add_message), err_str, message_type); } else { self->add_message(err_str, message_type); } @@ -557,8 +557,6 @@ EditorLog::EditorLog() { eh.errfunc = _error_handler; eh.userdata = this; add_error_handler(&eh); - - current = Thread::get_caller_id(); } void EditorLog::deinit() { diff --git a/editor/editor_log.h b/editor/editor_log.h index 7012a2a43c..9c652e912a 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -156,8 +156,6 @@ private: ErrorHandlerList eh; - Thread::ID current; - //void _dragged(const Point2& p_ofs); void _meta_clicked(const String &p_meta); void _clear_request(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e883c296ac..cb647ffc35 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1036,24 +1036,37 @@ void EditorNode::_fs_changed() { } } +void EditorNode::_resources_reimporting(const Vector<String> &p_resources) { + // This will copy all the modified properties of the nodes into 'scenes_modification_table' + // before they are actually reimported. It's important to do this before the reimportation + // because if a mesh is present in an inherited scene, the resource will be modified in + // the inherited scene. Then, get_modified_properties_for_node will return the mesh property, + // which will trigger a recopy of the previous mesh, preventing the reload. + for (const String &res_path : p_resources) { + if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { + preload_reimporting_with_path_in_edited_scenes(res_path); + } + } +} + void EditorNode::_resources_reimported(const Vector<String> &p_resources) { List<String> scenes; int current_tab = scene_tabs->get_current_tab(); - for (int i = 0; i < p_resources.size(); i++) { - String file_type = ResourceLoader::get_resource_type(p_resources[i]); + for (const String &res_path : p_resources) { + String file_type = ResourceLoader::get_resource_type(res_path); if (file_type == "PackedScene") { - scenes.push_back(p_resources[i]); + scenes.push_back(res_path); // Reload later if needed, first go with normal resources. continue; } - if (!ResourceCache::has(p_resources[i])) { + if (!ResourceCache::has(res_path)) { // Not loaded, no need to reload. continue; } // Reload normally. - Ref<Resource> resource = ResourceCache::get_ref(p_resources[i]); + Ref<Resource> resource = ResourceCache::get_ref(res_path); if (resource.is_valid()) { resource->reload_from_file(); } @@ -3340,13 +3353,17 @@ void EditorNode::_exit_editor(int p_exit_code) { dim_editor(true); // Unload addons before quitting to allow cleanup. + unload_editor_addons(); + + get_tree()->quit(p_exit_code); +} + +void EditorNode::unload_editor_addons() { for (const KeyValue<String, EditorPlugin *> &E : addon_name_to_plugin) { print_verbose(vformat("Unloading addon: %s", E.key)); remove_editor_plugin(E.value, false); memdelete(E.value); } - - get_tree()->quit(p_exit_code); } void EditorNode::_discard_changes(const String &p_str) { @@ -4162,15 +4179,6 @@ HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node * return modified_property_map; } -void EditorNode::update_ownership_table_for_addition_node_ancestors(Node *p_current_node, HashMap<Node *, Node *> &p_ownership_table) { - p_ownership_table.insert(p_current_node, p_current_node->get_owner()); - - for (int i = 0; i < p_current_node->get_child_count(); i++) { - Node *child = p_current_node->get_child(i); - update_ownership_table_for_addition_node_ancestors(child, p_ownership_table); - } -} - void EditorNode::update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification) { if (p_node) { // First, attempt to restore the script property since it may affect the get_property_list method. @@ -4279,15 +4287,24 @@ void EditorNode::update_node_reference_modification_table_for_node( } } -void EditorNode::update_reimported_diff_data_for_node( - Node *p_edited_scene, - Node *p_reimported_root, - Node *p_node, - HashMap<NodePath, ModificationNodeEntry> &p_modification_table, - List<AdditiveNodeEntry> &p_addition_list) { +bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimported_root, Node *p_node) { + if (p_node == p_reimported_root) { + return false; + } + bool node_part_of_subscene = p_node != p_edited_scene && p_edited_scene->get_scene_inherited_state().is_valid() && - p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0; + p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0 && + // It's important to process added nodes from the base scene in the inherited scene as + // additional nodes to ensure they do not disappear on reload. + // When p_reimported_root == p_edited_scene that means the edited scene + // is the reimported scene, in that case the node is in the root base scene, + // so it's not an addition, otherwise, the node would be added twice on reload. + (p_node->get_owner() != p_edited_scene || p_reimported_root == p_edited_scene); + + if (node_part_of_subscene) { + return false; + } // Loop through the owners until either we reach the root node or nullptr Node *valid_node_owner = p_node->get_owner(); @@ -4298,7 +4315,25 @@ void EditorNode::update_reimported_diff_data_for_node( valid_node_owner = valid_node_owner->get_owner(); } - if ((valid_node_owner == p_reimported_root && (p_reimported_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_reimported_root) { + // When the owner is the imported scene and the owner is also the edited scene, + // that means the node was added in the current edited scene. + // We can be sure here because if the node that the node does not come from + // the base scene because we checked just over with 'get_scene_inherited_state()->find_node_by_path'. + if (valid_node_owner == p_reimported_root && p_reimported_root != p_edited_scene) { + return false; + } + + return true; +} + +void EditorNode::get_preload_scene_modification_table( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table) { + // Only take the nodes that are in the base imported scene. The additional nodes will be managed + // after the resources are reimported. It's not important to check which property has + // changed on nodes that will not be reimported because they are not part of the reimported scene. + if (!is_additional_node_in_scene(p_edited_scene, p_reimported_root, p_node)) { HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, false); // Find all valid connections to other nodes. @@ -4359,14 +4394,27 @@ void EditorNode::update_reimported_diff_data_for_node( p_modification_table[p_reimported_root->get_path_to(p_node)] = modification_node_entry; } - } else { + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child = p_node->get_child(i); + get_preload_scene_modification_table(p_edited_scene, p_reimported_root, child, p_modification_table); + } +} + +void EditorNode::update_reimported_diff_data_for_additional_nodes( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, + HashMap<NodePath, ModificationNodeEntry> &p_modification_table, + List<AdditiveNodeEntry> &p_addition_list) { + if (is_additional_node_in_scene(p_edited_scene, p_reimported_root, p_node)) { // Only save additional nodes which have an owner since this was causing issues transient ownerless nodes // which get recreated upon scene tree entry. // For now instead, assume all ownerless nodes are transient and will have to be recreated. if (p_node->get_owner()) { HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node, true); - - if (p_node->get_parent()->get_owner() != nullptr && p_node->get_parent()->get_owner() != p_edited_scene) { + if (p_node->get_owner() == p_edited_scene) { AdditiveNodeEntry new_additive_node_entry; new_additive_node_entry.node = p_node; new_additive_node_entry.parent = p_reimported_root->get_path_to(p_node->get_parent()); @@ -4382,18 +4430,8 @@ void EditorNode::update_reimported_diff_data_for_node( new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent()); } - // Gathers the ownership of all ancestor nodes for later use. - HashMap<Node *, Node *> ownership_table; - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *child = p_node->get_child(i); - update_ownership_table_for_addition_node_ancestors(child, ownership_table); - } - - new_additive_node_entry.ownership_table = ownership_table; - p_addition_list.push_back(new_additive_node_entry); } - if (!modified_properties.is_empty()) { ModificationNodeEntry modification_node_entry; modification_node_entry.property_table = modified_properties; @@ -4405,10 +4443,9 @@ void EditorNode::update_reimported_diff_data_for_node( for (int i = 0; i < p_node->get_child_count(); i++) { Node *child = p_node->get_child(i); - update_reimported_diff_data_for_node(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list); + update_reimported_diff_data_for_additional_nodes(p_edited_scene, p_reimported_root, child, p_modification_table, p_addition_list); } } -// void EditorNode::open_request(const String &p_path) { if (!opening_prev) { @@ -5780,6 +5817,25 @@ void EditorNode::reload_scene(const String &p_path) { scene_tabs->set_current_tab(current_tab); } +void EditorNode::get_edited_scene_map(const String &p_instance_path, HashMap<int, List<Node *>> &p_edited_scene_map) { + // Walk through each opened scene to get a global list of all instances which match + // the current reimported scenes. + for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { + if (editor_data.get_scene_path(i) == p_instance_path) { + continue; + } + Node *edited_scene_root = editor_data.get_edited_scene_root(i); + + if (edited_scene_root) { + List<Node *> valid_nodes; + find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes); + if (valid_nodes.size() > 0) { + p_edited_scene_map[i] = valid_nodes; + } + } + } +} + void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) { String scene_file_path = p_node->get_scene_file_path(); @@ -5807,25 +5863,54 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * } } -void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) { +void EditorNode::preload_reimporting_with_path_in_edited_scenes(const String &p_instance_path) { HashMap<int, List<Node *>> edited_scene_map; - Array replaced_nodes; + scenes_modification_table.clear(); - // Walk through each opened scene to get a global list of all instances which match - // the current reimported scenes. - for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { - if (editor_data.get_scene_path(i) != p_instance_path) { - Node *edited_scene_root = editor_data.get_edited_scene_root(i); - - if (edited_scene_root) { - List<Node *> valid_nodes; - find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes); - if (valid_nodes.size() > 0) { - edited_scene_map[i] = valid_nodes; + get_edited_scene_map(p_instance_path, edited_scene_map); + + if (edited_scene_map.size() > 0) { + scenes_modification_table.clear(); + + int original_edited_scene_idx = editor_data.get_edited_scene(); + Node *original_edited_scene_root = editor_data.get_edited_scene_root(); + + // Prevent scene roots with the same name from being in the tree at the same time. + scene_root->remove_child(original_edited_scene_root); + + for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) { + // Set the current scene. + int current_scene_idx = edited_scene_map_elem.key; + editor_data.set_edited_scene(current_scene_idx); + Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx); + + // Make sure the node is in the tree so that editor_selection can add node smoothly. + scene_root->add_child(current_edited_scene); + + for (Node *original_node : edited_scene_map_elem.value) { + // Fetching all the modified properties of the nodes reimported scene. + HashMap<NodePath, ModificationNodeEntry> modification_table; + get_preload_scene_modification_table(current_edited_scene, original_node, original_node, modification_table); + + if (modification_table.size() > 0) { + NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node); + scenes_modification_table[current_scene_idx][scene_path_to_node] = modification_table; } } + + scene_root->remove_child(current_edited_scene); } + + editor_data.set_edited_scene(original_edited_scene_idx); + scene_root->add_child(original_edited_scene_root); } +} + +void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) { + HashMap<int, List<Node *>> edited_scene_map; + Array replaced_nodes; + + get_edited_scene_map(p_instance_path, edited_scene_map); if (edited_scene_map.size() > 0) { // Reload the new instance. @@ -5879,6 +5964,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // Load a replacement scene for the node. Ref<PackedScene> current_packed_scene; + Ref<PackedScene> base_packed_scene; if (original_node_file_path == p_instance_path) { // If the node file name directly matches the scene we're replacing, // just load it since we already cached it. @@ -5905,6 +5991,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // Ensure the inheritance chain is loaded in the correct order so that cache can // be properly updated. for (String path : required_load_paths) { + if (current_packed_scene.is_valid()) { + base_packed_scene = current_packed_scene; + } if (!local_scene_cache.find(path)) { current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err); local_scene_cache[path] = current_packed_scene; @@ -5917,15 +6006,58 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins ERR_FAIL_COND(current_packed_scene.is_null()); // Instantiate early so that caches cleared on load in SceneState can be rebuilt early. - Node *instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + Node *instantiated_node = nullptr; + + // If we are in a inherit scene, it's easier to create a new base scene and + // grab the node from there. + // When scene_path_to_node is '.' and we have scene_inherited_state, it's because + // it's a muli-level inheritance scene. We should use + NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node); + Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state(); + if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != p_instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) { + Node *root_node = scene_state->instantiate(SceneState::GenEditState::GEN_EDIT_STATE_INSTANCE); + instantiated_node = root_node->get_node(scene_path_to_node); + + if (instantiated_node) { + if (instantiated_node->get_parent()) { + // Remove from the root so we can delete it from memory. + instantiated_node->get_parent()->remove_child(instantiated_node); + // No need of the additional children that could have been added to the node + // in the base scene. That will be managed by the 'addition_list' later. + _remove_all_not_owned_children(instantiated_node, instantiated_node); + memdelete(root_node); + } + } else { + // Should not happen because we checked with find_node_by_path before, just in case. + memdelete(root_node); + } + } + if (!instantiated_node) { + // If no base scene was found to create the node, we will use the reimported packed scene directly. + // But, when the current edited scene is the reimported scene, it's because it's a inherited scene + // of the reimported scene. In that case, we will not instantiate current_packed_scene, because + // we would reinstanciate ourself. Using the base scene is better. + if (current_edited_scene == original_node) { + if (base_packed_scene.is_valid()) { + instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + } else { + instantiated_node = instance_scene_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + } + } else { + instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + } + } ERR_FAIL_NULL(instantiated_node); // Walk the tree for the current node and extract relevant diff data, storing it in the modification table. // For additional nodes which are part of the current scene, they get added to the addition table. HashMap<NodePath, ModificationNodeEntry> modification_table; List<AdditiveNodeEntry> addition_list; - update_reimported_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list); + if (scenes_modification_table.has(current_scene_idx) && scenes_modification_table[current_scene_idx].has(scene_path_to_node)) { + modification_table = scenes_modification_table[current_scene_idx][scene_path_to_node]; + } + update_reimported_diff_data_for_additional_nodes(current_edited_scene, original_node, original_node, modification_table, addition_list); // Disconnect all relevant connections, all connections from and persistent connections to. for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) { @@ -5978,9 +6110,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins is_editable = owner->is_editable_instance(original_node); } - // For clear instance state for path recaching. - instantiated_node->set_scene_instance_state(Ref<SceneState>()); - bool original_node_is_displayed_folded = original_node->is_displayed_folded(); bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); @@ -5990,16 +6119,11 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins // Is this replacing the edited root node? if (current_edited_scene == original_node) { - instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state()); - // Fix unsaved inherited scene - if (original_node_file_path.is_empty()) { - Ref<SceneState> state = current_packed_scene->get_state(); - state->set_path(current_packed_scene->get_path()); - instantiated_node->set_scene_inherited_state(state); - instantiated_node->set_scene_file_path(String()); - } + // Set the instance as un inherited scene of itself. + instantiated_node->set_scene_inherited_state(instantiated_node->get_scene_instance_state()); + instantiated_node->set_scene_instance_state(nullptr); + instantiated_node->set_scene_file_path(original_node_file_path); current_edited_scene = instantiated_node; - editor_data.set_edited_scene_root(current_edited_scene); } @@ -6051,18 +6175,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins node_3d->set_transform(additive_node_entry.transform_3d); } } - - // Restore the ownership of its ancestors - for (KeyValue<Node *, Node *> &E : additive_node_entry.ownership_table) { - Node *current_ancestor = E.key; - Node *ancestor_owner = E.value; - - if (ancestor_owner == original_node) { - ancestor_owner = instantiated_node; - } - - current_ancestor->set_owner(ancestor_owner); - } } // Restore the selection. @@ -6124,6 +6236,24 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins editor_data.restore_edited_scene_state(editor_selection, &editor_history); } + + scenes_modification_table.clear(); +} + +void EditorNode::_remove_all_not_owned_children(Node *p_node, Node *p_owner) { + Vector<Node *> nodes_to_remove; + if (p_node != p_owner && p_node->get_owner() != p_owner) { + nodes_to_remove.push_back(p_node); + } + for (int i = 0; i < p_node->get_child_count(); i++) { + Node *child_node = p_node->get_child(i); + _remove_all_not_owned_children(child_node, p_owner); + } + + for (Node *node : nodes_to_remove) { + node->get_parent()->remove_child(node); + node->queue_free(); + } } int EditorNode::plugin_init_callback_count = 0; @@ -6428,7 +6558,6 @@ EditorNode::EditorNode() { // No scripting by default if in editor (except for tool). ScriptServer::set_scripting_enabled(false); - Input::get_singleton()->set_use_accumulated_input(true); if (!DisplayServer::get_singleton()->is_touchscreen_available()) { // Only if no touchscreen ui hint, disable emulation just in case. Input::get_singleton()->set_emulate_touch_from_mouse(false); @@ -6478,6 +6607,14 @@ EditorNode::EditorNode() { } { + bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing"); + bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input"); + + Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing); + Input::get_singleton()->set_use_accumulated_input(use_accumulated_input); + } + + { int display_scale = EDITOR_GET("interface/editor/display_scale"); switch (display_scale) { @@ -7564,6 +7701,7 @@ EditorNode::EditorNode() { EditorFileSystem::get_singleton()->connect("sources_changed", callable_mp(this, &EditorNode::_sources_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_fs_changed)); + EditorFileSystem::get_singleton()->connect("resources_reimporting", callable_mp(this, &EditorNode::_resources_reimporting)); EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &EditorNode::_resources_reimported)); EditorFileSystem::get_singleton()->connect("resources_reload", callable_mp(this, &EditorNode::_resources_changed)); diff --git a/editor/editor_node.h b/editor/editor_node.h index 7a26156ab8..4d55eaf1b2 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -556,6 +556,7 @@ private: void _plugin_over_self_own(EditorPlugin *p_plugin); void _fs_changed(); + void _resources_reimporting(const Vector<String> &p_resources); void _resources_reimported(const Vector<String> &p_resources); void _sources_changed(bool p_exist); @@ -675,6 +676,8 @@ private: void _notify_nodes_scene_reimported(Node *p_node, Array p_reimported_nodes); + void _remove_all_not_owned_children(Node *p_node, Node *p_owner); + protected: friend class FileSystemDock; @@ -803,8 +806,6 @@ public: // Used if the original parent node is lost Transform2D transform_2d; Transform3D transform_3d; - // Used to keep track of the ownership of all ancestor nodes so they can be restored later. - HashMap<Node *, Node *> ownership_table; }; struct ConnectionWithNodePath { @@ -819,7 +820,8 @@ public: List<Node::GroupInfo> groups; }; - void update_ownership_table_for_addition_node_ancestors(Node *p_current_node, HashMap<Node *, Node *> &p_ownership_table); + HashMap<int, HashMap<NodePath, HashMap<NodePath, ModificationNodeEntry>>> scenes_modification_table; + void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification); void update_node_reference_modification_table_for_node( @@ -828,12 +830,18 @@ public: List<Node *> p_excluded_nodes, HashMap<NodePath, ModificationNodeEntry> &p_modification_table); - void update_reimported_diff_data_for_node( + void get_preload_scene_modification_table( + Node *p_edited_scene, + Node *p_reimported_root, + Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table); + + void update_reimported_diff_data_for_additional_nodes( Node *p_edited_scene, Node *p_reimported_root, Node *p_node, HashMap<NodePath, ModificationNodeEntry> &p_modification_table, List<AdditiveNodeEntry> &p_addition_list); + bool is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimported_root, Node *p_node); bool is_scene_open(const String &p_path); bool is_multi_window_enabled() const; @@ -888,7 +896,9 @@ public: void reload_scene(const String &p_path); + void get_edited_scene_map(const String &p_instance_path, HashMap<int, List<Node *>> &p_edited_scene_map); void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list); + void preload_reimporting_with_path_in_edited_scenes(const String &p_path); void reload_instances_with_path_in_edited_scenes(const String &p_path); bool is_exiting() const { return exiting; } @@ -908,6 +918,7 @@ public: void save_before_run(); void try_autosave(); void restart_editor(); + void unload_editor_addons(); void dim_editor(bool p_dimming); bool is_editor_dimmed() const; diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index 548eac1737..5d378820ae 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -58,12 +58,12 @@ void EditorRunNative::_notification(int p_what) { for (int j = 0; j < EditorExport::get_singleton()->get_export_platform_count(); j++) { if (eep->get_name() == EditorExport::get_singleton()->get_export_platform(j)->get_name()) { platform_idx = j; + break; } } int dc = MIN(eep->get_options_count(), 9000); - bool needs_templates; String error; - if (dc > 0 && preset->is_runnable() && eep->can_export(preset, error, needs_templates)) { + if (dc > 0 && preset->is_runnable()) { popup->add_icon_item(eep->get_run_icon(), eep->get_name(), -1); popup->set_item_disabled(-1, true); for (int j = 0; j < dc; j++) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 5d3cc80da9..4107048375 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -474,11 +474,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/vsync_mode", 1, "Disabled,Enabled,Adaptive,Mailbox") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/update_continuously", false, "") -#ifdef ANDROID_ENABLED - EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) - EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_input_buffering", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) -#endif - // Inspector EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "") @@ -526,11 +521,13 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { // Touchscreen bool has_touchscreen_ui = DisplayServer::get_singleton()->is_touchscreen_available(); EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", has_touchscreen_ui, "") + set_restart_if_changed("interface/touchscreen/increase_scrollbar_touch_area", true); EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", has_touchscreen_ui, "") set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true); EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui, "") set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true); EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1, "1,5,1") + set_restart_if_changed("interface/touchscreen/scale_gizmo_handles", true); // Scene tabs EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/scene_tabs/display_close_button", 1, "Never,If Tab Active,Always"); // TabBar::CloseButtonDisplayPolicy @@ -866,6 +863,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { /* Extra config */ + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "input/buffering/agile_event_flushing", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "input/buffering/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + // TRANSLATORS: Project Manager here refers to the tool used to create/manage Godot projects. EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/sorting_order", 0, "Last Edited,Name,Path") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/directory_naming_convention", 1, "No convention,kebab-case,snake_case,camelCase,PascalCase,Title Case") diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 9a0f2f18fa..72ab186036 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -423,8 +423,8 @@ EditorExport::EditorExport() { save_timer->set_one_shot(true); save_timer->connect("timeout", callable_mp(this, &EditorExport::_save)); - _export_presets_updated = "export_presets_updated"; - _export_presets_runnable_updated = "export_presets_runnable_updated"; + _export_presets_updated = StringName("export_presets_updated", true); + _export_presets_runnable_updated = StringName("export_presets_runnable_updated", true); singleton = this; set_process(true); diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index c0646dc572..0768ae128b 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1229,8 +1229,12 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & config->set_value("remap", "path", export_path); // Erase useless sections. - config->erase_section("deps"); - config->erase_section("params"); + if (config->has_section("deps")) { + config->erase_section("deps"); + } + if (config->has_section("params")) { + config->erase_section("params"); + } String import_text = config->encode_to_text(); CharString cs = import_text.utf8(); @@ -1294,8 +1298,12 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } // Erase useless sections. - config->erase_section("deps"); - config->erase_section("params"); + if (config->has_section("deps")) { + config->erase_section("deps"); + } + if (config->has_section("params")) { + config->erase_section("params"); + } String import_text = config->encode_to_text(); CharString cs = import_text.utf8(); diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp index 6cfc035fc9..a5f7e8556c 100644 --- a/editor/groups_editor.cpp +++ b/editor/groups_editor.cpp @@ -275,6 +275,11 @@ void GroupsEditor::_queue_update_groups_and_tree() { void GroupsEditor::_update_groups_and_tree() { update_groups_and_tree_queued = false; + // The scene_root_node could be unset before we actually run this code because this is queued with call_deferred(). + // In that case NOTIFICATION_VISIBILITY_CHANGED will call this function again soon. + if (!scene_root_node) { + return; + } _update_groups(); _update_tree(); } diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index 3e74a3c94e..3cb95a3926 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -188,10 +188,11 @@ Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Sh } void EditorBottomPanel::remove_item(Control *p_item) { + bool was_visible = false; for (int i = 0; i < items.size(); i++) { if (items[i].control == p_item) { if (p_item->is_visible_in_tree()) { - _switch_to_item(false, i); + was_visible = true; } item_vbox->remove_child(items[i].control); button_hbox->remove_child(items[i].button); @@ -200,6 +201,16 @@ void EditorBottomPanel::remove_item(Control *p_item) { break; } } + + if (was_visible) { + // Open the first panel to ensure that if the removed dock was visible, the bottom + // panel will not collapse. + _switch_to_item(true, 0); + } else if (last_opened_control == p_item) { + // When a dock is removed by plugins, it might not have been visible, and it + // might have been the last_opened_control. We need to make sure to reset the last opened control. + last_opened_control = items[0].control; + } } void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible) { diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 3e94310c83..afc6d58d63 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -634,8 +634,10 @@ void EditorFileDialog::_item_selected(int p_item) { file->set_text(d["name"]); _request_single_thumbnail(get_current_dir().path_join(get_current_file())); - // FILE_MODE_OPEN_ANY can alternate this text depending on what's selected. - set_ok_button_text(TTR("Open")); + if (mode != FILE_MODE_SAVE_FILE) { + // FILE_MODE_OPEN_ANY can alternate this text depending on what's selected. + set_ok_button_text(TTR("Open")); + } } else if (mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) { file->set_text(""); set_ok_button_text(TTR("Select This Folder")); @@ -1968,6 +1970,7 @@ void EditorFileDialog::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &EditorFileDialog::set_option_name, &EditorFileDialog::get_option_name); base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &EditorFileDialog::set_option_values, &EditorFileDialog::get_option_values); base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &EditorFileDialog::set_option_default, &EditorFileDialog::get_option_default); + PropertyListHelper::register_base_helper(&base_property_helper); } void EditorFileDialog::set_show_hidden_files(bool p_show) { diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 6c85641d3a..4e8d6d63bf 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -36,6 +36,10 @@ #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" +bool EditorSpinSlider::is_text_field() const { + return true; +} + String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const { if (!read_only && grabber->is_visible()) { Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL; diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h index a999f5c48f..a0c0685629 100644 --- a/editor/gui/editor_spin_slider.h +++ b/editor/gui/editor_spin_slider.h @@ -96,6 +96,8 @@ protected: void _focus_entered(); public: + virtual bool is_text_field() const override; + String get_tooltip(const Point2 &p_pos) const override; String get_text_value() const; diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 6d68e93c75..a0c05598a2 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -425,9 +425,13 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, } if (!current_material.is_empty()) { - mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename()); + if (mesh->get_surface_count() >= 1) { + mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename()); + } } else if (!current_group.is_empty()) { - mesh->set_surface_name(mesh->get_surface_count() - 1, current_group); + if (mesh->get_surface_count() >= 1) { + mesh->set_surface_name(mesh->get_surface_count() - 1, current_group); + } } Array array = surf_tool->commit_to_arrays(); diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 2db3fb1f07..5ee4322a22 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -291,14 +291,18 @@ bool ResourceImporterScene::get_option_visibility(const String &p_path, const St for (int i = 0; i < post_importer_plugins.size(); i++) { Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, animation_importer, p_option, p_options); if (ret.get_type() == Variant::BOOL) { - return ret; + if (!ret) { + return false; + } } } for (Ref<EditorSceneFormatImporter> importer : scene_importers) { Variant ret = importer->get_option_visibility(p_path, animation_importer, p_option, p_options); if (ret.get_type() == Variant::BOOL) { - return ret; + if (!ret) { + return false; + } } } diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 846456ea55..a8f8e9ef11 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -431,31 +431,39 @@ void ImportDock::_importer_selected(int i_idx) { void ImportDock::_preset_selected(int p_idx) { int item_id = preset->get_popup()->get_item_id(p_idx); + String setting_name = "importer_defaults/" + params->importer->get_importer_name(); switch (item_id) { case ITEM_SET_AS_DEFAULT: { - Dictionary d; + Dictionary import_settings; + // When import settings already exist, we will update these settings + // to ensure that the dictionary retains settings that are not displayed in the + // editor. For Scene, the dictionary is the same for FBX, GLTF, and Blender, but each + // file type has some different settings. + if (ProjectSettings::get_singleton()->has_setting(setting_name)) { + import_settings = GLOBAL_GET(setting_name); + } for (const PropertyInfo &E : params->properties) { - d[E.name] = params->values[E.name]; + import_settings[E.name] = params->values[E.name]; } - ProjectSettings::get_singleton()->set("importer_defaults/" + params->importer->get_importer_name(), d); + ProjectSettings::get_singleton()->set(setting_name, import_settings); ProjectSettings::get_singleton()->save(); _update_preset_menu(); } break; case ITEM_LOAD_DEFAULT: { - ERR_FAIL_COND(!ProjectSettings::get_singleton()->has_setting("importer_defaults/" + params->importer->get_importer_name())); + ERR_FAIL_COND(!ProjectSettings::get_singleton()->has_setting(setting_name)); - Dictionary d = GLOBAL_GET("importer_defaults/" + params->importer->get_importer_name()); - List<Variant> v; - d.get_key_list(&v); + Dictionary import_settings = GLOBAL_GET(setting_name); + List<Variant> keys; + import_settings.get_key_list(&keys); if (params->checking) { params->checked.clear(); } - for (const Variant &E : v) { - params->values[E] = d[E]; + for (const Variant &E : keys) { + params->values[E] = import_settings[E]; if (params->checking) { params->checked.insert(E); } @@ -463,7 +471,7 @@ void ImportDock::_preset_selected(int p_idx) { params->update(); } break; case ITEM_CLEAR_DEFAULT: { - ProjectSettings::get_singleton()->set("importer_defaults/" + params->importer->get_importer_name(), Variant()); + ProjectSettings::get_singleton()->set(setting_name, Variant()); ProjectSettings::get_singleton()->save(); _update_preset_menu(); } break; diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 02fa582da4..5d60de973a 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -345,6 +345,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { } frame->set_max((double)anim->get_length()); autoplay->set_pressed(current == player->get_autoplay()); + player->stop(); } else { track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 294df95874..f9be1b08d9 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -772,7 +772,6 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po // Reselect if (Engine::get_singleton()->is_editor_hint()) { selected_from_canvas = true; - EditorNode::get_singleton()->edit_node(item); } } } diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index e37619aa20..e54d0c0d1a 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -118,12 +118,6 @@ void CurveEdit::_notification(int p_what) { queue_redraw(); } } break; - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - if (!EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen")) { - break; - } - [[fallthrough]]; - } case NOTIFICATION_THEME_CHANGED: { float gizmo_scale = EDITOR_GET("interface/touchscreen/scale_gizmo_handles"); point_radius = Math::round(BASE_POINT_RADIUS * get_theme_default_base_scale() * gizmo_scale); @@ -1083,10 +1077,8 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons Image &im = **img_ref; im.initialize_data(thumbnail_size.x, thumbnail_size.y, false, Image::FORMAT_RGBA8); - Color bg_color(0.1, 0.1, 0.1, 1.0); - Color line_color(0.8, 0.8, 0.8, 1.0); + Color line_color = EditorInterface::get_singleton()->get_editor_theme()->get_color(SceneStringName(font_color), EditorStringName(Editor)); - im.fill(bg_color); // Set the first pixel of the thumbnail. float v = (curve->sample_baked(0) - curve->get_min_value()) / curve->get_range(); int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 2289105d78..1c17d99d0d 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -110,6 +110,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { 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.")); } 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.")); + } break; default: { } break; } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 96127ec93e..070471f3f3 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -317,7 +317,16 @@ void ScriptTextEditor::_error_clicked(const Variant &p_line) { if (!scr.is_valid()) { EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); } else { - ScriptEditor::get_singleton()->edit(scr, line, column); + int corrected_column = column; + + const String line_text = code_editor->get_text_editor()->get_line(line); + const int indent_size = code_editor->get_text_editor()->get_indent_size(); + if (indent_size > 1) { + const int tab_count = line_text.length() - line_text.lstrip("\t").length(); + corrected_column -= tab_count * (indent_size - 1); + } + + ScriptEditor::get_singleton()->edit(scr, line, corrected_column); } } } diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 4a59530159..b76e673414 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -163,7 +163,10 @@ void TileMapLayerEditorTilesPlugin::_update_tile_set_sources_list() { int old_source = -1; if (old_current > -1) { old_source = sources_list->get_item_metadata(old_current); + } else { + old_source = sources_list->get_meta("old_source", -1); } + sources_list->set_meta("old_source", old_source); sources_list->clear(); TileMapLayer *edited_layer = _get_edited_layer(); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 2f36198b23..3215f85293 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -6499,8 +6499,9 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("LessThan (<)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than (<)")), { VisualShaderNodeCompare::FUNC_LESS_THAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("LessThanEqual (<=)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), { VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("NotEqual (!=)", "Conditional/Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), { VisualShaderNodeCompare::FUNC_NOT_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("Switch (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 3D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Switch2D (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 2D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SwitchVector2D (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 2D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SwitchVector3D (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 3D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SwitchVector4D (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 4D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); add_options.push_back(AddOption("SwitchBool (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated boolean if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_BOOLEAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); add_options.push_back(AddOption("SwitchFloat (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated floating-point scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_FLOAT }, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("SwitchInt (==)", "Conditional/Functions", "VisualShaderNodeSwitch", TTR("Returns an associated integer scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_INT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); @@ -6904,8 +6905,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("ProximityFade", "Utility", "VisualShaderNodeProximityFade", TTR("The proximity fade effect fades out each pixel based on its distance to another object."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("RandomRange", "Utility", "VisualShaderNodeRandomRange", TTR("Returns a random value between the minimum and maximum input values."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("Remap", "Utility", "VisualShaderNodeRemap", TTR("Remaps a given input from the input range to the output range."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("RotationByAxis", "Utility", "VisualShaderNodeRotationByAxis", TTR("Rotates an input vector by a given angle."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("RotationByAxis", "Utility", "VisualShaderNodeRotationByAxis", TTR("Rotates an input vector by a given angle."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("RotationByAxis", "Utility", "VisualShaderNodeRotationByAxis", TTR("Builds a rotation matrix from the given axis and angle, multiply the input vector by it and returns both this vector and a matrix."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); // VECTOR diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 52d7b14b65..59f45ef5db 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1053,6 +1053,14 @@ ProjectManager::ProjectManager() { } EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come. + { + bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing"); + bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input"); + + Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing); + Input::get_singleton()->set_use_accumulated_input(use_accumulated_input); + } + int display_scale = EDITOR_GET("interface/editor/display_scale"); switch (display_scale) { diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 262a1ecc1a..52d86a1a95 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -315,6 +315,8 @@ void ProjectDialog::_create_dir_toggled(bool p_pressed) { target_path = target_path.path_join(last_custom_target_dir); } } else { + // Strip any trailing slash. + target_path = target_path.rstrip("/\\"); // Save and remove target dir name. if (target_path.get_file() == auto_dir) { last_custom_target_dir = ""; @@ -387,6 +389,8 @@ void ProjectDialog::_browse_install_path() { } void ProjectDialog::_project_path_selected(const String &p_path) { + show_dialog(); + if (create_dir->is_pressed() && (mode == MODE_NEW || mode == MODE_INSTALL)) { // Replace parent directory, but keep target dir name. project_path->set_text(p_path.path_join(project_path->get_text().get_file())); @@ -684,8 +688,6 @@ void ProjectDialog::set_project_path(const String &p_path) { } void ProjectDialog::ask_for_path_and_show() { - // Workaround: for the file selection dialog content to be rendered we need to show its parent dialog. - show_dialog(); _browse_project_path(); } @@ -785,6 +787,14 @@ void ProjectDialog::_notification(int p_what) { project_browse->set_icon(get_editor_theme_icon(SNAME("FolderBrowse"))); install_browse->set_icon(get_editor_theme_icon(SNAME("FolderBrowse"))); } break; + case NOTIFICATION_READY: { + fdialog_project = memnew(EditorFileDialog); + fdialog_project->set_previews_enabled(false); // Crucial, otherwise the engine crashes. + fdialog_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + fdialog_project->connect("dir_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); + fdialog_project->connect("file_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); + callable_mp((Node *)this, &Node::add_sibling).call_deferred(fdialog_project, false); + } break; } } @@ -965,21 +975,14 @@ ProjectDialog::ProjectDialog() { Control *spacer = memnew(Control); spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); default_files_container->add_child(spacer); - - fdialog_project = memnew(EditorFileDialog); - fdialog_project->set_previews_enabled(false); //Crucial, otherwise the engine crashes. - fdialog_project->set_access(EditorFileDialog::ACCESS_FILESYSTEM); fdialog_install = memnew(EditorFileDialog); fdialog_install->set_previews_enabled(false); //Crucial, otherwise the engine crashes. fdialog_install->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - add_child(fdialog_project); add_child(fdialog_install); project_name->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_project_name_changed).unbind(1)); project_path->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_project_path_changed).unbind(1)); install_path->connect(SceneStringName(text_changed), callable_mp(this, &ProjectDialog::_install_path_changed).unbind(1)); - fdialog_project->connect("dir_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); - fdialog_project->connect("file_selected", callable_mp(this, &ProjectDialog::_project_path_selected)); fdialog_install->connect("dir_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 94bd3e16d3..d0f1fbfae7 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -149,7 +149,8 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) { + Control *focus_owner = get_viewport()->gui_get_focus_owner(); + if (focus_owner && focus_owner->is_text_field()) { return; } @@ -158,7 +159,11 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) { } if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) { - _tool_selected(TOOL_RENAME); + // Prevent renaming if a button is focused + // to avoid conflict with Enter shortcut on macOS + if (!focus_owner || !Object::cast_to<BaseButton>(focus_owner)) { + _tool_selected(TOOL_RENAME); + } #ifdef MODULE_REGEX_ENABLED } else if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { _tool_selected(TOOL_BATCH_RENAME); @@ -985,6 +990,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { undo_redo->add_do_method(root, "set_scene_file_path", String()); undo_redo->add_do_method(node, "set_owner", (Object *)nullptr); undo_redo->add_do_method(root, "set_owner", node); + undo_redo->add_do_method(node, "set_unique_name_in_owner", false); _node_replace_owner(root, root, node, MODE_DO); undo_redo->add_undo_method(root, "set_scene_file_path", root->get_scene_file_path()); @@ -995,6 +1001,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { undo_redo->add_undo_method(node->get_parent(), "move_child", node, node->get_index(false)); undo_redo->add_undo_method(root, "set_owner", (Object *)nullptr); undo_redo->add_undo_method(node, "set_owner", root); + undo_redo->add_undo_method(node, "set_unique_name_in_owner", node->is_unique_name_in_owner()); _node_replace_owner(root, root, root, MODE_UNDO); undo_redo->add_do_method(scene_tree, "update_tree"); @@ -2276,6 +2283,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V Vector<StringName> former_names; int inc = 0; + bool need_edit = false; for (int ni = 0; ni < p_nodes.size(); ni++) { // No undo implemented for this yet. @@ -2296,7 +2304,11 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V inc--; // If the child will generate a gap when moved, adjust. } - if (!same_parent) { + if (same_parent) { + // When node is reparented to the same parent, EditorSelection does not change. + // After hovering another node, the inspector has to be manually updated in this case. + need_edit = select_node_hovered_at_end_of_drag; + } else { undo_redo->add_do_method(node->get_parent(), "remove_child", node); undo_redo->add_do_method(new_parent, "add_child", node, true); } @@ -2401,6 +2413,10 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V perform_node_renames(nullptr, &path_renames); undo_redo->commit_action(); + + if (need_edit) { + EditorNode::get_singleton()->edit_current(); + } } void SceneTreeDock::_script_created(Ref<Script> p_script) { diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 03752656c0..a63b6d4e14 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -2652,8 +2652,6 @@ bool EditorThemeManager::is_generated_theme_outdated() { EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/font") || EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/main_font") || EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/code_font") || - EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/increase_scrollbar_touch_area") || - EditorSettings::get_singleton()->check_changed_settings_in_group("interface/touchscreen/scale_gizmo_handles") || EditorSettings::get_singleton()->check_changed_settings_in_group("editors/visual_editors") || EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme") || EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help/help") || diff --git a/main/main.cpp b/main/main.cpp index 060b3fe2f6..870f7d31b8 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -60,6 +60,7 @@ #include "platform/register_platform_apis.h" #include "scene/main/scene_tree.h" #include "scene/main/window.h" +#include "scene/property_list_helper.h" #include "scene/register_scene_types.h" #include "scene/resources/packed_scene.h" #include "scene/theme/theme_db.h" @@ -793,6 +794,7 @@ void Main::test_cleanup() { ResourceLoader::remove_custom_loaders(); ResourceSaver::remove_custom_savers(); + PropertyListHelper::clear_base_helpers(); #ifdef TOOLS_ENABLED GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_EDITOR); @@ -2514,7 +2516,6 @@ error: memdelete(message_queue); } - OS::get_singleton()->benchmark_end_measure("Startup", "Core"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup"); #if defined(STEAMAPI_ENABLED) @@ -2915,8 +2916,6 @@ Error Main::setup2(bool p_show_boot_logo) { MAIN_PRINT("Main: Clear Color"); DisplayServer::set_early_window_clear_color_override(false); - RenderingServer::get_singleton()->set_default_clear_color( - GLOBAL_GET("rendering/environment/defaults/default_clear_color")); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), String()); GLOBAL_DEF(PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns"), String()); @@ -2926,7 +2925,8 @@ Error Main::setup2(bool p_show_boot_logo) { Input *id = Input::get_singleton(); if (id) { - agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); + bool agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); + id->set_agile_input_event_flushing(agile_input_event_flushing); if (bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { @@ -2939,8 +2939,6 @@ Error Main::setup2(bool p_show_boot_logo) { id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true))); } - GLOBAL_DEF("input_devices/buffering/android/use_accumulated_input", true); - GLOBAL_DEF("input_devices/buffering/android/use_input_buffering", true); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); @@ -3217,6 +3215,8 @@ void Main::setup_boot_logo() { } #endif } + RenderingServer::get_singleton()->set_default_clear_color( + GLOBAL_GET("rendering/environment/defaults/default_clear_color")); } String Main::get_rendering_driver_name() { @@ -3977,7 +3977,6 @@ uint32_t Main::hide_print_fps_attempts = 3; uint32_t Main::frame = 0; bool Main::force_redraw_requested = false; int Main::iterating = 0; -bool Main::agile_input_event_flushing = false; bool Main::is_iterating() { return iterating > 0; @@ -4038,7 +4037,7 @@ bool Main::iteration() { NavigationServer3D::get_singleton()->sync(); for (int iters = 0; iters < advance.physics_steps; ++iters) { - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { + if (Input::get_singleton()->is_agile_input_event_flushing()) { Input::get_singleton()->flush_buffered_events(); } @@ -4095,7 +4094,7 @@ bool Main::iteration() { Engine::get_singleton()->_in_physics = false; } - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { + if (Input::get_singleton()->is_agile_input_event_flushing()) { Input::get_singleton()->flush_buffered_events(); } @@ -4108,7 +4107,7 @@ bool Main::iteration() { RenderingServer::get_singleton()->sync(); //sync if still drawing from previous frames. - if (DisplayServer::get_singleton()->can_any_window_draw() && + if ((DisplayServer::get_singleton()->can_any_window_draw() || DisplayServer::get_singleton()->has_additional_outputs()) && RenderingServer::get_singleton()->is_render_loop_enabled()) { if ((!force_redraw_requested) && OS::get_singleton()->is_in_low_processor_usage_mode()) { if (RenderingServer::get_singleton()->has_changed()) { @@ -4167,16 +4166,17 @@ bool Main::iteration() { iterating--; - // Needed for OSs using input buffering regardless accumulation (like Android) - if (Input::get_singleton()->is_using_input_buffering() && !agile_input_event_flushing) { - Input::get_singleton()->flush_buffered_events(); - } - if (movie_writer) { movie_writer->add_frame(); } +#ifdef TOOLS_ENABLED + bool quit_after_timeout = false; +#endif if ((quit_after > 0) && (Engine::get_singleton()->_process_frames >= quit_after)) { +#ifdef TOOLS_ENABLED + quit_after_timeout = true; +#endif exit = true; } @@ -4209,6 +4209,12 @@ bool Main::iteration() { } #endif +#ifdef TOOLS_ENABLED + if (exit && quit_after_timeout && EditorNode::get_singleton()) { + EditorNode::get_singleton()->unload_editor_addons(); + } +#endif + return exit; } @@ -4246,6 +4252,7 @@ void Main::cleanup(bool p_force) { ResourceLoader::remove_custom_loaders(); ResourceSaver::remove_custom_savers(); + PropertyListHelper::clear_base_helpers(); // Flush before uninitializing the scene, but delete the MessageQueue as late as possible. message_queue->flush(); diff --git a/main/main.h b/main/main.h index b1cfcd3c2d..6dd2ff7d7a 100644 --- a/main/main.h +++ b/main/main.h @@ -58,7 +58,6 @@ class Main { static uint32_t frame; static bool force_redraw_requested; static int iterating; - static bool agile_input_event_flushing; public: static bool is_cmdline_tool(); diff --git a/methods.py b/methods.py index 99c47ca077..b0f7df9ab2 100644 --- a/methods.py +++ b/methods.py @@ -268,7 +268,7 @@ def get_version_info(module_version_string="", silent=False): if os.path.exists(".git"): try: version_info["git_timestamp"] = subprocess.check_output( - ["git", "log", "-1", "--pretty=format:%ct", githash] + ["git", "log", "-1", "--pretty=format:%ct", "--no-show-signature", githash] ).decode("utf-8") except (subprocess.CalledProcessError, OSError): # `git` not found in PATH. @@ -648,6 +648,7 @@ def detect_visual_c_compiler_version(tools_env): def find_visual_c_batch_file(env): + # TODO: We should investigate if we can avoid relying on SCons internals here. from SCons.Tool.MSCommon.vc import find_batch_file, find_vc_pdir, get_default_version, get_host_target msvc_version = get_default_version(env) @@ -661,10 +662,11 @@ def find_visual_c_batch_file(env): if env.scons_version < (4, 6, 0): return find_batch_file(env, msvc_version, host_platform, target_platform)[0] - # Scons 4.6.0+ removed passing env, so we need to get the product_dir ourselves first, + # SCons 4.6.0+ removed passing env, so we need to get the product_dir ourselves first, # then pass that as the last param instead of env as the first param as before. - # We should investigate if we can avoid relying on SCons internals here. - product_dir = find_vc_pdir(env, msvc_version) + # Param names need to be explicit, as they were shuffled around in SCons 4.8.0. + product_dir = find_vc_pdir(msvc_version=msvc_version, env=env) + return find_batch_file(msvc_version, host_platform, target_platform, product_dir)[0] diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 4b0d22a1aa..ce8f24c7a9 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -254,13 +254,6 @@ Validate extension JSON: API was removed: classes/VisualShaderNodeComment/method Validate extension JSON: API was removed: classes/VisualShaderNodeComment/properties/title -GH-87888 --------- -Validate extension JSON: API was removed: classes/Skeleton3D/properties/animate_physical_bones - -These base class is changed to SkeletonModifier3D which is processed by Skeleton3D with the assumption that it is Skeleton3D's child. - - GH-90575 -------- Validate extension JSON: API was removed: classes/BoneAttachment3D/methods/on_bone_pose_update @@ -379,3 +372,11 @@ GH-93982 Validate extension JSON: Error: Field 'classes/Sprite3D/properties/frame_coords': type changed value in new API, from "Vector2" to "Vector2i". The type was wrong to begin with and has been corrected. Vector2 and Vector2i are convertible, so it should be compatible. + + +GH-94243 +-------- +Validate extension JSON: Error: Field 'classes/Image/methods/get_mipmap_offset/return_value': meta changed value in new API, from "int32" to "int64". + +Type changed to int64_t to support baking large lightmaps. +No compatibility method needed, both GDExtension and C# generate it as int64_t anyway. diff --git a/misc/scripts/install_d3d12_sdk_windows.py b/misc/scripts/install_d3d12_sdk_windows.py index d7574e6222..2e5e4fce18 100755 --- a/misc/scripts/install_d3d12_sdk_windows.py +++ b/misc/scripts/install_d3d12_sdk_windows.py @@ -25,12 +25,6 @@ if deps_folder: else: deps_folder = os.path.join("bin", "build_deps") -# DirectX Shader Compiler -# Check for latest version: https://github.com/microsoft/DirectXShaderCompiler/releases/latest -dxc_version = "v1.8.2403.2" -dxc_filename = "dxc_2024_03_29.zip" -dxc_archive = os.path.join(deps_folder, dxc_filename) -dxc_folder = os.path.join(deps_folder, "dxc") # Mesa NIR # Check for latest version: https://github.com/godotengine/godot-nir-static/releases/latest mesa_version = "23.1.9" @@ -54,25 +48,8 @@ agility_sdk_folder = os.path.join(deps_folder, "agility_sdk") if not os.path.exists(deps_folder): os.makedirs(deps_folder) -# DirectX Shader Compiler -print("\x1b[1m[1/4] DirectX Shader Compiler\x1b[0m") -if os.path.isfile(dxc_archive): - os.remove(dxc_archive) -print(f"Downloading DirectX Shader Compiler {dxc_filename} ...") -urllib.request.urlretrieve( - f"https://github.com/microsoft/DirectXShaderCompiler/releases/download/{dxc_version}/{dxc_filename}", - dxc_archive, -) -if os.path.exists(dxc_folder): - print(f"Removing existing local DirectX Shader Compiler installation in {dxc_folder} ...") - shutil.rmtree(dxc_folder) -print(f"Extracting DirectX Shader Compiler {dxc_filename} to {dxc_folder} ...") -shutil.unpack_archive(dxc_archive, dxc_folder) -os.remove(dxc_archive) -print(f"DirectX Shader Compiler {dxc_filename} installed successfully.\n") - # Mesa NIR -print("\x1b[1m[2/4] Mesa NIR\x1b[0m") +print("\x1b[1m[1/3] Mesa NIR\x1b[0m") if os.path.isfile(mesa_archive): os.remove(mesa_archive) print(f"Downloading Mesa NIR {mesa_filename} ...") @@ -99,7 +76,7 @@ if dlltool == "": dlltool = shutil.which("x86_64-w64-mingw32-dlltool") or "" has_mingw = gendef != "" and dlltool != "" -print("\x1b[1m[3/4] WinPixEventRuntime\x1b[0m") +print("\x1b[1m[2/3] WinPixEventRuntime\x1b[0m") if os.path.isfile(pix_archive): os.remove(pix_archive) print(f"Downloading WinPixEventRuntime {pix_version} ...") @@ -130,7 +107,7 @@ else: print(f"WinPixEventRuntime {pix_version} installed successfully.\n") # DirectX 12 Agility SDK -print("\x1b[1m[4/4] DirectX 12 Agility SDK\x1b[0m") +print("\x1b[1m[3/3] DirectX 12 Agility SDK\x1b[0m") if os.path.isfile(agility_sdk_archive): os.remove(agility_sdk_archive) print(f"Downloading DirectX 12 Agility SDK {agility_sdk_version} ...") diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp index 216fa6c9a2..fc1f01ae50 100644 --- a/modules/basis_universal/image_compress_basisu.cpp +++ b/modules/basis_universal/image_compress_basisu.cpp @@ -120,7 +120,8 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha Vector<uint32_t> mip_data_padded; for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { - int ofs, size, width, height; + int64_t ofs, size; + int width, height; image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); const uint8_t *image_mip_data = image_data.ptr() + ofs; diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index ebd1577172..84534fec06 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -51,7 +51,7 @@ <param index="3" name="data" type="int" default="0" /> <description> Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer. - [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] before calling this method. + [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] on both ends before calling this method. </description> </method> <method name="create_host"> @@ -61,7 +61,9 @@ <param index="2" name="in_bandwidth" type="int" default="0" /> <param index="3" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost that will allow up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth]. + Creates an ENetHost that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + This method binds a random available dynamic UDP port on the host machine at the [i]unspecified[/i] address. Use [method create_host_bound] to specify the address and port. + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="create_host_bound"> @@ -73,7 +75,8 @@ <param index="4" name="in_bandwidth" type="int" default="0" /> <param index="5" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost like [method create_host] which is also bound to the given [param bind_address] and [param bind_port]. + Creates an ENetHost bound to the given [param bind_address] and [param bind_port] that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="destroy"> @@ -141,8 +144,9 @@ <return type="Array" /> <param index="0" name="timeout" type="int" default="0" /> <description> - Waits for events on the specified host and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. + Waits for events on this connection and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. Call this function regularly to handle connections, disconnections, and to receive new packets. + [b]Note:[/b] This method must be called on both ends involved in the event (sending and receiving hosts). </description> </method> <method name="socket_send"> diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index afb63246e4..daa4db37df 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -126,10 +126,9 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) { - if (p_option == "fbx/embedded_image_handling") { - return false; - } - if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF && p_option == "fbx/embedded_image_handling") { + // Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf. + // These options are available only for ufbx. + if (p_option.begins_with("fbx/") && p_option != "fbx/importer" && p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF) { return false; } return true; diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index fb5e324390..4d5f220539 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -76,9 +76,6 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { state->set_import_as_skeleton_bones(true); } - if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { - state->set_import_as_skeleton_bones(true); - } p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; state->set_bake_fps(p_options["animation/fps"]); Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir()); @@ -93,21 +90,17 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if (file_extension != "fbx" && p_option.begins_with("fbx/")) { - return false; - } - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || p_path.get_extension().to_lower() == "fbx") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 73b1e44db3..eaf2565e69 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1537,10 +1537,14 @@ void GDScript::clear(ClearData *p_clear_data) { } } - RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); - for (GDScript *E : must_clear_dependencies) { - clear_data->scripts.insert(E); - E->clear(clear_data); + // If we're in the process of shutting things down then every single script will be cleared + // anyway, so we can safely skip this very costly operation. + if (!GDScriptLanguage::singleton->finishing) { + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + for (GDScript *E : must_clear_dependencies) { + clear_data->scripts.insert(E); + E->clear(clear_data); + } } for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { @@ -2246,6 +2250,11 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { + if (finishing) { + return; + } + finishing = true; + _call_stack.free(); // Clear the cache before parsing the script_list @@ -2281,6 +2290,8 @@ void GDScriptLanguage::finish() { } script_list.clear(); function_list.clear(); + + finishing = false; } void GDScriptLanguage::profiling_start() { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d097cb193b..4e78fbe302 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -411,6 +411,8 @@ class GDScriptLanguage : public ScriptLanguage { static GDScriptLanguage *singleton; + bool finishing = false; + Variant *_global_array = nullptr; Vector<Variant> global_array; HashMap<StringName, int> globals; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a6b4bce000..67b40a6198 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5180,7 +5180,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (!class_exists(base_native)) { push_error(vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native), p_source); return false; - } else if (p_is_constructor && !ClassDB::can_instantiate(base_native)) { + } else if (p_is_constructor && ClassDB::is_abstract(base_native)) { if (p_base_type.kind == GDScriptParser::DataType::CLASS) { push_error(vformat(R"(Class "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.class_type->fqcn.get_file(), base_native), p_source); } else if (p_base_type.kind == GDScriptParser::DataType::SCRIPT) { diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 5469dad3f7..b0ac4aa800 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1064,12 +1064,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Get at (potential) root stack pos, so it can be returned. GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + if (r_error) { return GDScriptCodeGenerator::Address(); } GDScriptCodeGenerator::Address prev_base = base; + // In case the base has a setter, don't use the address directly, as we want to call that setter. + // So use a temp value instead and call the setter at the end. + GDScriptCodeGenerator::Address base_temp; + if (base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) { + base_temp = codegen.add_temporary(base.type); + gen->write_assign(base_temp, base); + prev_base = base_temp; + } + struct ChainInfo { bool is_named = false; GDScriptCodeGenerator::Address base; @@ -1218,6 +1228,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_end_jump_if_shared(); } } + } else if (base_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + // Save the temp value back to the base by calling its setter. + gen->write_call(GDScriptCodeGenerator::Address(), base, member_property_setter_function, { assigned }); } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index b58b44973e..b1ffc02e4b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1521,22 +1521,19 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, G } if (scr.is_valid()) { ci.type.script_path = scr->get_path(); + ci.type.script_type = scr; + ci.type.native_type = scr->get_instance_base_type(); + ci.type.kind = GDScriptParser::DataType::SCRIPT; if (scr->get_path().ends_with(".gd")) { - Error err; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(scr->get_path()); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.class_type = parser->get_parser()->get_tree(); ci.type.kind = GDScriptParser::DataType::CLASS; - p_context.dependent_parsers.push_back(parser); return ci; } } - - ci.type.kind = GDScriptParser::DataType::SCRIPT; - ci.type.script_type = scr; - ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; } @@ -1811,8 +1808,6 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (mb && mb->is_const()) { bool all_is_const = true; Vector<Variant> args; - GDScriptParser::CompletionContext c2 = p_context; - c2.current_line = call->start_line; for (int i = 0; all_is_const && i < call->arguments.size(); i++) { GDScriptCompletionIdentifier arg; @@ -1849,16 +1844,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } if (FileAccess::exists(script)) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); found = true; } } @@ -2310,9 +2303,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); if (script.to_lower().ends_with(".gd")) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); @@ -2320,7 +2312,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); return true; } } else { diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 42b0b066e1..2162a727b3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -198,6 +198,10 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const { return object->get_instance_id(); } +StringName GDScriptLambdaSelfCallable::get_method() const { + return function->get_name(); +} + int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const { if (function == nullptr) { r_is_valid = false; diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 45c0235913..2d27b8d679 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -87,6 +87,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a1ea94667d..54bb152f7f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -260,6 +260,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } @@ -277,6 +278,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_suite = current_suite; context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; + context.parser = this; completion_context = context; } @@ -413,7 +415,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String } tokenizer = buffer_tokenizer; - script_path = p_script_path; + script_path = p_script_path.simplify_path(); current = tokenizer->scan(); // Avoid error or newline as the first token. // The latter can mess with the parser when opening files filled exclusively with comments and newlines. diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 21942222cf..60ee477656 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1314,7 +1314,7 @@ public: Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; - List<Ref<GDScriptParserRef>> dependent_parsers; + GDScriptParser *parser = nullptr; }; struct CompletionCall { diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 5b1639e250..404b61fb40 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -574,7 +574,9 @@ GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() { if (len == 1 && _peek(-1) == '_') { // Lone underscore. - return make_token(Token::UNDERSCORE); + Token token = make_token(Token::UNDERSCORE); + token.literal = "_"; + return token; } String name(_start, len); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 5d1805696d..5eab8a6306 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -550,9 +550,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg; - Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); - memnew_placement(&stack[i + 3], Variant(arg)); + if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { + const GDScriptDataType &arg_type = argument_types[i].container_element_types[0]; + Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type); + memnew_placement(&stack[i + 3], Variant(array)); + } else { + Variant variant; + Variant::construct(argument_types[i].builtin_type, variant, &p_args[i], 1, r_err); + if (unlikely(r_err.error)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].builtin_type; + call_depth--; + return _get_default_variant_for_data_type(return_type); + } + memnew_placement(&stack[i + 3], Variant(variant)); + } } else { memnew_placement(&stack[i + 3], Variant(*p_args[i])); } @@ -1794,7 +1807,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1885,7 +1898,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out index 7b9f1066b0..9b38957101 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out @@ -1,4 +1,5 @@ GDTEST_RUNTIME_ERROR >> ERROR >> Method/function failed. +>> Unable to convert array index 0 from "Object" to "Object". not ok diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd new file mode 100644 index 0000000000..160e43a797 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd @@ -0,0 +1,21 @@ +# https://github.com/godotengine/godot/issues/94074 + +func foo(): + pass + +func test(): + var lambda_self := func test() -> void: + foo() + var anon_lambda_self := func() -> void: + foo() + + print(lambda_self.get_method()) # Should print "test". + print(anon_lambda_self.get_method()) # Should print "<anonymous lambda>". + + var lambda_non_self := func test() -> void: + pass + var anon_lambda_non_self := func() -> void: + pass + + print(lambda_non_self.get_method()) # Should print "test". + print(anon_lambda_non_self.get_method()) # Should print "<anonymous lambda>". diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out new file mode 100644 index 0000000000..17ee47fca2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out @@ -0,0 +1,5 @@ +GDTEST_OK +test +<anonymous lambda> +test +<anonymous lambda> diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd new file mode 100644 index 0000000000..9e27a500bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/85952 + +var vec: Vector2 = Vector2.ZERO: + set(new_vec): + prints("setting vec from", vec, "to", new_vec) + if new_vec == Vector2(1, 1): + vec = new_vec + +func test(): + vec.x = 2 + vec.y = 2 + + prints("vec is", vec) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out new file mode 100644 index 0000000000..31b3b3a3a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out @@ -0,0 +1,4 @@ +GDTEST_OK +setting vec from (0, 0) to (2, 0) +setting vec from (0, 0) to (0, 2) +vec is (0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd new file mode 100644 index 0000000000..b07c40b6da --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -0,0 +1,15 @@ +extends Node + +func test() -> void: + var node1 := Node.new() + node1.name = "_" + var node2 := Node.new() + node2.name = "Child" + var node3 := Node.new() + node3.name = "Child" + + add_child(node1) + node1.add_child(node2) + add_child(node3) + + assert(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd new file mode 100644 index 0000000000..13f2c3b956 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd @@ -0,0 +1,7 @@ +# GH-93990 + +func test_param(array: Array[String]) -> void: + print(array.get_typed_builtin() == TYPE_STRING) + +func test() -> void: + test_param(PackedStringArray()) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out new file mode 100644 index 0000000000..55482c2b52 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out @@ -0,0 +1,2 @@ +GDTEST_OK +true diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index b474128fd6..f70e440781 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -332,7 +332,8 @@ Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_pa } void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - if (p_path.get_extension().to_lower() != "blend") { + // Returns all the options when path is empty because that means it's for the Project Settings. + if (!p_path.is_empty() && p_path.get_extension().to_lower() != "blend") { return; } #define ADD_OPTION_BOOL(PATH, VALUE) \ diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index b38c64de01..ce7e17d361 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -84,8 +84,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + String file_extension = p_path.get_extension().to_lower(); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || file_extension == "gltf" || file_extension == "glb") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { @@ -98,10 +102,6 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index c0232e6d0c..dff1e62e82 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -789,8 +789,9 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_ ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. + ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri); buffer_data = FileAccess::get_file_as_bytes(uri); - ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); + ERR_FAIL_COND_V_MSG(buffer_data.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 01764d66ed..d7762295eb 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -976,6 +976,8 @@ void AudioStreamPlaybackInteractive::switch_to_clip_by_name(const StringName &p_ return; } + ERR_FAIL_COND_MSG(stream.is_null(), "Attempted to switch while not playing back any stream."); + for (int i = 0; i < stream->get_clip_count(); i++) { if (stream->get_clip_name(i) == p_name) { switch_request = i; diff --git a/modules/interactive_music/register_types.cpp b/modules/interactive_music/register_types.cpp index 5baea13f81..6175ea6493 100644 --- a/modules/interactive_music/register_types.cpp +++ b/modules/interactive_music/register_types.cpp @@ -42,11 +42,11 @@ void initialize_interactive_music_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { GDREGISTER_CLASS(AudioStreamPlaylist); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackPlaylist); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPlaylist); GDREGISTER_CLASS(AudioStreamInteractive); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackInteractive); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackInteractive); GDREGISTER_CLASS(AudioStreamSynchronized); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackSynchronized); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackSynchronized); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 7ac7bd8088..33b0b0d015 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -233,14 +233,14 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ MeshInstance &mi = mesh_instances.write[m_i]; Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()); sizes.push_back(s); - atlas_size = atlas_size.max(s + Size2i(2, 2)); + atlas_size = atlas_size.max(s + Size2i(2, 2).maxi(p_denoiser_range)); } int max = nearest_power_of_2_templated(atlas_size.width); max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); if (max > p_max_texture_size) { - return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + return BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE; } if (p_step_function) { @@ -254,19 +254,27 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ int best_atlas_memory = 0x7FFFFFFF; Vector<Vector3i> best_atlas_offsets; - //determine best texture array atlas size by bruteforce fitting + // Determine best texture array atlas size by bruteforce fitting. while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) { Vector<Vector2i> source_sizes; Vector<int> source_indices; source_sizes.resize(sizes.size()); source_indices.resize(sizes.size()); for (int i = 0; i < source_indices.size(); i++) { - source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps + source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps. source_indices.write[i] = i; } Vector<Vector3i> atlas_offsets; atlas_offsets.resize(source_sizes.size()); + // Ensure the sizes can all fit into a single atlas layer. + // This should always happen, and this check is only in place to prevent an infinite loop. + for (int i = 0; i < source_sizes.size(); i++) { + if (source_sizes[i] > atlas_size) { + return BAKE_ERROR_ATLAS_TOO_SMALL; + } + } + int slices = 0; while (source_sizes.size() > 0) { diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index 2579697d05..1266457113 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -127,9 +127,12 @@ Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char * void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { OpenXRInteractionProfileMetadata *pmd = OpenXRInteractionProfileMetadata::get_singleton(); - ERR_FAIL_NULL(pmd); - - interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + if (pmd) { + interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + } else { + // OpenXR module not enabled, ignore checks. + interaction_profile_path = p_input_profile_path; + } emit_changed(); } diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index 937973f388..a353073f21 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -248,26 +248,27 @@ void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) { Error err = OK; - action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); - if (err != OK) { - if ((err == ERR_FILE_NOT_FOUND || err == ERR_CANT_OPEN) && p_create_new_if_missing) { - action_map.instantiate(); - action_map->create_default_action_sets(); - - // Save it immediately - err = ResourceSaver::save(action_map, p_path); - if (err != OK) { - // show warning but continue - EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); - } - - } else { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(p_path)) { + action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err != OK) { EditorNode::get_singleton()->show_warning(vformat(TTR("Error loading %s: %s."), edited_path, error_names[err])); edited_path = ""; header_label->set_text(""); return; } + } else if (p_create_new_if_missing) { + action_map.instantiate(); + action_map->create_default_action_sets(); + action_map->set_path(p_path); + + // Save it immediately + err = ResourceSaver::save(action_map, p_path); + if (err != OK) { + // Show warning but continue. + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); + } } edited_path = p_path; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 748ef3af94..81e9deb7ab 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -454,7 +454,6 @@ public: _FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; } _FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; } _FORCE_INLINE_ bool can_render() { - ERR_ON_RENDER_THREAD_V(false); return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender; } diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index fbbc61a91c..cce9c09361 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -651,6 +651,10 @@ bool OpenXRInterface::initialize() { // make this our primary interface xr_server->set_primary_interface(this); + // Register an additional output with the display server, so rendering won't + // be skipped if no windows are visible. + DisplayServer::get_singleton()->register_additional_output(this); + initialized = true; return initialized; @@ -674,6 +678,8 @@ void OpenXRInterface::uninitialize() { } } + DisplayServer::get_singleton()->unregister_additional_output(this); + initialized = false; } diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp index fba76621d6..72640cd936 100644 --- a/modules/squish/image_decompress_squish.cpp +++ b/modules/squish/image_decompress_squish.cpp @@ -77,10 +77,11 @@ void image_decompress_squish(Image *p_image) { } for (int i = 0; i <= mm_count; i++) { - int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0; + int64_t src_ofs = 0, mipmap_size = 0; + int mipmap_w = 0, mipmap_h = 0; p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); - int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); + int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); w >>= 1; diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 06b304dcde..8dc0e869d0 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -651,7 +651,6 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis #endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread r_error = OK; } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 479158c91f..a0da9019c6 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -441,6 +441,7 @@ void EditorExportPlatformAndroid::_update_preset_status() { } else { has_runnable_preset.clear(); } + devices_changed.set(); } #endif diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index dad397de61..5515347bd6 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -117,10 +117,6 @@ open class GodotEditor : GodotActivity() { val longPressEnabled = enableLongPressGestures() val panScaleEnabled = enablePanAndScaleGestures() - val useInputBuffering = useInputBuffering() - val useAccumulatedInput = useAccumulatedInput() - GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) - checkForProjectPermissionsToEnable() runOnUiThread { @@ -128,7 +124,6 @@ open class GodotEditor : GodotActivity() { godotFragment?.godot?.renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) - enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) } } } @@ -280,13 +275,6 @@ open class GodotEditor : GodotActivity() { java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) /** - * Use input buffering for the Godot Android editor. - */ - protected open fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_input_buffering")) - - protected open fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_accumulated_input")) - - /** * Whether we should launch the new godot instance in an adjacent window * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT */ diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index f50b5577c3..2bcfba559c 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -45,10 +45,6 @@ class GodotGame : GodotEditor() { override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - override fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) - - override fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) - override fun checkForProjectPermissionsToEnable() { // Nothing to do.. by the time we get here, the project permissions will have already // been requested by the Editor window. diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index c188a97ca5..7e2a44ab39 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -628,26 +628,19 @@ class Godot(private val context: Context) : SensorEventListener { private fun onGodotSetupCompleted() { Log.v(TAG, "OnGodotSetupCompleted") - if (!isEditorBuild()) { - // These properties are defined after Godot setup completion, so we retrieve them here. - val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) - val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") - - val useInputBuffering = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) - val useAccumulatedInput = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) - GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) - - runOnUiThread { - renderView?.inputHandler?.apply { - enableLongPress(longPressEnabled) - enablePanningAndScalingGestures(panScaleEnabled) - enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) - try { - setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) - } catch (e: NumberFormatException) { - Log.w(TAG, e) - } + // These properties are defined after Godot setup completion, so we retrieve them here. + val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) + val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) + val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") + + runOnUiThread { + renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + try { + setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) + } catch (e: NumberFormatException) { + Log.w(TAG, e) } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 37e889daf7..909daf05c9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -242,9 +242,8 @@ public class GodotLib { public static native void onRendererPaused(); /** - * Invoked on the GL thread to update the input dispatch settings - * @param useAccumulatedInput True to use accumulated input, false otherwise - * @param useInputBuffering True to use input buffering, false otherwise + * @return true if input must be dispatched from the render thread. If false, input is + * dispatched from the UI thread. */ - public static native void updateInputDispatchSettings(boolean useAccumulatedInput, boolean useInputBuffering); + public static native boolean shouldDispatchInputToRenderThread(); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt index 4cd3bd8db9..2929a0a0b0 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt @@ -76,7 +76,10 @@ internal class GodotGestureHandler(private val inputHandler: GodotInputHandler) } override fun onLongPress(event: MotionEvent) { - contextClickRouter(event) + val toolType = GodotInputHandler.getEventToolType(event) + if (toolType != MotionEvent.TOOL_TYPE_MOUSE) { + contextClickRouter(event) + } } private fun contextClickRouter(event: MotionEvent) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 889618914d..273774a33d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -77,8 +77,6 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; - private boolean dispatchInputToRenderThread = false; - public GodotInputHandler(GodotRenderView godotView) { final Context context = godotView.getView().getContext(); mRenderView = godotView; @@ -111,19 +109,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { } /** - * Specifies whether input should be dispatch on the UI thread or on the Render thread. - * @param enable true to dispatch input on the Render thread, false to dispatch input on the UI thread - */ - public void enableInputDispatchToRenderThread(boolean enable) { - this.dispatchInputToRenderThread = enable; - } - - /** * @return true if input must be dispatched from the render thread. If false, input is * dispatched from the UI thread. */ private boolean shouldDispatchInputToRenderThread() { - return dispatchInputToRenderThread; + return GodotLib.shouldDispatchInputToRenderThread(); } /** @@ -472,7 +462,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { return button; } - private static int getEventToolType(MotionEvent event) { + static int getEventToolType(MotionEvent event) { return event.getPointerCount() > 0 ? event.getToolType(0) : MotionEvent.TOOL_TYPE_UNKNOWN; } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 87d4281c5a..11e897facf 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -550,10 +550,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) { - if (Input::get_singleton()) { - Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input); - Input::get_singleton()->set_use_input_buffering(p_use_input_buffering); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz) { + Input *input = Input::get_singleton(); + if (input) { + return !input->is_agile_input_event_flushing(); } + return false; } } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 852c475e7e..d027da31fa 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz); } #endif // JAVA_GODOT_LIB_JNI_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 490f73a36d..5b65c8b485 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -2971,6 +2971,7 @@ void EditorExportPlatformIOS::_update_preset_status() { } else { has_runnable_preset.clear(); } + devices_changed.set(); } #endif diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm index 8b614662b7..4067701a41 100644 --- a/platform/ios/keyboard_input_view.mm +++ b/platform/ios/keyboard_input_view.mm @@ -149,23 +149,18 @@ return; } + NSString *substringToDelete = nil; if (self.previousSelectedRange.length == 0) { - // We are deleting all text before cursor if no range was selected. - // This way any inserted or changed text will be updated. - NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; - [self deleteText:substringToDelete.length]; + // Get previous text to delete. + substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location]; } else { - // If text was previously selected - // we are sending only one `backspace`. - // It will remove all text from text input. + // If text was previously selected we are sending only one `backspace`. It will remove all text from text input. [self deleteText:1]; } - NSString *substringToEnter; - + NSString *substringToEnter = nil; if (self.selectedRange.length == 0) { - // If previous cursor had a selection - // we have to calculate an inserted text. + // If previous cursor had a selection we have to calculate an inserted text. if (self.previousSelectedRange.length != 0) { NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length; NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location); @@ -187,7 +182,18 @@ substringToEnter = [self.text substringWithRange:self.selectedRange]; } - [self enterText:substringToEnter]; + NSInteger skip = 0; + if (substringToDelete != nil) { + for (NSInteger i = 0; i < MIN([substringToDelete length], [substringToEnter length]); i++) { + if ([substringToDelete characterAtIndex:i] == [substringToEnter characterAtIndex:i]) { + skip++; + } else { + break; + } + } + [self deleteText:[substringToDelete length] - skip]; // Delete changed part of previous text. + } + [self enterText:[substringToEnter substringFromIndex:skip]]; // Enter changed part of new text. self.previousText = self.text; self.previousSelectedRange = self.selectedRange; diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 3534c1afee..a67428b9a4 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -374,6 +374,12 @@ void JoypadLinux::open_joypad(const char *p_path) { name = namebuf; } + for (const String &word : name.to_lower().split(" ")) { + if (banned_words.has(word)) { + return; + } + } + if (ioctl(fd, EVIOCGID, &inpid) < 0) { close(fd); return; diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index 26a9908d4e..bf24d8e5a5 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -94,6 +94,21 @@ private: Vector<String> attached_devices; + // List of lowercase words that will prevent the controller from being recognized if its name matches. + // This is done to prevent trackpads, graphics tablets and motherboard LED controllers from being + // recognized as controllers (and taking up controller ID slots as a result). + // Only whole words are matched within the controller name string. The match is case-insensitive. + const Vector<String> banned_words = { + "touchpad", // Matches e.g. "SynPS/2 Synaptics TouchPad", "Sony Interactive Entertainment DualSense Wireless Controller Touchpad" + "trackpad", + "clickpad", + "keyboard", // Matches e.g. "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control" + "mouse", // Matches e.g. "Mouse passthrough" + "pen", // Matches e.g. "Wacom One by Wacom S Pen" + "finger", // Matches e.g. "Wacom HID 495F Finger" + "led", // Matches e.g. "ASRock LED Controller" + }; + static void monitor_joypads_thread_func(void *p_user); void monitor_joypads_thread_run(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index adc9beed66..93096fcdcc 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1238,7 +1238,7 @@ void DisplayServerWayland::process_events() { } else { try_suspend(); } - } else if (wayland_thread.get_reset_frame()) { + } else if (!wayland_thread.is_suspended() || wayland_thread.get_reset_frame()) { // At last, a sign of life! We're no longer suspended. suspended = false; } diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 7bdc75db29..dea8bae438 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -2047,6 +2047,11 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z SeatState *ss = (SeatState *)data; ERR_FAIL_NULL(ss); + if (!ss->pointed_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + PointerData &pd = ss->pointer_data_buffer; WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); @@ -2249,13 +2254,11 @@ void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { return; } @@ -2275,14 +2278,17 @@ void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_too } void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { - TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); + if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) { + // We're probably on a decoration or something. + return; + } + TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); if (!ts) { return; } SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { return; } @@ -2304,13 +2310,12 @@ void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_table void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - - if (!ts) { + if (!ts || !ts->data_pending.proximal_surface) { + // Not our stuff, we don't care. return; } SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { return; } @@ -2331,7 +2336,6 @@ void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tabl void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } @@ -2349,7 +2353,6 @@ void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } @@ -2365,11 +2368,15 @@ void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } + if (!ts->data_pending.proximal_surface) { + // We're probably on a decoration or some other third-party thing. + return; + } + WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface); ERR_FAIL_NULL(ws); @@ -2386,7 +2393,6 @@ void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t pressure) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } @@ -2400,7 +2406,6 @@ void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_to void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } @@ -2425,7 +2430,6 @@ void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } @@ -2461,13 +2465,11 @@ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *wp_tablet_tool_v2, uint32_t time) { TabletToolState *ts = wp_tablet_tool_get_state(wp_tablet_tool_v2); - if (!ts) { return; } SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - if (!ss) { return; } @@ -3245,6 +3247,8 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid zxdg_exported_v1_add_listener(ws.xdg_exported, &xdg_exported_listener, &ws); } + wl_surface_commit(ws.wl_surface); + // Wait for the surface to be configured before continuing. wl_display_roundtrip(wl_display); } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index edf3a40ccb..d6eb101a68 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1519,7 +1519,7 @@ Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const { if (image) { XColor c; c.pixel = XGetPixel(image, 0, 0); - XFree(image); + XDestroyImage(image); XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c); color = Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0); break; @@ -1637,11 +1637,12 @@ Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const { } } } else { - XFree(image); - ERR_FAIL_V_MSG(Ref<Image>(), vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel)); + String msg = vformat("XImage with RGB mask %x %x %x and depth %d is not supported.", (uint64_t)image->red_mask, (uint64_t)image->green_mask, (uint64_t)image->blue_mask, (int64_t)image->bits_per_pixel); + XDestroyImage(image); + ERR_FAIL_V_MSG(Ref<Image>(), msg); } img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); - XFree(image); + XDestroyImage(image); } return img; diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 73e2f2d45b..a934e991a1 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1213,6 +1213,7 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres const String &p_ent_path, const String &p_helper_ent_path, bool p_should_error_on_non_code) { static Vector<String> extensions_to_sign; + bool sandbox = p_preset->get("codesign/entitlements/app_sandbox/enabled"); if (extensions_to_sign.is_empty()) { extensions_to_sign.push_back("dylib"); extensions_to_sign.push_back("framework"); @@ -1239,7 +1240,7 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres if (extensions_to_sign.has(current_file.get_extension())) { String ent_path = p_ent_path; bool set_bundle_id = false; - if (FileAccess::exists(current_file_path)) { + if (sandbox && FileAccess::exists(current_file_path)) { int ftype = MachO::get_filetype(current_file_path); if (ftype == 2 || ftype == 5) { ent_path = p_helper_ent_path; @@ -1274,7 +1275,7 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access const String &p_in_app_path, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, const String &p_helper_ent_path, - bool p_should_error_on_non_code_sign) { + bool p_should_error_on_non_code_sign, bool p_sandbox) { static Vector<String> extensions_to_sign; if (extensions_to_sign.is_empty()) { @@ -1368,7 +1369,7 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access if (extensions_to_sign.has(p_in_app_path.get_extension())) { String ent_path = p_ent_path; bool set_bundle_id = false; - if (FileAccess::exists(p_in_app_path)) { + if (p_sandbox && FileAccess::exists(p_in_app_path)) { int ftype = MachO::get_filetype(p_in_app_path); if (ftype == 2 || ftype == 5) { ent_path = p_helper_ent_path; @@ -1389,13 +1390,13 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access Error EditorExportPlatformMacOS::_export_macos_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name, Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, - const String &p_ent_path, const String &p_helper_ent_path) { + const String &p_ent_path, const String &p_helper_ent_path, bool p_sandbox) { Error error{ OK }; const Vector<String> &macos_plugins{ p_editor_export_plugin->get_macos_plugin_files() }; for (int i = 0; i < macos_plugins.size(); ++i) { String src_path{ ProjectSettings::get_singleton()->globalize_path(macos_plugins[i]) }; String path_in_app{ p_app_path_name + "/Contents/PlugIns/" + src_path.get_file() }; - error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, p_helper_ent_path, false); + error = _copy_and_sign_files(dir_access, src_path, path_in_app, p_sign_enabled, p_preset, p_ent_path, p_helper_ent_path, false, p_sandbox); if (error != OK) { break; } @@ -2168,11 +2169,11 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); if (shared_objects[i].target.is_empty()) { String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(); - err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, hlp_ent_path, true); + err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, hlp_ent_path, true, sandbox); } else { String path_in_app = tmp_app_path_name.path_join(shared_objects[i].target); tmp_app_dir->make_dir_recursive(path_in_app); - err = _copy_and_sign_files(da, src_path, path_in_app.path_join(src_path.get_file()), sign_enabled, p_preset, ent_path, hlp_ent_path, false); + err = _copy_and_sign_files(da, src_path, path_in_app.path_join(src_path.get_file()), sign_enabled, p_preset, ent_path, hlp_ent_path, false, sandbox); } if (err != OK) { break; @@ -2181,7 +2182,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p Vector<Ref<EditorExportPlugin>> export_plugins{ EditorExport::get_singleton()->get_export_plugins() }; for (int i = 0; i < export_plugins.size(); ++i) { - err = _export_macos_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path, hlp_ent_path); + err = _export_macos_plugins_for(export_plugins[i], tmp_app_path_name, da, sign_enabled, p_preset, ent_path, hlp_ent_path, sandbox); if (err != OK) { break; } diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index 6134d756b9..1929fc8d5f 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -94,10 +94,10 @@ class EditorExportPlatformMacOS : public EditorExportPlatform { Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, const String &p_helper_ent_path, bool p_should_error_on_non_code = true); Error _copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path, const String &p_helper_ent_path, - bool p_should_error_on_non_code_sign); + bool p_should_error_on_non_code_sign, bool p_sandbox); Error _export_macos_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name, Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, - const String &p_ent_path, const String &p_helper_ent_path); + const String &p_ent_path, const String &p_helper_ent_path, bool p_sandbox); Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name); Error _create_pkg(const Ref<EditorExportPreset> &p_preset, const String &p_pkg_path, const String &p_app_path_name); Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path); diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 77f3a28ae7..7d43ac9fe6 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -329,8 +329,9 @@ Callable::CallError ce; wd.drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(wd.drop_files_callback, v_args, 1, ce))); + ERR_FAIL_V_MSG(NO, vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(wd.drop_files_callback, v_args, 1, ce))); } + return YES; } return NO; diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index b24c6cb1fd..22487d2756 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -65,19 +65,7 @@ void AudioDriverWeb::_sample_playback_finished_callback(const char *p_playback_o return; } - Object *player_object = ObjectDB::get_instance(playback->player_id); - if (player_object == nullptr) { - return; - } - Node *player = Object::cast_to<Node>(player_object); - if (player == nullptr) { - return; - } - - const StringName finished = SNAME("finished"); - if (player->has_signal(finished)) { - player->emit_signal(finished); - } + AudioServer::get_singleton()->stop_sample_playback(playback); } void AudioDriverWeb::_audio_driver_process(int p_from, int p_samples) { diff --git a/platform/web/detect.py b/platform/web/detect.py index cb4dac1125..79485ea28a 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -78,6 +78,7 @@ def get_flags(): # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about # 100 KiB over -Os, which does not justify the negative impact on # run-time performance. + # Note that this overrides the "auto" behavior for target/dev_build. "optimize": "size", } diff --git a/platform/windows/SCsub b/platform/windows/SCsub index f2fb8616ae..f8ed8b73f5 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -108,18 +108,6 @@ if env["d3d12"]: # Used in cases where we can have multiple archs side-by-side. arch_bin_dir = "#bin/" + env["arch"] - # DXC - if env["dxc_path"] != "" and os.path.exists(env["dxc_path"]): - dxc_dll = "dxil.dll" - # Whether this one is loaded from arch-specific directory or not can be determined at runtime. - # Let's copy to both and let the user decide the distribution model. - for v in ["#bin", arch_bin_dir]: - env.Command( - v + "/" + dxc_dll, - env["dxc_path"] + "/bin/" + dxc_arch_subdir + "/" + dxc_dll, - Copy("$TARGET", "$SOURCE"), - ) - # Agility SDK if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]): agility_dlls = ["D3D12Core.dll", "d3d12SDKLayers.dll"] diff --git a/platform/windows/detect.py b/platform/windows/detect.py index fa94846416..3671bbef08 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -214,11 +214,6 @@ def get_opts(): os.path.join(d3d12_deps_folder, "mesa"), ), ( - "dxc_path", - "Path to the DirectX Shader Compiler distribution (required for D3D12)", - os.path.join(d3d12_deps_folder, "dxc"), - ), - ( "agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", os.path.join(d3d12_deps_folder, "agility_sdk"), @@ -592,7 +587,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): "libEGL.windows." + env["arch"] + prebuilt_lib_extra_suffix, "libGLES.windows." + env["arch"] + prebuilt_lib_extra_suffix, ] - LIBS += ["dxgi", "d3d9", "d3d11"] + LIBS += ["dxgi", "d3d9", "d3d11", "synchronization"] env.Prepend(CPPPATH=["#thirdparty/angle/include"]) if env["target"] in ["editor", "template_debug"]: @@ -649,7 +644,8 @@ def configure_mingw(env: "SConsEnvironment"): # TODO: Re-evaluate the need for this / streamline with common config. if env["target"] == "template_release": - env.Append(CCFLAGS=["-msse2"]) + if env["arch"] != "arm64": + env.Append(CCFLAGS=["-msse2"]) elif env.dev_build: # Allow big objects. It's supposed not to have drawbacks but seems to break # GCC LTO, so enabling for debug builds only (which are not built with LTO @@ -817,7 +813,7 @@ def configure_mingw(env: "SConsEnvironment"): "ANGLE.windows." + env["arch"], ] ) - env.Append(LIBS=["dxgi", "d3d9", "d3d11"]) + env.Append(LIBS=["dxgi", "d3d9", "d3d11", "synchronization"]) env.Prepend(CPPPATH=["#thirdparty/angle/include"]) env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 1f8748fa3d..f29048b16d 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2009,7 +2009,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) } if (p_mode == WINDOW_MODE_WINDOWED) { - ShowWindow(wd.hWnd, SW_RESTORE); + ShowWindow(wd.hWnd, SW_NORMAL); wd.maximized = false; wd.minimized = false; } @@ -2911,24 +2911,67 @@ Key DisplayServerWindows::keyboard_get_label_from_physical(Key p_keycode) const return p_keycode; } -String _get_full_layout_name_from_registry(HKL p_layout) { - String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0"); +String DisplayServerWindows::_get_keyboard_layout_display_name(const String &p_klid) const { String ret; + HKEY key; + if (RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key) != ERROR_SUCCESS) { + return String(); + } - HKEY hkey; - WCHAR layout_text[1024]; - memset(layout_text, 0, 1024 * sizeof(WCHAR)); - - if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { - return ret; + WCHAR buffer[MAX_PATH] = {}; + DWORD buffer_size = MAX_PATH; + if (RegGetValueW(key, (LPCWSTR)p_klid.utf16().get_data(), L"Layout Display Name", RRF_RT_REG_SZ, nullptr, buffer, &buffer_size) == ERROR_SUCCESS) { + if (load_indirect_string) { + if (load_indirect_string(buffer, buffer, buffer_size, nullptr) == S_OK) { + ret = String::utf16((const char16_t *)buffer, buffer_size); + } + } + } else { + if (RegGetValueW(key, (LPCWSTR)p_klid.utf16().get_data(), L"Layout Text", RRF_RT_REG_SZ, nullptr, buffer, &buffer_size) == ERROR_SUCCESS) { + ret = String::utf16((const char16_t *)buffer, buffer_size); + } } - DWORD buffer = 1024; - DWORD vtype = REG_SZ; - if (RegQueryValueExW(hkey, L"Layout Text", nullptr, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { - ret = String::utf16((const char16_t *)layout_text); + RegCloseKey(key); + return ret; +} + +String DisplayServerWindows::_get_klid(HKL p_hkl) const { + String ret; + + WORD device = HIWORD(p_hkl); + if ((device & 0xf000) == 0xf000) { + WORD layout_id = device & 0x0fff; + + HKEY key; + if (RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", &key) != ERROR_SUCCESS) { + return String(); + } + + DWORD index = 0; + wchar_t klid_buffer[KL_NAMELENGTH]; + DWORD klid_buffer_size = KL_NAMELENGTH; + while (RegEnumKeyExW(key, index, klid_buffer, &klid_buffer_size, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) { + wchar_t layout_id_buf[MAX_PATH] = {}; + DWORD layout_id_size = MAX_PATH; + if (RegGetValueW(key, klid_buffer, L"Layout Id", RRF_RT_REG_SZ, nullptr, layout_id_buf, &layout_id_size) == ERROR_SUCCESS) { + if (layout_id == String::utf16((char16_t *)layout_id_buf, layout_id_size).hex_to_int()) { + ret = String::utf16((const char16_t *)klid_buffer, klid_buffer_size).lpad(8, "0"); + break; + } + } + klid_buffer_size = KL_NAMELENGTH; + ++index; + } + + RegCloseKey(key); + } else { + if (device == 0) { + device = LOWORD(p_hkl); + } + ret = (String::num_uint64((uint64_t)device, 16, false)).lpad(8, "0"); } - RegCloseKey(hkey); + return ret; } @@ -2940,7 +2983,7 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const { HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL)); GetKeyboardLayoutList(layout_count, layouts); - String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine). + String ret = _get_keyboard_layout_display_name(_get_klid(layouts[p_index])); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine). if (ret.is_empty()) { WCHAR buf[LOCALE_NAME_MAX_LENGTH]; memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR)); @@ -3808,9 +3851,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ACTIVATE: { // Activation can happen just after the window has been created, even before the callbacks are set. // Therefore, it's safer to defer the delivery of the event. - if (!windows[window_id].activate_timer_id) { - windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); - } + // It's important to set an nIDEvent different from the SetTimer for move_timer_id because + // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned. + windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam); return 0; } break; @@ -4161,6 +4204,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; + if (windows[window_id].window_focused || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); } @@ -4187,13 +4231,118 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA break; } + pointer_button[GET_POINTERID_WPARAM(wParam)] = MouseButton::NONE; windows[window_id].block_mm = true; return 0; } break; case WM_POINTERLEAVE: { + pointer_button[GET_POINTERID_WPARAM(wParam)] = MouseButton::NONE; windows[window_id].block_mm = false; return 0; } break; + case WM_POINTERDOWN: + case WM_POINTERUP: { + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { + break; + } + + if ((tablet_get_current_driver() != "winink") || !winink_available) { + break; + } + + Ref<InputEventMouseButton> mb; + mb.instantiate(); + mb->set_window_id(window_id); + + BitField<MouseButtonMask> last_button_state = 0; + if (IS_POINTER_FIRSTBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::LEFT); + mb->set_button_index(MouseButton::LEFT); + } + if (IS_POINTER_SECONDBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::RIGHT); + mb->set_button_index(MouseButton::RIGHT); + } + if (IS_POINTER_THIRDBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MIDDLE); + mb->set_button_index(MouseButton::MIDDLE); + } + if (IS_POINTER_FOURTHBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1); + mb->set_button_index(MouseButton::MB_XBUTTON1); + } + if (IS_POINTER_FIFTHBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2); + mb->set_button_index(MouseButton::MB_XBUTTON2); + } + mb->set_button_mask(last_button_state); + + const BitField<WinKeyModifierMask> &mods = _get_mods(); + mb->set_ctrl_pressed(mods.has_flag(WinKeyModifierMask::CTRL)); + mb->set_shift_pressed(mods.has_flag(WinKeyModifierMask::SHIFT)); + mb->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT)); + mb->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META)); + + POINT coords; // Client coords. + coords.x = GET_X_LPARAM(lParam); + coords.y = GET_Y_LPARAM(lParam); + + // Note: Handle popup closing here, since mouse event is not emulated and hook will not be called. + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup; + if (delta > 250) { + Point2i pos = Point2i(coords.x, coords.y) - _get_screens_origin(); + List<WindowID>::Element *C = nullptr; + List<WindowID>::Element *E = popup_list.back(); + // Find top popup to close. + while (E) { + // Popup window area. + Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get())); + // Area of the parent window, which responsible for opening sub-menu. + Rect2i safe_rect = window_get_popup_safe_rect(E->get()); + if (win_rect.has_point(pos)) { + break; + } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) { + break; + } else { + C = E; + E = E->prev(); + } + } + if (C) { + _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST); + } + } + + int64_t pen_id = GET_POINTERID_WPARAM(wParam); + if (uMsg == WM_POINTERDOWN) { + mb->set_pressed(true); + if (pointer_down_time.has(pen_id) && (pointer_prev_button[pen_id] == mb->get_button_index()) && (ABS(coords.y - pointer_last_pos[pen_id].y) < GetSystemMetrics(SM_CYDOUBLECLK)) && GetMessageTime() - pointer_down_time[pen_id] < (LONG)GetDoubleClickTime()) { + mb->set_double_click(true); + pointer_down_time[pen_id] = 0; + } else { + pointer_down_time[pen_id] = GetMessageTime(); + pointer_prev_button[pen_id] = mb->get_button_index(); + pointer_last_pos[pen_id] = Vector2(coords.x, coords.y); + } + pointer_button[pen_id] = mb->get_button_index(); + } else { + if (!pointer_button.has(pen_id)) { + return 0; + } + mb->set_pressed(false); + mb->set_button_index(pointer_button[pen_id]); + pointer_button[pen_id] = MouseButton::NONE; + } + + ScreenToClient(windows[window_id].hWnd, &coords); + + mb->set_position(Vector2(coords.x, coords.y)); + mb->set_global_position(Vector2(coords.x, coords.y)); + + Input::get_singleton()->parse_input_event(mb); + + return 0; + } break; case WM_POINTERUPDATE: { if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { break; @@ -4271,7 +4420,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_alt_pressed(mods.has_flag(WinKeyModifierMask::ALT)); mm->set_meta_pressed(mods.has_flag(WinKeyModifierMask::META)); - mm->set_button_mask(mouse_get_button_state()); + BitField<MouseButtonMask> last_button_state = 0; + if (IS_POINTER_FIRSTBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::LEFT); + } + if (IS_POINTER_SECONDBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::RIGHT); + } + if (IS_POINTER_THIRDBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MIDDLE); + } + if (IS_POINTER_FOURTHBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON1); + } + if (IS_POINTER_FIFTHBUTTON_WPARAM(wParam)) { + last_button_state.set_flag(MouseButtonMask::MB_XBUTTON2); + } + mm->set_button_mask(last_button_state); POINT coords; // Client coords. coords.x = GET_X_LPARAM(lParam); @@ -4442,6 +4607,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(mm->get_position() - window_get_position(receiving_window_id) + window_get_position(window_id)); mm->set_global_position(mm->get_position()); } + Input::get_singleton()->parse_input_event(mm); } break; @@ -4728,7 +4894,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); - windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_MOVE_REDRAW, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); } break; case WM_EXITSIZEMOVE: { KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id); @@ -5247,6 +5413,9 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, ::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } + RECT real_client_rect; + GetClientRect(wd.hWnd, &real_client_rect); + #ifdef RD_ENABLED if (rendering_context) { union { @@ -5276,7 +5445,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, return INVALID_WINDOW_ID; } - rendering_context->window_set_size(id, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top); + rendering_context->window_set_size(id, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top); rendering_context->window_set_vsync_mode(id, p_vsync_mode); wd.context_created = true; } @@ -5284,7 +5453,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, #ifdef GLES3_ENABLED if (gl_manager_native) { - if (gl_manager_native->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) { + if (gl_manager_native->window_create(id, wd.hWnd, hInstance, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top) != OK) { memdelete(gl_manager_native); gl_manager_native = nullptr; windows.erase(id); @@ -5294,7 +5463,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, } if (gl_manager_angle) { - if (gl_manager_angle->window_create(id, nullptr, wd.hWnd, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) { + if (gl_manager_angle->window_create(id, nullptr, wd.hWnd, real_client_rect.right - real_client_rect.left, real_client_rect.bottom - real_client_rect.top) != OK) { memdelete(gl_manager_angle); gl_manager_angle = nullptr; windows.erase(id); @@ -5433,6 +5602,9 @@ GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr; LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr; PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr; +// Shell API, +SHLoadIndirectStringPtr DisplayServerWindows::load_indirect_string = nullptr; + Vector2i _get_device_ids(const String &p_device_name) { if (p_device_name.is_empty()) { return Vector2i(); @@ -5606,6 +5778,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win FreeLibrary(nt_lib); } + // Load Shell API. + HMODULE shellapi_lib = LoadLibraryW(L"shlwapi.dll"); + if (shellapi_lib) { + load_indirect_string = (SHLoadIndirectStringPtr)GetProcAddress(shellapi_lib, "SHLoadIndirectString"); + } + // Load UXTheme, available on Windows 10+ only. if (os_ver.dwBuildNumber >= 10240) { HMODULE ux_theme_lib = LoadLibraryW(L"uxtheme.dll"); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 382f18c239..650f0412ea 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -207,6 +207,50 @@ typedef UINT32 PEN_MASK; #define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 #endif +#ifndef POINTER_MESSAGE_FLAG_SECONDBUTTON +#define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 +#endif + +#ifndef POINTER_MESSAGE_FLAG_THIRDBUTTON +#define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 +#endif + +#ifndef POINTER_MESSAGE_FLAG_FOURTHBUTTON +#define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 +#endif + +#ifndef POINTER_MESSAGE_FLAG_FIFTHBUTTON +#define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 +#endif + +#ifndef IS_POINTER_FLAG_SET_WPARAM +#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) (((DWORD)HIWORD(wParam) & (flag)) == (flag)) +#endif + +#ifndef IS_POINTER_FIRSTBUTTON_WPARAM +#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON) +#endif + +#ifndef IS_POINTER_SECONDBUTTON_WPARAM +#define IS_POINTER_SECONDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON) +#endif + +#ifndef IS_POINTER_THIRDBUTTON_WPARAM +#define IS_POINTER_THIRDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON) +#endif + +#ifndef IS_POINTER_FOURTHBUTTON_WPARAM +#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON) +#endif + +#ifndef IS_POINTER_FIFTHBUTTON_WPARAM +#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON) +#endif + +#ifndef GET_POINTERID_WPARAM +#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam)) +#endif + #if WINVER < 0x0602 enum tagPOINTER_INPUT_TYPE { PT_POINTER = 0x00000001, @@ -274,10 +318,19 @@ typedef struct tagPOINTER_PEN_INFO { #define WM_POINTERLEAVE 0x024A #endif +#ifndef WM_POINTERDOWN +#define WM_POINTERDOWN 0x0246 +#endif + +#ifndef WM_POINTERUP +#define WM_POINTERUP 0x0247 +#endif + typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint); +typedef HRESULT(WINAPI *SHLoadIndirectStringPtr)(PCWSTR pszSource, PWSTR pszOutBuf, UINT cchOutBuf, void **ppvReserved); typedef struct { BYTE bWidth; // Width, in pixels, of the image @@ -328,10 +381,18 @@ class DisplayServerWindows : public DisplayServer { static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI; static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI; + // Shell API + static SHLoadIndirectStringPtr load_indirect_string; + void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver); String tablet_driver; Vector<String> tablet_drivers; + enum TimerID { + TIMER_ID_MOVE_REDRAW = 1, + TIMER_ID_WINDOW_ACTIVATION = 2, + }; + enum { KEY_EVENT_BUFFER_SIZE = 512 }; @@ -474,6 +535,11 @@ class DisplayServerWindows : public DisplayServer { IndicatorID indicator_id_counter = 0; HashMap<IndicatorID, IndicatorData> indicators; + HashMap<int64_t, MouseButton> pointer_prev_button; + HashMap<int64_t, MouseButton> pointer_button; + HashMap<int64_t, LONG> pointer_down_time; + HashMap<int64_t, Vector2> pointer_last_pos; + void _send_window_event(const WindowData &wd, WindowEvent p_event); void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); @@ -526,6 +592,9 @@ class DisplayServerWindows : public DisplayServer { Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb); + String _get_keyboard_layout_display_name(const String &p_klid) const; + String _get_klid(HKL p_hkl) const; + public: LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam); diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml index 06b272c10e..9e2db756ce 100644 --- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml +++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml @@ -26,7 +26,7 @@ If set to [code]1[/code], ANGLE libraries are exported with the exported application. If set to [code]0[/code], ANGLE libraries are exported only if [member ProjectSettings.rendering/gl_compatibility/driver] is set to [code]"opengl3_angle"[/code]. </member> <member name="application/export_d3d12" type="int" setter="" getter=""> - If set to [code]1[/code], Direct3D 12 runtime (DXIL, Agility SDK, PIX) libraries are exported with the exported application. If set to [code]0[/code], Direct3D 12 libraries are exported only if [member ProjectSettings.rendering/rendering_device/driver] is set to [code]"d3d12"[/code]. + If set to [code]1[/code], the Direct3D 12 runtime libraries (Agility SDK, PIX) are exported with the exported application. If set to [code]0[/code], Direct3D 12 libraries are exported only if [member ProjectSettings.rendering/rendering_device/driver] is set to [code]"d3d12"[/code]. </member> <member name="application/file_description" type="String" setter="" getter=""> File description to be presented to users. Required. See [url=https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block]StringFileInfo[/url]. diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 6ce9d27dc5..12694b0155 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -208,18 +208,14 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> int export_d3d12 = p_preset->get("application/export_d3d12"); bool agility_sdk_multiarch = p_preset->get("application/d3d12_agility_sdk_multiarch"); - bool include_dxil_libs = false; + bool include_d3d12_extra_libs = false; if (export_d3d12 == 0) { - include_dxil_libs = (String(GLOBAL_GET("rendering/rendering_device/driver.windows")) == "d3d12") && (String(GLOBAL_GET("rendering/renderer/rendering_method")) != "gl_compatibility"); + include_d3d12_extra_libs = (String(GLOBAL_GET("rendering/rendering_device/driver.windows")) == "d3d12") && (String(GLOBAL_GET("rendering/renderer/rendering_method")) != "gl_compatibility"); } else if (export_d3d12 == 1) { - include_dxil_libs = true; + include_d3d12_extra_libs = true; } - if (include_dxil_libs) { + if (include_d3d12_extra_libs) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da->file_exists(template_path.get_base_dir().path_join("dxil." + arch + ".dll"))) { - da->make_dir_recursive(p_path.get_base_dir().path_join(arch)); - da->copy(template_path.get_base_dir().path_join("dxil." + arch + ".dll"), p_path.get_base_dir().path_join(arch).path_join("dxil.dll"), get_chmod_flags()); - } if (da->file_exists(template_path.get_base_dir().path_join("D3D12Core." + arch + ".dll"))) { if (agility_sdk_multiarch) { da->make_dir_recursive(p_path.get_base_dir().path_join(arch)); diff --git a/platform/windows/gl_manager_windows_native.cpp b/platform/windows/gl_manager_windows_native.cpp index c8d7534e26..f74aa4ced7 100644 --- a/platform/windows/gl_manager_windows_native.cpp +++ b/platform/windows/gl_manager_windows_native.cpp @@ -76,6 +76,8 @@ static String format_error_message(DWORD id) { const int OGL_THREAD_CONTROL_ID = 0x20C1221E; const int OGL_THREAD_CONTROL_DISABLE = 0x00000002; const int OGL_THREAD_CONTROL_ENABLE = 0x00000001; +const int VRR_MODE_ID = 0x1194F158; +const int VRR_MODE_FULLSCREEN_ONLY = 0x1; typedef int(__cdecl *NvAPI_Initialize_t)(); typedef int(__cdecl *NvAPI_Unload_t)(); @@ -104,10 +106,12 @@ static bool nvapi_err_check(const char *msg, int status) { return true; } -// On windows we have to disable threaded optimization when using NVIDIA graphics cards -// to avoid stuttering, see https://stackoverflow.com/questions/36959508/nvidia-graphics-driver-causing-noticeable-frame-stuttering/37632948 -// also see https://github.com/Ryujinx/Ryujinx/blob/master/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs -void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() { +// On windows we have to customize the NVIDIA application profile: +// * disable threaded optimization when using NVIDIA cards to avoid stuttering, see +// https://stackoverflow.com/questions/36959508/nvidia-graphics-driver-causing-noticeable-frame-stuttering/37632948 +// https://github.com/Ryujinx/Ryujinx/blob/master/src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs +// * disable G-SYNC in windowed mode, as it results in unstable editor refresh rates +void GLManagerNative_Windows::_nvapi_setup_profile() { HMODULE nvapi = nullptr; #ifdef _WIN64 nvapi = LoadLibraryA("nvapi64.dll"); @@ -239,21 +243,29 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() { } } - NVDRS_SETTING setting; - setting.version = NVDRS_SETTING_VER; - setting.settingId = OGL_THREAD_CONTROL_ID; - setting.settingType = NVDRS_DWORD_TYPE; - setting.settingLocation = NVDRS_CURRENT_PROFILE_LOCATION; - setting.isCurrentPredefined = 0; - setting.isPredefinedValid = 0; + NVDRS_SETTING ogl_thread_control_setting = { 0 }; + ogl_thread_control_setting.version = NVDRS_SETTING_VER; + ogl_thread_control_setting.settingId = OGL_THREAD_CONTROL_ID; + ogl_thread_control_setting.settingType = NVDRS_DWORD_TYPE; int thread_control_val = OGL_THREAD_CONTROL_DISABLE; if (!GLOBAL_GET("rendering/gl_compatibility/nvidia_disable_threaded_optimization")) { thread_control_val = OGL_THREAD_CONTROL_ENABLE; } - setting.u32CurrentValue = thread_control_val; - setting.u32PredefinedValue = thread_control_val; + ogl_thread_control_setting.u32CurrentValue = thread_control_val; - if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &setting))) { + if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &ogl_thread_control_setting))) { + NvAPI_DRS_DestroySession(session_handle); + NvAPI_Unload(); + return; + } + + NVDRS_SETTING vrr_mode_setting = { 0 }; + vrr_mode_setting.version = NVDRS_SETTING_VER; + vrr_mode_setting.settingId = VRR_MODE_ID; + vrr_mode_setting.settingType = NVDRS_DWORD_TYPE; + vrr_mode_setting.u32CurrentValue = VRR_MODE_FULLSCREEN_ONLY; + + if (!nvapi_err_check("NVAPI: Error calling NvAPI_DRS_SetSetting", NvAPI_DRS_SetSetting(session_handle, profile_handle, &vrr_mode_setting))) { NvAPI_DRS_DestroySession(session_handle); NvAPI_Unload(); return; @@ -270,6 +282,7 @@ void GLManagerNative_Windows::_nvapi_disable_threaded_optimization() { } else { print_verbose("NVAPI: Enabled OpenGL threaded optimization successfully"); } + print_verbose("NVAPI: Disabled G-SYNC for windowed mode successfully"); NvAPI_DRS_DestroySession(session_handle); } @@ -495,7 +508,7 @@ void GLManagerNative_Windows::swap_buffers() { } Error GLManagerNative_Windows::initialize() { - _nvapi_disable_threaded_optimization(); + _nvapi_setup_profile(); return OK; } diff --git a/platform/windows/gl_manager_windows_native.h b/platform/windows/gl_manager_windows_native.h index b4e2a3acdf..532092ae74 100644 --- a/platform/windows/gl_manager_windows_native.h +++ b/platform/windows/gl_manager_windows_native.h @@ -78,7 +78,7 @@ private: int glx_minor, glx_major; private: - void _nvapi_disable_threaded_optimization(); + void _nvapi_setup_profile(); int _find_or_create_display(GLWindow &win); Error _create_context(GLWindow &win, GLDisplay &gl_display); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 157702655e..9025f53f42 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -1634,26 +1634,6 @@ String OS_Windows::get_locale() const { return "en"; } -// We need this because GetSystemInfo() is unreliable on WOW64 -// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724381(v=vs.85).aspx -// Taken from MSDN -typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); -LPFN_ISWOW64PROCESS fnIsWow64Process; - -BOOL is_wow64() { - BOOL wow64 = FALSE; - - fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); - - if (fnIsWow64Process) { - if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) { - wow64 = FALSE; - } - } - - return wow64; -} - String OS_Windows::get_processor_name() const { const String id = "Hardware\\Description\\System\\CentralProcessor\\0"; diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index b3f735e044..014419573c 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -574,7 +574,7 @@ StringName AnimatedSprite2D::get_animation() const { PackedStringArray AnimatedSprite2D::get_configuration_warnings() const { PackedStringArray warnings = Node2D::get_configuration_warnings(); if (frames.is_null()) { - warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames.")); + warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Sprite Frames\" property in order for AnimatedSprite2D to display frames.")); } return warnings; } diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index b3586a1da0..9dd9d4a376 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -31,6 +31,7 @@ #include "parallax_2d.h" #include "core/config/project_settings.h" +#include "scene/main/viewport.h" void Parallax2D::_notification(int p_what) { switch (p_what) { @@ -72,7 +73,11 @@ void Parallax2D::_validate_property(PropertyInfo &p_property) const { void Parallax2D::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_pos) { if (!ignore_camera_scroll) { - set_screen_offset(p_adj_screen_pos); + if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) { + set_screen_offset((p_adj_screen_pos + Vector2(0.5, 0.5)).floor()); + } else { + set_screen_offset(p_adj_screen_pos); + } } } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index d1f1c97ca2..48ade1e5cc 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -996,6 +996,7 @@ TileMap::TileMap() { base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled); base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format); + PropertyListHelper::register_base_helper(&base_property_helper); memdelete(defaults); } diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index ef492a6994..77c5d5a499 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1104,10 +1104,12 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); - if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL) { + if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) { return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL; } else if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES) { return BAKE_ERROR_MESHES_INVALID; + } else if (bake_err == Lightmapper::BAKE_ERROR_ATLAS_TOO_SMALL) { + return BAKE_ERROR_ATLAS_TOO_SMALL; } // POSTBAKE: Save Textures. @@ -1711,6 +1713,8 @@ void LightmapGI::_bind_methods() { BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); BIND_ENUM_CONSTANT(BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL); + BIND_ENUM_CONSTANT(BAKE_ERROR_LIGHTMAP_TOO_SMALL); + BIND_ENUM_CONSTANT(BAKE_ERROR_ATLAS_TOO_SMALL); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 527667177b..67480132b6 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -143,6 +143,7 @@ public: BAKE_ERROR_USER_ABORTED, BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL, BAKE_ERROR_LIGHTMAP_TOO_SMALL, + BAKE_ERROR_ATLAS_TOO_SMALL, }; enum EnvironmentMode { diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index 39181ad9a2..9aa8ef8ccb 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -143,9 +143,10 @@ public: }; enum BakeError { - BAKE_ERROR_LIGHTMAP_TOO_SMALL, + BAKE_OK, + BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, - BAKE_OK + BAKE_ERROR_ATLAS_TOO_SMALL, }; enum BakeQuality { diff --git a/scene/3d/physics/rigid_body_3d.cpp b/scene/3d/physics/rigid_body_3d.cpp index 5ea413f2c4..275e8cd7a9 100644 --- a/scene/3d/physics/rigid_body_3d.cpp +++ b/scene/3d/physics/rigid_body_3d.cpp @@ -249,7 +249,7 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) { //process additions for (int i = 0; i < toadd_count; i++) { - _body_inout(1, toremove[i].rid, toadd[i].id, toadd[i].shape, toadd[i].local_shape); + _body_inout(1, toadd[i].rid, toadd[i].id, toadd[i].shape, toadd[i].local_shape); } contact_monitor->locked = false; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index b5263add48..3d24b3bbe9 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -301,6 +301,7 @@ void Skeleton3D::setup_simulator() { sim->is_compat = true; sim->set_active(false); // Don't run unneeded process. add_child(simulator); + set_animate_physical_bones(animate_physical_bones); } #endif // _DISABLE_DEPRECATED @@ -1097,6 +1098,9 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("physical_bones_start_simulation", "bones"), &Skeleton3D::physical_bones_start_simulation_on, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("physical_bones_add_collision_exception", "exception"), &Skeleton3D::physical_bones_add_collision_exception); ClassDB::bind_method(D_METHOD("physical_bones_remove_collision_exception", "exception"), &Skeleton3D::physical_bones_remove_collision_exception); + + ADD_GROUP("Deprecated", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones"); #endif // _DISABLE_DEPRECATED } @@ -1136,19 +1140,15 @@ Node *Skeleton3D::get_simulator() { } void Skeleton3D::set_animate_physical_bones(bool p_enabled) { + animate_physical_bones = p_enabled; PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); if (!sim) { return; } - animate_physical_bones = p_enabled; sim->set_active(animate_physical_bones || sim->is_simulating_physics()); } bool Skeleton3D::get_animate_physical_bones() const { - PhysicalBoneSimulator3D *sim = cast_to<PhysicalBoneSimulator3D>(simulator); - if (!sim) { - return false; - } return animate_physical_bones; } diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 2d70aafcad..a009383f45 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -67,7 +67,7 @@ class Skeleton3D : public Node3D { GDCLASS(Skeleton3D, Node3D); #ifndef DISABLE_DEPRECATED - bool animate_physical_bones = false; + bool animate_physical_bones = true; Node *simulator = nullptr; void setup_simulator(); #endif // _DISABLE_DEPRECATED diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 8ac585719c..50218a6d86 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1435,7 +1435,7 @@ StringName AnimatedSprite3D::get_animation() const { PackedStringArray AnimatedSprite3D::get_configuration_warnings() const { PackedStringArray warnings = SpriteBase3D::get_configuration_warnings(); if (frames.is_null()) { - warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames.")); + warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Sprite Frames\" property in order for AnimatedSprite3D to display frames.")); } return warnings; } diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp index 6653e01f25..36c14e03d5 100644 --- a/scene/audio/audio_stream_player_internal.cpp +++ b/scene/audio/audio_stream_player_internal.cpp @@ -152,7 +152,6 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() { Ref<AudioSamplePlayback> sample_playback; sample_playback.instantiate(); sample_playback->stream = stream; - sample_playback->player_id = node->get_instance_id(); stream_playback->set_sample_playback(sample_playback); } } else if (!stream->is_meta_stream()) { @@ -262,9 +261,6 @@ void AudioStreamPlayerInternal::seek(float p_seconds) { void AudioStreamPlayerInternal::stop() { for (Ref<AudioStreamPlayback> &playback : stream_playbacks) { AudioServer::get_singleton()->stop_playback_stream(playback); - if (_is_sample() && playback->get_sample_playback().is_valid()) { - AudioServer::get_singleton()->stop_sample_playback(playback->get_sample_playback()); - } } stream_playbacks.clear(); diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index 1663a7d602..e9c178e423 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -35,7 +35,7 @@ Size2 AspectRatioContainer::get_minimum_size() const { Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp index 1af33d2814..5dc336ceb5 100644 --- a/scene/gui/center_container.cpp +++ b/scene/gui/center_container.cpp @@ -36,7 +36,7 @@ Size2 CenterContainer::get_minimum_size() const { } Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index c3c4b1d3fb..00b9a3478a 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -251,6 +251,27 @@ void CodeEdit::_notification(int p_what) { } void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { + Ref<InputEventPanGesture> pan_gesture = p_gui_input; + if (pan_gesture.is_valid() && code_completion_active && code_completion_rect.has_point(pan_gesture->get_position())) { + const real_t delta = pan_gesture->get_delta().y; + code_completion_pan_offset += delta; + if (code_completion_pan_offset <= -1.0) { + if (code_completion_current_selected > 0) { + code_completion_current_selected--; + code_completion_force_item_center = -1; + queue_redraw(); + } + code_completion_pan_offset += 1.0f; + } else if (code_completion_pan_offset >= +1.0) { + if (code_completion_current_selected < code_completion_options.size() - 1) { + code_completion_current_selected++; + code_completion_force_item_center = -1; + queue_redraw(); + } + code_completion_pan_offset -= 1.0f; + } + } + Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { // Ignore mouse clicks in IME input mode, let TextEdit handle it. @@ -285,6 +306,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (code_completion_current_selected > 0) { code_completion_current_selected--; code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); } } break; @@ -292,6 +314,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); } } break; @@ -301,6 +324,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); + code_completion_pan_offset = 0.0f; if (mb->is_double_click()) { confirm_code_completion(); } @@ -472,6 +496,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { code_completion_current_selected = code_completion_options.size() - 1; } code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); accept_event(); return; @@ -483,6 +508,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { code_completion_current_selected = 0; } code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); accept_event(); return; @@ -490,6 +516,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (k->is_action("ui_page_up", true)) { code_completion_current_selected = MAX(0, code_completion_current_selected - theme_cache.code_completion_max_lines); code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); accept_event(); return; @@ -497,6 +524,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (k->is_action("ui_page_down", true)) { code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + theme_cache.code_completion_max_lines); code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); accept_event(); return; @@ -2119,6 +2147,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { ERR_FAIL_INDEX(p_index, code_completion_options.size()); code_completion_current_selected = p_index; code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; queue_redraw(); } @@ -3244,6 +3273,7 @@ void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); code_completion_force_item_center = -1; + code_completion_pan_offset = 0.0f; } void CodeEdit::_filter_code_completion_candidates_impl() { @@ -3305,6 +3335,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (_should_reset_selected_option_for_new_options(code_completion_options_new)) { code_completion_current_selected = 0; + code_completion_pan_offset = 0.0f; } code_completion_options = code_completion_options_new; @@ -3520,6 +3551,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_options_new.sort_custom<CodeCompletionOptionCompare>(); if (_should_reset_selected_option_for_new_options(code_completion_options_new)) { code_completion_current_selected = 0; + code_completion_pan_offset = 0.0f; } code_completion_options = code_completion_options_new; diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 56f8cce548..580435f65e 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -214,6 +214,7 @@ private: int code_completion_longest_line = 0; Rect2i code_completion_rect; Rect2i code_completion_scroll_rect; + float code_completion_pan_offset = 0.0f; HashSet<char32_t> code_completion_prefixes; List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 1d53edbfa6..d169e82e5d 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -696,7 +696,7 @@ void Control::_update_canvas_item_transform() { // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() if (is_inside_tree() && Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { - xform[2] = xform[2].round(); + xform[2] = (xform[2] + Vector2(0.5, 0.5)).floor(); } RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index c9372525bd..8047369ab1 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1350,6 +1350,7 @@ void FileDialog::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); + PropertyListHelper::register_base_helper(&base_property_helper); } void FileDialog::set_show_hidden_files(bool p_show) { diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index f77d66fe98..eedcd473fb 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -250,7 +250,7 @@ Size2 FlowContainer::get_minimum_size() const { Size2i minimum; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 933b4df6e3..bf16c0699e 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1905,6 +1905,7 @@ void ItemList::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &ItemList::set_item_disabled, &ItemList::is_item_disabled); + PropertyListHelper::register_base_helper(&base_property_helper); } ItemList::ItemList() { diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 218153e011..0006204ae3 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -2589,7 +2589,10 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_secret_character", "character"), &LineEdit::set_secret_character); ClassDB::bind_method(D_METHOD("get_secret_character"), &LineEdit::get_secret_character); ClassDB::bind_method(D_METHOD("menu_option", "option"), &LineEdit::menu_option); + // TODO: Properly handle popups when advanced GUI is disabled. +#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu); +#endif // ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("is_menu_visible"), &LineEdit::is_menu_visible); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled); ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 2ce12794a7..4f8d818a6c 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -679,7 +679,10 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); + // TODO: Properly handle popups when advanced GUI is disabled. +#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); +#endif // ADVANCED_GUI_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index"); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 998f99b2f9..c60f728f34 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -173,7 +173,10 @@ bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { } void MenuButton::_bind_methods() { + // TODO: Properly handle popups when advanced GUI is disabled. +#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); +#endif // ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover); ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); @@ -198,6 +201,7 @@ void MenuButton::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); + PropertyListHelper::register_base_helper(&base_property_helper); } void MenuButton::set_disable_shortcuts(bool p_disabled) { diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index da15b44bdc..a1425fb847 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -577,6 +577,7 @@ void OptionButton::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); + PropertyListHelper::register_base_helper(&base_property_helper); } void OptionButton::set_disable_shortcuts(bool p_disabled) { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 7f795ea710..f62421061b 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2824,6 +2824,7 @@ void PopupMenu::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &PopupMenu::set_item_disabled, &PopupMenu::is_item_disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &PopupMenu::set_item_as_separator, &PopupMenu::is_item_separator); + PropertyListHelper::register_base_helper(&base_property_helper); } void PopupMenu::popup(const Rect2i &p_bounds) { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 8ffa0f8c63..5ef02bf19d 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2098,7 +2098,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { handled = true; } if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { - vscroll->scroll(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); + vscroll->scroll(theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { @@ -4947,10 +4947,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("outline_size"); } else if (bbcode_name == "fade") { - int start_index = 0; + int start_index = brk_pos; OptionMap::Iterator start_option = bbcode_options.find("start"); if (start_option) { - start_index = start_option->value.to_int(); + start_index += start_option->value.to_int(); } int length = 10; diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 925600756a..8ab9b4c1cd 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -122,11 +122,11 @@ void SplitContainerDragger::_notification(int p_what) { } } -Control *SplitContainer::_get_sortable_child(int p_idx) const { +Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const { int idx = 0; for (int i = 0; i < get_child_count(false); i++) { - Control *c = as_sortable_control(get_child(i, false)); + Control *c = as_sortable_control(get_child(i, false), p_visibility_mode); if (!c) { continue; } @@ -258,7 +258,8 @@ Size2 SplitContainer::get_minimum_size() const { int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; for (int i = 0; i < 2; i++) { - if (!_get_sortable_child(i)) { + Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE); + if (!child) { break; } @@ -270,7 +271,7 @@ Size2 SplitContainer::get_minimum_size() const { } } - Size2 ms = _get_sortable_child(i)->get_combined_minimum_size(); + Size2 ms = child->get_combined_minimum_size(); if (vertical) { minimum.height += ms.height; diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 95f26f5e0b..db870554c2 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -82,7 +82,7 @@ private: Ref<Texture2D> _get_grabber_icon() const; void _compute_middle_sep(bool p_clamp); void _resort(); - Control *_get_sortable_child(int p_idx) const; + Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const; protected: bool is_fixed = false; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index ddc757c452..1ae18f5728 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1873,6 +1873,7 @@ void TabBar::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled); + PropertyListHelper::register_base_helper(&base_property_helper); } TabBar::TabBar() { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 9cc59f1def..a51ef143fa 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -8143,7 +8143,7 @@ void TextEdit::_update_gutter_width() { /* Syntax highlighting. */ Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { - return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); + return (syntax_highlighter.is_null() || setting_text) ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); } /* Deprecated. */ diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index b0dfdacad0..46fcdcf7f6 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -5440,6 +5440,24 @@ int Tree::get_drop_section_at_position(const Point2 &p_pos) const { return -100; } +bool Tree::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + if (drag_touching) { + // Disable data drag & drop when touch dragging. + return false; + } + + return Control::can_drop_data(p_point, p_data); +} + +Variant Tree::get_drag_data(const Point2 &p_point) { + if (drag_touching) { + // Disable data drag & drop when touch dragging. + return Variant(); + } + + return Control::get_drag_data(p_point); +} + TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index e9c93c6e03..3200459b5a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -698,6 +698,8 @@ public: virtual String get_tooltip(const Point2 &p_pos) const override; + virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; + virtual Variant get_drag_data(const Point2 &p_point) override; TreeItem *get_item_at_position(const Point2 &p_pos) const; int get_column_at_position(const Point2 &p_pos) const; int get_drop_section_at_position(const Point2 &p_pos) const; diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index f36bbe9395..29166f3d92 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -161,12 +161,12 @@ void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placehold } switch (current_type) { - case Variant::Type::NIL: - if (placeholder_type != Variant::Type::NODE_PATH) { + case Variant::Type::NIL: { + Ref<Resource> resource = p_set.value; + if (placeholder_type != Variant::Type::NODE_PATH && !resource.is_valid()) { break; } - // If it's nil but we have a NodePath, we guess what works. - + // If it's nil but we have a NodePath or a Resource, we guess what works. p_instance->set(p_set.name, p_set.value, &is_valid); if (is_valid) { break; @@ -174,13 +174,15 @@ void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placehold p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value), &is_valid); break; - case Variant::Type::OBJECT: + } + case Variant::Type::OBJECT: { if (placeholder_type != Variant::Type::NODE_PATH) { break; } // Easiest case, we want a node, but we have a deferred NodePath. p_instance->set(p_set.name, try_get_node(p_placeholder, p_instance, p_set.value)); break; + } case Variant::Type::ARRAY: { // If we have reached here it means our array types don't match, // so we will convert the placeholder array into the correct type @@ -209,9 +211,10 @@ void InstancePlaceholder::set_value_on_instance(InstancePlaceholder *p_placehold } break; } - default: + default: { WARN_PRINT(vformat("Property '%s' with type '%s' could not be set when creating instance of '%s'.", p_set.name, Variant::get_type_name(current_type), p_placeholder->get_name())); break; + } } } diff --git a/scene/main/node.h b/scene/main/node.h index 6b93724478..ee195ddef9 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -596,7 +596,7 @@ public: // No thread processing. // Only accessible if node is outside the scene tree // or access will happen from a node-safe thread. - return !data.inside_tree || is_current_thread_safe_for_nodes(); + return is_current_thread_safe_for_nodes() || unlikely(!data.inside_tree); } else { // Thread processing. return true; diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index ce258ee8c3..f840aaa759 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -30,6 +30,19 @@ #include "property_list_helper.h" +Vector<PropertyListHelper *> PropertyListHelper::base_helpers; // static + +void PropertyListHelper::clear_base_helpers() { // static + for (PropertyListHelper *helper : base_helpers) { + helper->clear(); + } + base_helpers.clear(); +} + +void PropertyListHelper::register_base_helper(PropertyListHelper *p_helper) { // static + base_helpers.push_back(p_helper); +} + const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const { const Vector<String> components = p_property.rsplit("/", true, 1); if (components.size() < 2 || !components[0].begins_with(prefix)) { @@ -176,9 +189,8 @@ bool PropertyListHelper::property_get_revert(const String &p_property, Variant & return false; } -PropertyListHelper::~PropertyListHelper() { - // No object = it's the main helper. Do a cleanup. - if (!object && is_initialized()) { +void PropertyListHelper::clear() { + if (is_initialized()) { memdelete(array_length_getter); for (const KeyValue<String, Property> &E : property_list) { @@ -187,5 +199,6 @@ PropertyListHelper::~PropertyListHelper() { memdelete(E.value.getter); } } + property_list.clear(); } } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index 6bc65f6e3e..1ab923e76d 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -42,6 +42,8 @@ class PropertyListHelper { MethodBind *getter = nullptr; }; + static Vector<PropertyListHelper *> base_helpers; + String prefix; MethodBind *array_length_getter = nullptr; HashMap<String, Property> property_list; @@ -53,6 +55,9 @@ class PropertyListHelper { int _call_array_length_getter() const; public: + static void clear_base_helpers(); + static void register_base_helper(PropertyListHelper *p_helper); + void set_prefix(const String &p_prefix); template <typename G> void set_array_length_getter(G p_array_length_getter) { @@ -83,7 +88,7 @@ public: bool property_can_revert(const String &p_property) const; bool property_get_revert(const String &p_property, Variant &r_value) const; - ~PropertyListHelper(); + void clear(); }; #endif // PROPERTY_LIST_HELPER_H diff --git a/scene/resources/2d/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp index a845809bf2..4a290db86b 100644 --- a/scene/resources/2d/navigation_polygon.cpp +++ b/scene/resources/2d/navigation_polygon.cpp @@ -193,6 +193,10 @@ void NavigationPolygon::set_data(const Vector<Vector2> &p_vertices, const Vector for (int i = 0; i < p_polygons.size(); i++) { polygons.write[i].indices = p_polygons[i]; } + { + MutexLock lock(navigation_mesh_generation); + navigation_mesh.unref(); + } } void NavigationPolygon::get_data(Vector<Vector2> &r_vertices, Vector<Vector<int>> &r_polygons) { diff --git a/scene/resources/atlas_texture.cpp b/scene/resources/atlas_texture.cpp index 28e4186048..ec0d2ac6fe 100644 --- a/scene/resources/atlas_texture.cpp +++ b/scene/resources/atlas_texture.cpp @@ -122,6 +122,19 @@ bool AtlasTexture::has_filter_clip() const { return filter_clip; } +Rect2 AtlasTexture::_get_region_rect() const { + Rect2 rc = region; + if (atlas.is_valid()) { + if (rc.size.width == 0) { + rc.size.width = atlas->get_width(); + } + if (rc.size.height == 0) { + rc.size.height = atlas->get_height(); + } + } + return rc; +} + void AtlasTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_atlas", "atlas"), &AtlasTexture::set_atlas); ClassDB::bind_method(D_METHOD("get_atlas"), &AtlasTexture::get_atlas); @@ -142,25 +155,15 @@ void AtlasTexture::_bind_methods() { } void AtlasTexture::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const { - if (!atlas.is_valid()) { + if (atlas.is_null()) { return; } - - Rect2 rc = region; - - if (rc.size.width == 0) { - rc.size.width = atlas->get_width(); - } - - if (rc.size.height == 0) { - rc.size.height = atlas->get_height(); - } - + const Rect2 rc = _get_region_rect(); atlas->draw_rect_region(p_canvas_item, Rect2(p_pos + margin.position, rc.size), rc, p_modulate, p_transpose, filter_clip); } void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const { - if (!atlas.is_valid()) { + if (atlas.is_null()) { return; } @@ -174,8 +177,8 @@ void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile } void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const { - //this might not necessarily work well if using a rect, needs to be fixed properly - if (!atlas.is_valid()) { + // This might not necessarily work well if using a rect, needs to be fixed properly. + if (atlas.is_null()) { return; } @@ -195,10 +198,13 @@ bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, if (src.size == Size2()) { src.size = region.size; } + if (src.size == Size2() && atlas.is_valid()) { + src.size = atlas->get_size(); + } Vector2 scale = p_rect.size / src.size; src.position += (region.position - margin.position); - Rect2 src_clipped = region.intersection(src); + Rect2 src_clipped = _get_region_rect().intersection(src); if (src_clipped.size == Size2()) { return false; } @@ -217,14 +223,14 @@ bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, } bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { - if (!atlas.is_valid()) { + if (atlas.is_null()) { return true; } int x = p_x + region.position.x - margin.position.x; int y = p_y + region.position.y - margin.position.y; - // margin edge may outside of atlas + // Margin edge may outside of atlas. if (x < 0 || x >= atlas->get_width()) { return false; } @@ -236,16 +242,16 @@ bool AtlasTexture::is_pixel_opaque(int p_x, int p_y) const { } Ref<Image> AtlasTexture::get_image() const { - if (atlas.is_null() || region.size.x <= 0 || region.size.y <= 0) { + if (atlas.is_null()) { return Ref<Image>(); } - Ref<Image> atlas_image = atlas->get_image(); + const Ref<Image> &atlas_image = atlas->get_image(); if (atlas_image.is_null()) { return Ref<Image>(); } - return atlas_image->get_region(region); + return atlas_image->get_region(_get_region_rect()); } AtlasTexture::AtlasTexture() {} diff --git a/scene/resources/atlas_texture.h b/scene/resources/atlas_texture.h index 5aba098d09..0246a24743 100644 --- a/scene/resources/atlas_texture.h +++ b/scene/resources/atlas_texture.h @@ -37,6 +37,8 @@ class AtlasTexture : public Texture2D { GDCLASS(AtlasTexture, Texture2D); RES_BASE_EXTENSION("atlastex"); + Rect2 _get_region_rect() const; + protected: Ref<Texture2D> atlas; Rect2 region; diff --git a/scene/resources/audio_stream_polyphonic.cpp b/scene/resources/audio_stream_polyphonic.cpp index 45546d8dc7..e617096f3b 100644 --- a/scene/resources/audio_stream_polyphonic.cpp +++ b/scene/resources/audio_stream_polyphonic.cpp @@ -34,6 +34,9 @@ #include "scene/main/scene_tree.h" #include "servers/audio_server.h" +constexpr uint64_t ID_MASK = 0xFFFFFFFF; +constexpr uint64_t INDEX_SHIFT = 32; + Ref<AudioStreamPlayback> AudioStreamPolyphonic::instantiate_playback() { Ref<AudioStreamPlaybackPolyphonic> playback; playback.instantiate(); @@ -252,14 +255,14 @@ AudioStreamPlaybackPolyphonic::ID AudioStreamPlaybackPolyphonic::play_stream(con } AudioStreamPlaybackPolyphonic::Stream *AudioStreamPlaybackPolyphonic::_find_stream(int64_t p_id) { - uint32_t index = p_id >> INDEX_SHIFT; + uint32_t index = static_cast<uint64_t>(p_id) >> INDEX_SHIFT; if (index >= streams.size()) { return nullptr; } if (!streams[index].active.is_set()) { return nullptr; // Not active, no longer exists. } - int64_t id = p_id & ID_MASK; + int64_t id = static_cast<uint64_t>(p_id) & ID_MASK; if (streams[index].id != id) { return nullptr; } diff --git a/scene/resources/audio_stream_polyphonic.h b/scene/resources/audio_stream_polyphonic.h index 01d0176c44..b49ed1b741 100644 --- a/scene/resources/audio_stream_polyphonic.h +++ b/scene/resources/audio_stream_polyphonic.h @@ -60,11 +60,8 @@ public: class AudioStreamPlaybackPolyphonic : public AudioStreamPlayback { GDCLASS(AudioStreamPlaybackPolyphonic, AudioStreamPlayback) - enum { - INTERNAL_BUFFER_LEN = 128, - ID_MASK = 0xFFFFFFFF, - INDEX_SHIFT = 32 - }; + constexpr static uint32_t INTERNAL_BUFFER_LEN = 128; + struct Stream { SafeFlag active; SafeFlag pending_play; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index bedac4e933..4bedcb1820 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1003,6 +1003,7 @@ void VisualShader::remove_node(Type p_type, int p_id) { g->nodes[connection.to_node].node->set_input_port_connected(connection.to_port, false); } else if (connection.to_node == p_id) { g->nodes[connection.from_node].next_connected_nodes.erase(p_id); + g->nodes[connection.from_node].node->set_output_port_connected(connection.from_port, false); } g->connections.erase(E); } @@ -3897,6 +3898,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Roughness", "ROUGHNESS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "Point Size", "POINT_SIZE" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Model View Matrix", "MODELVIEW_MATRIX" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "Projection Matrix", "PROJECTION_MATRIX" }, //////////////////////////////////////////////////////////////////////// // Node3D, Fragment. //////////////////////////////////////////////////////////////////////// @@ -3961,6 +3963,7 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = { //////////////////////////////////////////////////////////////////////// { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Light", "LIGHT.rgb" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "Light Alpha", "LIGHT.a" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "Shadow Modulate", "SHADOW_MODULATE.rgb" }, //////////////////////////////////////////////////////////////////////// // Sky, Sky. diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 018b7be81c..38c60828e8 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -8177,6 +8177,9 @@ String VisualShaderNodeRotationByAxis::get_output_port_name(int p_port) const { } bool VisualShaderNodeRotationByAxis::has_output_port_preview(int p_port) const { + if (p_port == 0) { + return true; + } return false; } @@ -8190,15 +8193,20 @@ String VisualShaderNodeRotationByAxis::generate_code(Shader::Mode p_mode, Visual code += vformat(" vec3( __axis.y*__axis.x*(1.0-cos(__angle))+__axis.z*sin(__angle), cos(__angle)+__axis.y*__axis.y*(1.0-cos(__angle)), __axis.y*__axis.z*(1.0-cos(__angle))-__axis.x*sin(__angle) ),\n"); code += vformat(" vec3( __axis.z*__axis.x*(1.0-cos(__angle))-__axis.y*sin(__angle), __axis.z*__axis.y*(1.0-cos(__angle))+__axis.x*sin(__angle), cos(__angle)+__axis.z*__axis.z*(1.0-cos(__angle)) )\n"); code += vformat(" );\n"); - code += vformat(" %s = %s * __rot_matrix;\n", p_output_vars[0], p_input_vars[0]); - code += vformat(" %s = mat4(__rot_matrix);\n", p_output_vars[1]); + if (is_output_port_connected(0)) { + code += vformat(" %s = %s * __rot_matrix;\n", p_output_vars[0], p_input_vars[0]); + } + if (is_output_port_connected(1)) { + code += vformat(" %s = mat4(__rot_matrix);\n", p_output_vars[1]); + } code += " }\n"; return code; } VisualShaderNodeRotationByAxis::VisualShaderNodeRotationByAxis() { + set_input_port_default_value(0, Vector3()); set_input_port_default_value(1, 0.0); - set_input_port_default_value(2, Vector3(0.0, 0.0, 0.0)); + set_input_port_default_value(2, Vector3()); simple_decl = false; } diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 0dc6d16050..7ab150c141 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -741,6 +741,7 @@ void AudioStreamRandomizer::_bind_methods() { base_property_helper.set_array_length_getter(&AudioStreamRandomizer::get_streams_count); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream); base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight); + PropertyListHelper::register_base_helper(&base_property_helper); } AudioStreamRandomizer::AudioStreamRandomizer() { diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index 9149109381..0ca4777d5c 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -49,7 +49,6 @@ class AudioSamplePlayback : public RefCounted { public: Ref<AudioStream> stream; - ObjectID player_id; float offset = 0.0f; Vector<AudioFrame> volume_vector; StringName bus; diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index fefb8bfd41..54840adcae 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -1220,6 +1220,12 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, con void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { ERR_FAIL_COND(p_playback.is_null()); + // Handle sample playback. + if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) { + AudioServer::get_singleton()->stop_sample_playback(p_playback->get_sample_playback()); + return; + } + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); if (!playback_node) { return; @@ -1358,6 +1364,10 @@ void AudioServer::set_playback_highshelf_params(Ref<AudioStreamPlayback> p_playb bool AudioServer::is_playback_active(Ref<AudioStreamPlayback> p_playback) { ERR_FAIL_COND_V(p_playback.is_null(), false); + if (p_playback->get_is_sample() && p_playback->get_sample_playback().is_valid()) { + return sample_playback_list.has(p_playback->get_sample_playback()); + } + AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); if (!playback_node) { return false; @@ -1818,11 +1828,13 @@ void AudioServer::unregister_sample(const Ref<AudioSample> &p_sample) { void AudioServer::start_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null."); AudioDriver::get_singleton()->start_sample_playback(p_playback); + sample_playback_list.ordered_insert(p_playback); } void AudioServer::stop_sample_playback(const Ref<AudioSamplePlayback> &p_playback) { ERR_FAIL_COND_MSG(p_playback.is_null(), "Parameter p_playback is null."); AudioDriver::get_singleton()->stop_sample_playback(p_playback); + sample_playback_list.erase(p_playback); } void AudioServer::set_sample_playback_pause(const Ref<AudioSamplePlayback> &p_playback, bool p_paused) { diff --git a/servers/audio_server.h b/servers/audio_server.h index 4825e24336..84c091e320 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -329,6 +329,8 @@ private: friend class AudioDriver; void _driver_process(int p_frames, int32_t *p_buffer); + LocalVector<Ref<AudioStreamPlayback>> sample_playback_list; + protected: static void _bind_methods(); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index b6e0d58af7..d362a4073a 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -759,6 +759,17 @@ DisplayServer::WindowID DisplayServer::get_focused_window() const { void DisplayServer::set_context(Context p_context) { } +void DisplayServer::register_additional_output(Object *p_object) { + ObjectID id = p_object->get_instance_id(); + if (!additional_outputs.has(id)) { + additional_outputs.push_back(id); + } +} + +void DisplayServer::unregister_additional_output(Object *p_object) { + additional_outputs.erase(p_object->get_instance_id()); +} + void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature); ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name); @@ -997,6 +1008,10 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_window_transparency_available"), &DisplayServer::is_window_transparency_available); + ClassDB::bind_method(D_METHOD("register_additional_output", "object"), &DisplayServer::register_additional_output); + ClassDB::bind_method(D_METHOD("unregister_additional_output", "object"), &DisplayServer::unregister_additional_output); + ClassDB::bind_method(D_METHOD("has_additional_outputs"), &DisplayServer::has_additional_outputs); + #ifndef DISABLE_DEPRECATED BIND_ENUM_CONSTANT(FEATURE_GLOBAL_MENU); #endif diff --git a/servers/display_server.h b/servers/display_server.h index 5d82b6c13c..8c7e92fdc3 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -53,6 +53,8 @@ class DisplayServer : public Object { RID _get_rid_from_name(NativeMenu *p_nmenu, const String &p_menu_root) const; #endif + LocalVector<ObjectID> additional_outputs; + public: _FORCE_INLINE_ static DisplayServer *get_singleton() { return singleton; @@ -582,6 +584,10 @@ public: virtual bool is_window_transparency_available() const { return false; } + void register_additional_output(Object *p_output); + void unregister_additional_output(Object *p_output); + bool has_additional_outputs() const { return additional_outputs.size() > 0; } + static void register_create_function(const char *p_name, CreateFunction p_function, GetRenderingDriversFunction p_get_drivers); static int get_create_function_count(); static const char *get_create_function_name(int p_index); diff --git a/servers/physics_server_2d_wrap_mt.h b/servers/physics_server_2d_wrap_mt.h index f0c36a906f..5c757264b3 100644 --- a/servers/physics_server_2d_wrap_mt.h +++ b/servers/physics_server_2d_wrap_mt.h @@ -45,7 +45,11 @@ #endif #ifdef DEBUG_ENABLED +#ifdef DEV_ENABLED #define MAIN_THREAD_SYNC_WARN WARN_PRINT("Call to " + String(__FUNCTION__) + " causing PhysicsServer2D synchronizations on every frame. This significantly affects performance."); +#else +#define MAIN_THREAD_SYNC_WARN +#endif #endif class PhysicsServer2DWrapMT : public PhysicsServer2D { diff --git a/servers/physics_server_3d_wrap_mt.h b/servers/physics_server_3d_wrap_mt.h index 0909c46b55..2fd39546a5 100644 --- a/servers/physics_server_3d_wrap_mt.h +++ b/servers/physics_server_3d_wrap_mt.h @@ -44,7 +44,11 @@ #endif #ifdef DEBUG_ENABLED +#ifdef DEV_ENABLED #define MAIN_THREAD_SYNC_WARN WARN_PRINT("Call to " + String(__FUNCTION__) + " causing PhysicsServer3D synchronizations on every frame. This significantly affects performance."); +#else +#define MAIN_THREAD_SYNC_WARN +#endif #endif class PhysicsServer3DWrapMT : public PhysicsServer3D { diff --git a/servers/rendering/renderer_rd/environment/gi.cpp b/servers/rendering/renderer_rd/environment/gi.cpp index c7752f8a86..12ff28d7b0 100644 --- a/servers/rendering/renderer_rd/environment/gi.cpp +++ b/servers/rendering/renderer_rd/environment/gi.cpp @@ -1790,6 +1790,10 @@ void GI::SDFGI::debug_probes(RID p_framebuffer, const uint32_t p_view_count, con } void GI::SDFGI::pre_process_gi(const Transform3D &p_transform, RenderDataRD *p_render_data) { + if (p_render_data->sdfgi_update_data == nullptr) { + return; + } + RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); /* Update general SDFGI Buffer */ 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 536fc7a04a..ad19f36257 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -587,7 +587,7 @@ void RenderForwardClustered::_render_list_with_draw_list(RenderListParameters *p RD::get_singleton()->draw_list_end(); } -void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) { +void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); Ref<RenderSceneBuffersRD> rd = p_render_data->render_buffers; @@ -603,7 +603,7 @@ void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_dat } } - p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier); + p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier); // now do implementation UBO @@ -1126,6 +1126,10 @@ void RenderForwardClustered::_setup_lightmaps(const RenderDataRD *p_render_data, /* SDFGI */ void RenderForwardClustered::_update_sdfgi(RenderDataRD *p_render_data) { + if (p_render_data->sdfgi_update_data == nullptr) { + return; + } + Ref<RenderSceneBuffersRD> rb; if (p_render_data && p_render_data->render_buffers.is_valid()) { rb = p_render_data->render_buffers; @@ -1732,7 +1736,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co _setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform); _setup_voxelgis(*p_render_data->voxel_gi_instances); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); // May have changed due to the above (light buffer enlarged, as an example). _update_render_base_uniform_set(); @@ -1995,7 +1999,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co // Shadow pass can change the base uniform set samplers. _update_render_base_uniform_set(); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, true, using_motion_pass); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, true, using_motion_pass); RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, samplers, true); @@ -2209,7 +2213,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); { uint32_t transparent_color_pass_flags = (color_pass_flags | COLOR_PASS_FLAG_TRANSPARENT) & ~(COLOR_PASS_FLAG_SEPARATE_SPECULAR); @@ -2562,6 +2566,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page SceneState::ShadowPass shadow_pass; RenderSceneDataRD scene_data; + scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic? scene_data.cam_projection = p_projection; scene_data.cam_transform = p_transform; scene_data.view_projection[0] = p_projection; @@ -2581,7 +2586,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page render_data.instances = &p_instances; render_data.render_info = p_render_info; - _setup_environment(&render_data, true, p_viewport_size, !p_flip_y, Color(), false, false, p_use_pancake, shadow_pass_index); + _setup_environment(&render_data, true, p_viewport_size, Color(), false, false, p_use_pancake, shadow_pass_index); if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) { scene_data.screen_mesh_lod_threshold = 0.0; @@ -2654,6 +2659,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield"); RenderSceneDataRD scene_data; + scene_data.flip_y = true; scene_data.cam_projection = p_cam_projection; scene_data.cam_transform = p_cam_transform; scene_data.view_projection[0] = p_cam_projection; @@ -2673,7 +2679,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false, false); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false, false); PassMode pass_mode = PASS_MODE_SHADOW; @@ -2720,7 +2726,7 @@ void RenderForwardClustered::_render_material(const Transform3D &p_cam_transform _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -2771,7 +2777,7 @@ void RenderForwardClustered::_render_uv2(const PagedArray<RenderGeometryInstance _update_render_base_uniform_set(); - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -2887,7 +2893,7 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu RendererRD::MaterialStorage::store_transform(to_bounds.affine_inverse() * scene_data.cam_transform, scene_state.ubo.sdf_to_bounds); scene_data.emissive_exposure_normalization = p_exposure_normalization; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); RID rp_uniform_set = _setup_sdfgi_render_pass_uniform_set(p_albedo_texture, p_emission_texture, p_emission_aniso_texture, p_geom_facing_texture, RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index ae9e5e7c10..0aa4a0667e 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -361,7 +361,7 @@ class RenderForwardClustered : public RendererSceneRenderRD { static RenderForwardClustered *singleton; - void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0); + void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0); void _setup_voxelgis(const PagedArray<RID> &p_voxelgis); void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform); diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 9e0dacc1f2..42e1f7b6dc 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -640,7 +640,7 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; - actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.inv_view_matrix[2].xyz"; actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(scene_data.view_matrix * read_model_matrix)[3].xyz"; 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 194a70dc22..c03dd96062 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -804,7 +804,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RD::get_singleton()->draw_command_begin_label("Render Setup"); _setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); // May have changed due to the above (light buffer enlarged, as an example). _update_render_base_uniform_set(); @@ -953,7 +953,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color // Shadow pass can change the base uniform set samplers. _update_render_base_uniform_set(); - _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, p_render_data->render_buffers.is_valid()); + _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, p_render_data->render_buffers.is_valid()); if (merge_transparent_pass && using_subpass_post_process) { RENDER_TIMESTAMP("Render Opaque + Transparent + Tonemap"); @@ -1018,11 +1018,6 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RD::get_singleton()->draw_command_end_label(); // Draw Sky } - // rendering effects - if (ce_has_pre_transparent) { - _process_compositor_effects(RS::COMPOSITOR_EFFECT_CALLBACK_TYPE_PRE_TRANSPARENT, p_render_data); - } - if (merge_transparent_pass) { if (render_list[RENDER_LIST_ALPHA].element_info.size() > 0) { // transparent pass @@ -1058,6 +1053,11 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color RD::get_singleton()->draw_command_end_label(); // Render 3D Pass / Render Reflection Probe Pass + // rendering effects + if (ce_has_pre_transparent) { + _process_compositor_effects(RS::COMPOSITOR_EFFECT_CALLBACK_TYPE_PRE_TRANSPARENT, p_render_data); + } + if (scene_state.used_screen_texture) { // Copy screen texture to backbuffer so we can read from it _render_buffers_copy_screen_texture(p_render_data); @@ -1075,7 +1075,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true); // this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation - //_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); + //_setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false); RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, rp_uniform_set, spec_constant_base_flags, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count); render_list_params.framebuffer_format = fb_format; @@ -1310,6 +1310,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr } RenderSceneDataRD scene_data; + scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic? scene_data.cam_projection = p_projection; scene_data.cam_transform = p_transform; scene_data.view_projection[0] = p_projection; @@ -1327,7 +1328,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr render_data.instances = &p_instances; render_data.render_info = p_render_info; - _setup_environment(&render_data, true, Vector2(1, 1), !p_flip_y, Color(), false, p_use_pancake, shadow_pass_index); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, p_use_pancake, shadow_pass_index); if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) { scene_data.screen_mesh_lod_threshold = 0.0; @@ -1415,7 +1416,7 @@ void RenderForwardMobile::_render_material(const Transform3D &p_cam_transform, c render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -1460,7 +1461,7 @@ void RenderForwardMobile::_render_uv2(const PagedArray<RenderGeometryInstance *> render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); + _setup_environment(&render_data, true, Vector2(1, 1), Color()); PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -1526,6 +1527,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const _update_render_base_uniform_set(); RenderSceneDataRD scene_data; + scene_data.flip_y = true; scene_data.cam_projection = p_cam_projection; scene_data.cam_transform = p_cam_transform; scene_data.view_projection[0] = p_cam_projection; @@ -1541,7 +1543,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const render_data.scene_data = &scene_data; render_data.instances = &p_instances; - _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false); + _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false); PassMode pass_mode = PASS_MODE_SHADOW; @@ -1974,7 +1976,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const } } -void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) { +void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) { RID env = is_environment(p_render_data->environment) ? p_render_data->environment : RID(); RID reflection_probe_instance = p_render_data->reflection_probe.is_valid() ? RendererRD::LightStorage::get_singleton()->reflection_probe_instance_get_probe(p_render_data->reflection_probe) : RID(); @@ -1987,7 +1989,7 @@ void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, } } - p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false); + p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false); } /// RENDERING /// @@ -2056,6 +2058,10 @@ void RenderForwardMobile::_render_list_template(RenderingDevice::DrawListID p_dr uint32_t base_spec_constants = p_params->spec_constant_base_flags; + if (bool(inst->flags_cache & INSTANCE_DATA_FLAG_MULTIMESH)) { + base_spec_constants |= 1 << SPEC_CONSTANT_IS_MULTIMESH; + } + SceneState::PushConstant push_constant; push_constant.base_index = i + p_params->element_offset; diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index aa1b8f34b2..34260bd701 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -81,6 +81,7 @@ private: SPEC_CONSTANT_DISABLE_DECALS = 13, SPEC_CONSTANT_DISABLE_FOG = 14, SPEC_CONSTANT_USE_DEPTH_FOG = 16, + SPEC_CONSTANT_IS_MULTIMESH = 17, }; @@ -197,7 +198,7 @@ private: void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true); void _fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_append = false); - void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0); + void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0); void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform); RID render_base_uniform_set; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index dd722cc2dd..cf661bb8f4 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -544,7 +544,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; - actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.inv_view_matrix[2].xyz"; actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(scene_data.view_matrix * read_model_matrix)[3].xyz"; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index fa8cf9c028..e58e45f13e 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -509,11 +509,16 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend current_repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED; } + Color modulated = rect->modulate * base_color; + if (use_linear_colors) { + modulated = modulated.srgb_to_linear(); + } + //bind pipeline if (rect->flags & CANVAS_RECT_LCD) { RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD_LCD_BLEND].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format); RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline); - RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, rect->modulate); + RD::get_singleton()->draw_list_set_blend_constants(p_draw_list, modulated); } else { RID pipeline = pipeline_variants->variants[light_mode][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format); RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline); @@ -582,11 +587,6 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend push_constant.flags |= FLAGS_USE_LCD; } - Color modulated = rect->modulate * base_color; - if (use_linear_colors) { - modulated = modulated.srgb_to_linear(); - } - push_constant.modulation[0] = modulated.r; push_constant.modulation[1] = modulated.g; push_constant.modulation[2] = modulated.b; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 96bcd72099..0ebed49ee9 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -1124,6 +1124,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render scene_data.camera_visible_layers = p_camera_data->visible_layers; scene_data.taa_jitter = p_camera_data->taa_jitter; scene_data.main_cam_transform = p_camera_data->main_transform; + scene_data.flip_y = !p_reflection_probe.is_valid(); scene_data.view_count = p_camera_data->view_count; for (uint32_t v = 0; v < p_camera_data->view_count; v++) { diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl index 8618f083b3..d629f2738d 100644 --- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl @@ -90,7 +90,7 @@ void main() { if (sc_multiview) { view_dir = normalize(vertex + scene_data.eye_offset[params.view_index].xyz); } else { - view_dir = normalize(vertex); + view_dir = params.orthogonal ? vec3(0.0, 0.0, -1.0) : normalize(vertex); } vec3 ray_dir = normalize(reflect(view_dir, normal)); diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection_inc.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection_inc.glsl index 26405ab040..9b692824a1 100644 --- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection_inc.glsl @@ -20,7 +20,7 @@ vec3 reconstructCSPosition(vec2 screen_pos, float z) { return pos.xyz; } else { if (params.orthogonal) { - return vec3((screen_pos.xy * params.proj_info.xy + params.proj_info.zw), z); + return vec3(-(screen_pos.xy * params.proj_info.xy + params.proj_info.zw), z); } else { return vec3((screen_pos.xy * params.proj_info.xy + params.proj_info.zw) * z, z); } diff --git a/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl b/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl index 4f81e36c58..0332e23993 100644 --- a/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl @@ -50,7 +50,7 @@ layout(r16f, set = 2, binding = 3) uniform restrict writeonly image2DArray dest_ vec4 screen_space_to_view_space_depth(vec4 p_depth) { if (params.orthogonal) { vec4 depth = p_depth * 2.0 - 1.0; - return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; + return -(depth * (params.z_far - params.z_near) - (params.z_far + params.z_near)) / 2.0; } float depth_linearize_mul = params.z_near; @@ -68,7 +68,7 @@ vec4 screen_space_to_view_space_depth(vec4 p_depth) { float screen_space_to_view_space_depth(float p_depth) { if (params.orthogonal) { float depth = p_depth * 2.0 - 1.0; - return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / (2.0 * params.z_far); + return -(depth * (params.z_far - params.z_near) - (params.z_far + params.z_near)) / 2.0; } float depth_linearize_mul = params.z_near; 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 1637326b48..a64b2e10ea 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 @@ -76,6 +76,10 @@ void axis_angle_to_tbn(vec3 axis, float angle, out vec3 tangent, out vec3 binorm normal = omc_axis.zzz * axis + vec3(-s_axis.y, s_axis.x, c); } +/* Spec Constants */ + +layout(constant_id = 17) const bool sc_is_multimesh = false; + /* Varyings */ layout(location = 0) highp out vec3 vertex_interp; @@ -178,8 +182,6 @@ void main() { color_interp = color_attrib; #endif - bool is_multimesh = bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_MULTIMESH); - mat4 model_matrix = instances.data[draw_call.instance_index].transform; mat4 inv_view_matrix = scene_data.inv_view_matrix; #ifdef USE_DOUBLE_PRECISION @@ -203,7 +205,7 @@ void main() { mat4 matrix; mat4 read_model_matrix = model_matrix; - if (is_multimesh) { + if (sc_is_multimesh) { //multimesh, instances are for it #ifdef USE_PARTICLE_TRAILS @@ -399,7 +401,7 @@ void main() { // Then we combine the translations from the model matrix and the view matrix using emulated doubles. // We add the result to the vertex and ignore the final lost precision. vec3 model_origin = model_matrix[3].xyz; - if (is_multimesh) { + if (sc_is_multimesh) { vertex = mat3(matrix) * vertex; model_origin = double_add_vec3(model_origin, model_precision, matrix[3].xyz, vec3(0.0), model_precision); } diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index b5fdf8bebb..2f44096dc8 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -70,8 +70,14 @@ void RenderSceneBuffersRD::_bind_methods() { ClassDB::bind_method(D_METHOD("get_render_target"), &RenderSceneBuffersRD::get_render_target); ClassDB::bind_method(D_METHOD("get_view_count"), &RenderSceneBuffersRD::get_view_count); ClassDB::bind_method(D_METHOD("get_internal_size"), &RenderSceneBuffersRD::get_internal_size); + ClassDB::bind_method(D_METHOD("get_target_size"), &RenderSceneBuffersRD::get_target_size); + ClassDB::bind_method(D_METHOD("get_scaling_3d_mode"), &RenderSceneBuffersRD::get_scaling_3d_mode); + ClassDB::bind_method(D_METHOD("get_fsr_sharpness"), &RenderSceneBuffersRD::get_fsr_sharpness); ClassDB::bind_method(D_METHOD("get_msaa_3d"), &RenderSceneBuffersRD::get_msaa_3d); + ClassDB::bind_method(D_METHOD("get_texture_samples"), &RenderSceneBuffersRD::get_texture_samples); + ClassDB::bind_method(D_METHOD("get_screen_space_aa"), &RenderSceneBuffersRD::get_screen_space_aa); ClassDB::bind_method(D_METHOD("get_use_taa"), &RenderSceneBuffersRD::get_use_taa); + ClassDB::bind_method(D_METHOD("get_use_debanding"), &RenderSceneBuffersRD::get_use_debanding); } void RenderSceneBuffersRD::update_sizes(NamedTexture &p_named_texture) { diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp index dc1e64ddcc..ba8aafda6d 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp @@ -42,7 +42,11 @@ Transform3D RenderSceneDataRD::get_cam_transform() const { } Projection RenderSceneDataRD::get_cam_projection() const { - return cam_projection; + Projection correction; + correction.set_depth_correction(flip_y); + correction.add_jitter_offset(taa_jitter); + + return correction * cam_projection; } uint32_t RenderSceneDataRD::get_view_count() const { @@ -58,14 +62,18 @@ Vector3 RenderSceneDataRD::get_view_eye_offset(uint32_t p_view) const { Projection RenderSceneDataRD::get_view_projection(uint32_t p_view) const { ERR_FAIL_UNSIGNED_INDEX_V(p_view, view_count, Projection()); - return view_projection[p_view]; + Projection correction; + correction.set_depth_correction(flip_y); + correction.add_jitter_offset(taa_jitter); + + return correction * view_projection[p_view]; } RID RenderSceneDataRD::create_uniform_buffer() { return RD::get_singleton()->uniform_buffer_create(sizeof(UBODATA)); } -void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) { +void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) { RendererSceneRenderRD *render_scene_render = RendererSceneRenderRD::get_singleton(); UBODATA ubo_data; @@ -76,7 +84,7 @@ void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p UBO &prev_ubo = ubo_data.prev_ubo; Projection correction; - correction.set_depth_correction(p_flip_y); + correction.set_depth_correction(flip_y); correction.add_jitter_offset(taa_jitter); Projection projection = correction * cam_projection; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h index f6785942ed..5579a97792 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h @@ -50,6 +50,7 @@ public: Vector2 taa_jitter; uint32_t camera_visible_layers; bool cam_orthogonal = false; + bool flip_y = false; // For billboards to cast correct shadows. Transform3D main_cam_transform; @@ -90,7 +91,7 @@ public: virtual Projection get_view_projection(uint32_t p_view) const override; RID create_uniform_buffer(); - void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier); + void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier); virtual RID get_uniform_buffer() const override; private: diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 80c1f67d8a..10e295ab04 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -899,6 +899,7 @@ void RendererViewport::viewport_set_use_xr(RID p_viewport, bool p_use_xr) { void RendererViewport::viewport_set_scaling_3d_mode(RID p_viewport, RS::ViewportScaling3DMode p_mode) { Viewport *viewport = viewport_owner.get_or_null(p_viewport); ERR_FAIL_NULL(viewport); + ERR_FAIL_COND_EDMSG(p_mode == RS::VIEWPORT_SCALING_3D_MODE_FSR && OS::get_singleton()->get_current_rendering_method() != "forward_plus", "FSR1 is only available when using the Forward+ renderer."); ERR_FAIL_COND_EDMSG(p_mode == RS::VIEWPORT_SCALING_3D_MODE_FSR2 && OS::get_singleton()->get_current_rendering_method() != "forward_plus", "FSR2 is only available when using the Forward+ renderer."); if (viewport->scaling_3d_mode == p_mode) { diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 801ad1b825..2784e22429 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -1617,9 +1617,6 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye thread_local LocalVector<RDD::BufferTextureCopyRegion> command_buffer_texture_copy_regions_vector; command_buffer_texture_copy_regions_vector.clear(); - uint32_t block_w = 0, block_h = 0; - get_compressed_image_format_block_dimensions(tex->format, block_w, block_h); - uint32_t w = tex->width; uint32_t h = tex->height; uint32_t d = tex->depth; @@ -1635,8 +1632,8 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye copy_region.texture_region_size.z = d; command_buffer_texture_copy_regions_vector.push_back(copy_region); - w = MAX(block_w, w >> 1); - h = MAX(block_h, h >> 1); + w = (w >> 1); + h = (h >> 1); d = MAX(1u, d >> 1); } @@ -1653,6 +1650,10 @@ Vector<uint8_t> RenderingDevice::texture_get_data(RID p_texture, uint32_t p_laye const uint8_t *read_ptr = driver->buffer_map(tmp_buffer); ERR_FAIL_NULL_V(read_ptr, Vector<uint8_t>()); + uint32_t block_w = 0; + uint32_t block_h = 0; + get_compressed_image_format_block_dimensions(tex->format, block_w, block_h); + Vector<uint8_t> buffer_data; uint32_t tight_buffer_size = get_image_format_required_size(tex->format, tex->width, tex->height, tex->depth, tex->mipmaps); buffer_data.resize(tight_buffer_size); @@ -3500,7 +3501,12 @@ Error RenderingDevice::screen_prepare_for_drawing(DisplayServer::WindowID p_scre framebuffer = driver->swap_chain_acquire_framebuffer(main_queue, it->value, resize_required); } - ERR_FAIL_COND_V_MSG(framebuffer.id == 0, FAILED, "Unable to acquire framebuffer."); + if (framebuffer.id == 0) { + // Some drivers like NVIDIA are fast enough to invalidate the swap chain between resizing and acquisition (GH-94104). + // This typically occurs during continuous window resizing operations, especially if done quickly. + // Allow this to fail silently since it has no visual consequences. + return ERR_CANT_CREATE; + } // Store the framebuffer that will be used next to draw to this screen. screen_framebuffers[p_screen] = framebuffer; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index f5e0b811a2..e35fde406f 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -39,6 +39,8 @@ #define HAS_WARNING(flag) (warning_flags & flag) +int ShaderLanguage::instance_counter = 0; + String ShaderLanguage::get_operator_text(Operator p_op) { static const char *op_names[OP_MAX] = { "==", "!=", @@ -5546,10 +5548,16 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons } } if (is_sampler_type(call_function->arguments[i].type)) { - //let's see where our argument comes from - ERR_CONTINUE(n->type != Node::NODE_TYPE_VARIABLE); //bug? this should always be a variable - VariableNode *vn = static_cast<VariableNode *>(n); - StringName varname = vn->name; + // Let's see where our argument comes from. + StringName varname; + if (n->type == Node::NODE_TYPE_VARIABLE) { + VariableNode *vn = static_cast<VariableNode *>(n); + varname = vn->name; + } else if (n->type == Node::NODE_TYPE_ARRAY) { + ArrayNode *an = static_cast<ArrayNode *>(n); + varname = an->name; + } + if (shader->uniforms.has(varname)) { //being sampler, this either comes from a uniform ShaderNode::Uniform *u = &shader->uniforms[varname]; @@ -10812,17 +10820,16 @@ ShaderLanguage::ShaderLanguage() { nodes = nullptr; completion_class = TAG_GLOBAL; - int idx = 0; - while (builtin_func_defs[idx].name) { - if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) { - const StringName &name = StringName(builtin_func_defs[idx].name); - - if (!global_func_set.has(name)) { - global_func_set.insert(name); + if (instance_counter == 0) { + int idx = 0; + while (builtin_func_defs[idx].name) { + if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) { + global_func_set.insert(builtin_func_defs[idx].name); } + idx++; } - idx++; } + instance_counter++; #ifdef DEBUG_ENABLED warnings_check_map.insert(ShaderWarning::UNUSED_CONSTANT, &used_constants); @@ -10837,5 +10844,8 @@ ShaderLanguage::ShaderLanguage() { ShaderLanguage::~ShaderLanguage() { clear(); - global_func_set.clear(); + instance_counter--; + if (instance_counter == 0) { + global_func_set.clear(); + } } diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index edac819a1e..076bd8def4 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -800,6 +800,8 @@ public: static bool is_control_flow_keyword(String p_keyword); static void get_builtin_funcs(List<String> *r_keywords); + static int instance_counter; + struct BuiltInInfo { DataType type = TYPE_VOID; bool constant = false; diff --git a/servers/rendering_server.h b/servers/rendering_server.h index e15dba4353..693c822488 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -44,14 +44,6 @@ // Helper macros for code outside of the rendering server, but that is // called by the rendering server. #ifdef DEBUG_ENABLED -#define ERR_ON_RENDER_THREAD \ - RenderingServer *rendering_server = RenderingServer::get_singleton(); \ - ERR_FAIL_NULL(rendering_server); \ - ERR_FAIL_COND(rendering_server->is_on_render_thread()); -#define ERR_ON_RENDER_THREAD_V(m_ret) \ - RenderingServer *rendering_server = RenderingServer::get_singleton(); \ - ERR_FAIL_NULL_V(rendering_server, m_ret); \ - ERR_FAIL_COND_V(rendering_server->is_on_render_thread(), m_ret); #define ERR_NOT_ON_RENDER_THREAD \ RenderingServer *rendering_server = RenderingServer::get_singleton(); \ ERR_FAIL_NULL(rendering_server); \ @@ -61,8 +53,6 @@ ERR_FAIL_NULL_V(rendering_server, m_ret); \ ERR_FAIL_COND_V(!rendering_server->is_on_render_thread(), m_ret); #else -#define ERR_ON_RENDER_THREAD -#define ERR_ON_RENDER_THREAD_V(m_ret) #define ERR_NOT_ON_RENDER_THREAD #define ERR_NOT_ON_RENDER_THREAD_V(m_ret) #endif diff --git a/tests/core/io/test_http_client.h b/tests/core/io/test_http_client.h index 96a7735d03..961c653a0a 100644 --- a/tests/core/io/test_http_client.h +++ b/tests/core/io/test_http_client.h @@ -35,6 +35,8 @@ #include "tests/test_macros.h" +#include "modules/modules_enabled.gen.h" + namespace TestHTTPClient { TEST_CASE("[HTTPClient] Instantiation") { @@ -90,6 +92,7 @@ TEST_CASE("[HTTPClient] verify_headers") { ERR_PRINT_ON; } +#if defined(MODULE_MBEDTLS_ENABLED) || defined(WEB_ENABLED) TEST_CASE("[HTTPClient] connect_to_host") { Ref<HTTPClient> client = HTTPClient::create(); String host = "https://www.example.com"; @@ -100,6 +103,7 @@ TEST_CASE("[HTTPClient] connect_to_host") { Error err = client->connect_to_host(host, port, tls_options); CHECK_MESSAGE(err == OK, "Expected OK for successful connection"); } +#endif // MODULE_MBEDTLS_ENABLED || WEB_ENABLED } // namespace TestHTTPClient diff --git a/tests/core/io/test_image.h b/tests/core/io/test_image.h index 1b51286a9f..0698c60f2a 100644 --- a/tests/core/io/test_image.h +++ b/tests/core/io/test_image.h @@ -37,6 +37,8 @@ #include "tests/test_utils.h" #include "thirdparty/doctest/doctest.h" +#include "modules/modules_enabled.gen.h" + namespace TestImage { TEST_CASE("[Image] Instantiation") { @@ -107,6 +109,7 @@ TEST_CASE("[Image] Saving and loading") { image->get_data() == image_load->get_data(), "The loaded image should have the same data as the one that got saved."); +#ifdef MODULE_BMP_ENABLED // Load BMP Ref<Image> image_bmp = memnew(Image()); Ref<FileAccess> f_bmp = FileAccess::open(TestUtils::get_data_path("images/icon.bmp"), FileAccess::READ, &err); @@ -117,7 +120,9 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_bmp->load_bmp_from_buffer(data_bmp) == OK, "The BMP image should load successfully."); +#endif // MODULE_BMP_ENABLED +#ifdef MODULE_JPG_ENABLED // Load JPG Ref<Image> image_jpg = memnew(Image()); Ref<FileAccess> f_jpg = FileAccess::open(TestUtils::get_data_path("images/icon.jpg"), FileAccess::READ, &err); @@ -128,7 +133,9 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_jpg->load_jpg_from_buffer(data_jpg) == OK, "The JPG image should load successfully."); +#endif // MODULE_JPG_ENABLED +#ifdef MODULE_WEBP_ENABLED // Load WebP Ref<Image> image_webp = memnew(Image()); Ref<FileAccess> f_webp = FileAccess::open(TestUtils::get_data_path("images/icon.webp"), FileAccess::READ, &err); @@ -139,6 +146,7 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_webp->load_webp_from_buffer(data_webp) == OK, "The WebP image should load successfully."); +#endif // MODULE_WEBP_ENABLED // Load PNG Ref<Image> image_png = memnew(Image()); @@ -151,6 +159,7 @@ TEST_CASE("[Image] Saving and loading") { image_png->load_png_from_buffer(data_png) == OK, "The PNG image should load successfully."); +#ifdef MODULE_TGA_ENABLED // Load TGA Ref<Image> image_tga = memnew(Image()); Ref<FileAccess> f_tga = FileAccess::open(TestUtils::get_data_path("images/icon.tga"), FileAccess::READ, &err); @@ -161,6 +170,7 @@ TEST_CASE("[Image] Saving and loading") { CHECK_MESSAGE( image_tga->load_tga_from_buffer(data_tga) == OK, "The TGA image should load successfully."); +#endif // MODULE_TGA_ENABLED } TEST_CASE("[Image] Basic getters") { @@ -345,8 +355,8 @@ TEST_CASE("[Image] Custom mipmaps") { uint8_t *data_ptr = data.ptrw(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i++) { @@ -368,8 +378,8 @@ TEST_CASE("[Image] Custom mipmaps") { const uint8_t *data_ptr = data.ptr(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image_bytes->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i++) { @@ -392,8 +402,8 @@ TEST_CASE("[Image] Custom mipmaps") { const uint8_t *data_ptr = data.ptr(); for (int mip = 0; mip < mipmaps; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; image_rgbaf->get_mipmap_offset_and_size(mip, mip_offset, mip_size); for (int i = 0; i < mip_size; i += 4) { diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h index a9bc2e9b99..f8c5ef279d 100644 --- a/tests/core/math/test_basis.h +++ b/tests/core/math/test_basis.h @@ -93,9 +93,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) { Basis res = to_rotation.inverse() * rotation_from_computed_euler; - CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))).utf8().ptr()); + CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0)))); + CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1)))); + CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2)))); // Double check `to_rotation` decomposing with XYZ rotation order. const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ); @@ -103,13 +103,13 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) { res = to_rotation.inverse() * rotation_from_xyz_computed_euler; - CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))).utf8().ptr()); - CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))).utf8().ptr()); + CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0)))); + CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1)))); + CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2)))); - INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)).utf8().ptr()); - INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)).utf8().ptr()); - INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation))).utf8().ptr()); + INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order))); + INFO(vformat("Original Rotation: %s\n", String(deg_original_euler))); + INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation)))); } TEST_CASE("[Basis] Euler conversions") { diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index 358bbc08a3..c1aa39031d 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -405,7 +405,7 @@ void validate_argument(const Context &p_context, const ExposedClass &p_class, co err_msg += " " + type_error_msg; } - TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data()); + TEST_COND(!arg_defval_assignable_to_type, err_msg); } } @@ -590,7 +590,7 @@ void add_exposed_classes(Context &r_context) { exposed_class.name, method.name); TEST_FAIL_COND_WARN( (exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free"), - warn_msg.utf8().get_data()); + warn_msg); } else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { method.return_type.name = return_info.class_name; @@ -720,7 +720,7 @@ void add_exposed_classes(Context &r_context) { "Signal name conflicts with %s: '%s.%s.", method_conflict ? "method" : "property", class_name, signal.name); TEST_FAIL_COND((method_conflict || exposed_class.find_method_by_name(signal.name)), - warn_msg.utf8().get_data()); + warn_msg); exposed_class.signals_.push_back(signal); } diff --git a/tests/scene/test_graph_node.h b/tests/scene/test_graph_node.h index 72b8b682c9..bf6cc9be09 100644 --- a/tests/scene/test_graph_node.h +++ b/tests/scene/test_graph_node.h @@ -42,7 +42,7 @@ TEST_CASE("[GraphNode][SceneTree]") { SUBCASE("[GraphNode] Graph Node only child on delete should not cause error.") { // Setup. GraphNode *test_node = memnew(GraphNode); - test_child->set_name("Graph Node"); + test_node->set_name("Graph Node"); Control *test_child = memnew(Control); test_child->set_name("child"); test_node->add_child(test_child); diff --git a/tests/scene/test_path_follow_2d.h b/tests/scene/test_path_follow_2d.h index 1958befa18..45ae0dff5d 100644 --- a/tests/scene/test_path_follow_2d.h +++ b/tests/scene/test_path_follow_2d.h @@ -32,205 +32,223 @@ #define TEST_PATH_FOLLOW_2D_H #include "scene/2d/path_2d.h" +#include "scene/main/window.h" #include "tests/test_macros.h" namespace TestPathFollow2D { -TEST_CASE("[PathFollow2D] Sampling with progress ratio") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +bool is_equal_approx(const Vector2 &p_a, const Vector2 &p_b) { + const real_t tolerance = 0.001; + return Math::is_equal_approx(p_a.x, p_b.x, tolerance) && + Math::is_equal_approx(p_a.y, p_b.y, tolerance); +} + +TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress ratio") { + Ref<Curve2D> curve = memnew(Curve2D); + curve->set_bake_interval(1); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); + path_follow_2d->set_loop(false); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.125); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.375); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 50))); + CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 100))); + CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.625); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 100))); + CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.75); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 100))); + CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(0.875); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 50))); + CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress_ratio(1); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Sampling with progress") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Sampling with progress") { + Ref<Curve2D> curve = memnew(Curve2D); + curve->set_bake_interval(1); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); curve->add_point(Vector2(0, 100)); curve->add_point(Vector2(0, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); + path_follow_2d->set_loop(false); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress(0); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(50); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(100); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(150); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 50))); + CHECK(is_equal_approx(Vector2(100, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(200); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 100))); + CHECK(is_equal_approx(Vector2(100, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(250); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 100))); + CHECK(is_equal_approx(Vector2(50, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(300); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 100))); + CHECK(is_equal_approx(Vector2(0, 100), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(350); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 50))); + CHECK(is_equal_approx(Vector2(0, 50), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_progress(400); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(0, 0))); + CHECK(is_equal_approx(Vector2(0, 0), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Removal of a point in curve") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Removal of a point in curve") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); curve->add_point(Vector2(100, 100)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(100, 0))); + CHECK(is_equal_approx(Vector2(100, 0), path_follow_2d->get_transform().get_origin())); curve->remove_point(1); + path_follow_2d->set_progress_ratio(0.5); CHECK_MESSAGE( - path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 50)), + is_equal_approx(Vector2(50, 50), path_follow_2d->get_transform().get_origin()), "Path follow's position should be updated after removing a point from the curve"); memdelete(path); } -TEST_CASE("[PathFollow2D] Setting h_offset and v_offset") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Setting h_offset and v_offset") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_progress_ratio(0.5); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(50, 0))); + CHECK(is_equal_approx(Vector2(50, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_h_offset(25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(75, 0))); + CHECK(is_equal_approx(Vector2(75, 0), path_follow_2d->get_transform().get_origin())); path_follow_2d->set_v_offset(25); - CHECK(path_follow_2d->get_transform().get_origin().is_equal_approx(Vector2(75, 25))); + CHECK(is_equal_approx(Vector2(75, 25), path_follow_2d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow2D] Unit offset out of range") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Progress ratio out of range") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_loop(true); path_follow_2d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0.7, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.7), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_2d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0.3, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), (real_t)0.3), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_2d->set_loop(false); path_follow_2d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 0, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 0), "Progress Ratio should be clamped at 0"); path_follow_2d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_2d->get_progress_ratio() == 1, + Math::is_equal_approx(path_follow_2d->get_progress_ratio(), 1), "Progress Ratio should be clamped at 1"); memdelete(path); } -TEST_CASE("[PathFollow2D] Progress out of range") { - const Ref<Curve2D> &curve = memnew(Curve2D()); +TEST_CASE("[SceneTree][PathFollow2D] Progress out of range") { + Ref<Curve2D> curve = memnew(Curve2D); curve->add_point(Vector2(0, 0)); curve->add_point(Vector2(100, 0)); - const Path2D *path = memnew(Path2D); + Path2D *path = memnew(Path2D); path->set_curve(curve); - const PathFollow2D *path_follow_2d = memnew(PathFollow2D); + PathFollow2D *path_follow_2d = memnew(PathFollow2D); path->add_child(path_follow_2d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_2d->set_loop(true); path_follow_2d->set_progress(-50); CHECK_MESSAGE( - path_follow_2d->get_progress() == 50, + Math::is_equal_approx(path_follow_2d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_2d->set_progress(150); CHECK_MESSAGE( - path_follow_2d->get_progress() == 50, + Math::is_equal_approx(path_follow_2d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_2d->set_loop(false); path_follow_2d->set_progress(-50); CHECK_MESSAGE( - path_follow_2d->get_progress() == 0, + Math::is_equal_approx(path_follow_2d->get_progress(), 0), "Progress should be clamped at 0"); path_follow_2d->set_progress(150); CHECK_MESSAGE( - path_follow_2d->get_progress() == 100, + Math::is_equal_approx(path_follow_2d->get_progress(), 100), "Progress should be clamped at 1"); memdelete(path); diff --git a/tests/scene/test_path_follow_3d.h b/tests/scene/test_path_follow_3d.h index 7595fddd2f..d08af3a70c 100644 --- a/tests/scene/test_path_follow_3d.h +++ b/tests/scene/test_path_follow_3d.h @@ -32,188 +32,289 @@ #define TEST_PATH_FOLLOW_3D_H #include "scene/3d/path_3d.h" +#include "scene/main/window.h" #include "tests/test_macros.h" namespace TestPathFollow3D { -TEST_CASE("[PathFollow3D] Sampling with progress ratio") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +bool is_equal_approx(const Vector3 &p_a, const Vector3 &p_b) { + const real_t tolerance = 0.001; + return Math::is_equal_approx(p_a.x, p_b.x, tolerance) && + Math::is_equal_approx(p_a.y, p_b.y, tolerance) && + Math::is_equal_approx(p_a.z, p_b.z, tolerance); +} + +TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress ratio") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); curve->add_point(Vector3(100, 100, 100)); curve->add_point(Vector3(100, 0, 100)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path_follow_3d->set_loop(false); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress_ratio(0); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(0, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.125); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(50, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.25); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.375); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.5); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.625); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 50))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.75); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(0.875); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress_ratio(1); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow3D] Sampling with progress") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Sampling with progress") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); curve->add_point(Vector3(100, 100, 100)); curve->add_point(Vector3(100, 0, 100)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path_follow_3d->set_loop(false); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress(0); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(0, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(50); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(50, 0, 0)); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(50, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(100); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(150); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(200); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 0), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(250); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 50))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 50), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(300); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 100, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 100, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(350); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 50, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 50, 100), path_follow_3d->get_transform().get_origin())); path_follow_3d->set_progress(400); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector3(100, 0, 100))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 100), path_follow_3d->get_transform().get_origin())); memdelete(path); } -TEST_CASE("[PathFollow3D] Removal of a point in curve") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Removal of a point in curve") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); curve->add_point(Vector3(100, 100, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_progress_ratio(0.5); - CHECK(path_follow_3d->get_transform().get_origin().is_equal_approx(Vector2(100, 0, 0))); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(100, 0, 0), path_follow_3d->get_transform().get_origin())); curve->remove_point(1); + path_follow_3d->set_progress_ratio(0.5); + path_follow_3d->update_transform(true); CHECK_MESSAGE( - path_follow_3d->get_transform().get_origin().is_equal_approx(Vector2(50, 50, 0)), + is_equal_approx(Vector3(50, 50, 0), path_follow_3d->get_transform().get_origin()), "Path follow's position should be updated after removing a point from the curve"); memdelete(path); } -TEST_CASE("[PathFollow3D] Progress ratio out of range") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Progress ratio out of range") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_loop(true); path_follow_3d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0.7, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.7), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_3d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0.3, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), (real_t)0.3), "Progress Ratio should loop back from the end in the opposite direction"); path_follow_3d->set_loop(false); path_follow_3d->set_progress_ratio(-0.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 0, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 0), "Progress Ratio should be clamped at 0"); path_follow_3d->set_progress_ratio(1.3); CHECK_MESSAGE( - path_follow_3d->get_progress_ratio() == 1, + Math::is_equal_approx(path_follow_3d->get_progress_ratio(), 1), "Progress Ratio should be clamped at 1"); memdelete(path); } -TEST_CASE("[PathFollow3D] Progress out of range") { - const Ref<Curve3D> &curve = memnew(Curve3D()); +TEST_CASE("[SceneTree][PathFollow3D] Progress out of range") { + Ref<Curve3D> curve = memnew(Curve3D); curve->add_point(Vector3(0, 0, 0)); curve->add_point(Vector3(100, 0, 0)); - const Path3D *path = memnew(Path3D); + Path3D *path = memnew(Path3D); path->set_curve(curve); - const PathFollow3D *path_follow_3d = memnew(PathFollow3D); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); path_follow_3d->set_loop(true); path_follow_3d->set_progress(-50); CHECK_MESSAGE( - path_follow_3d->get_progress() == 50, + Math::is_equal_approx(path_follow_3d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_3d->set_progress(150); CHECK_MESSAGE( - path_follow_3d->get_progress() == 50, + Math::is_equal_approx(path_follow_3d->get_progress(), 50), "Progress should loop back from the end in the opposite direction"); path_follow_3d->set_loop(false); path_follow_3d->set_progress(-50); CHECK_MESSAGE( - path_follow_3d->get_progress() == 0, + Math::is_equal_approx(path_follow_3d->get_progress(), 0), "Progress should be clamped at 0"); path_follow_3d->set_progress(150); CHECK_MESSAGE( - path_follow_3d->get_progress() == 100, + Math::is_equal_approx(path_follow_3d->get_progress(), 100), "Progress should be clamped at max value of curve"); memdelete(path); } + +TEST_CASE("[SceneTree][PathFollow3D] Calculate forward vector") { + const real_t dist_cube_100 = 100 * Math::sqrt(3.0); + Ref<Curve3D> curve = memnew(Curve3D); + curve->add_point(Vector3(0, 0, 0)); + curve->add_point(Vector3(100, 0, 0)); + curve->add_point(Vector3(200, 100, -100)); + curve->add_point(Vector3(200, 100, 200)); + curve->add_point(Vector3(100, 0, 100)); + curve->add_point(Vector3(0, 0, 100)); + Path3D *path = memnew(Path3D); + path->set_curve(curve); + PathFollow3D *path_follow_3d = memnew(PathFollow3D); + path->add_child(path_follow_3d); + SceneTree::get_singleton()->get_root()->add_child(path); + + path_follow_3d->set_loop(false); + path_follow_3d->set_rotation_mode(PathFollow3D::RotationMode::ROTATION_ORIENTED); + + path_follow_3d->set_progress(-50); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(0); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(50); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100 + dist_cube_100 / 2); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(100 + dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(-0.577348, -0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(250 + dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0, 0, -1), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + 1.5 * dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(400 + 2 * dist_cube_100 - 0.01); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(0.577348, 0.577348, 0.577348), path_follow_3d->get_transform().get_basis().get_column(2))); + + path_follow_3d->set_progress(500 + 2 * dist_cube_100); + path_follow_3d->update_transform(true); + CHECK(is_equal_approx(Vector3(1, 0, 0), path_follow_3d->get_transform().get_basis().get_column(2))); + + memdelete(path); +} } // namespace TestPathFollow3D #endif // TEST_PATH_FOLLOW_3D_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 3c875797a4..edadc52a16 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -30,6 +30,8 @@ #include "test_main.h" +#include "modules/modules_enabled.gen.h" + #ifdef TOOLS_ENABLED #include "editor/editor_paths.h" #include "editor/editor_settings.h" @@ -103,8 +105,6 @@ #include "tests/scene/test_audio_stream_wav.h" #include "tests/scene/test_bit_map.h" #include "tests/scene/test_camera_2d.h" -#include "tests/scene/test_code_edit.h" -#include "tests/scene/test_color_picker.h" #include "tests/scene/test_control.h" #include "tests/scene/test_curve.h" #include "tests/scene/test_curve_2d.h" @@ -117,8 +117,8 @@ #include "tests/scene/test_node_2d.h" #include "tests/scene/test_packed_scene.h" #include "tests/scene/test_path_2d.h" +#include "tests/scene/test_path_follow_2d.h" #include "tests/scene/test_sprite_frames.h" -#include "tests/scene/test_text_edit.h" #include "tests/scene/test_theme.h" #include "tests/scene/test_timer.h" #include "tests/scene/test_viewport.h" @@ -128,19 +128,30 @@ #include "tests/servers/test_text_server.h" #include "tests/test_validate_testing.h" +#ifndef ADVANCED_GUI_DISABLED +#include "tests/scene/test_code_edit.h" +#include "tests/scene/test_color_picker.h" +#include "tests/scene/test_graph_node.h" +#include "tests/scene/test_text_edit.h" +#endif // ADVANCED_GUI_DISABLED + #ifndef _3D_DISABLED -#include "tests/scene/test_arraymesh.h" -#include "tests/scene/test_camera_3d.h" +#ifdef MODULE_NAVIGATION_ENABLED #include "tests/scene/test_navigation_agent_2d.h" #include "tests/scene/test_navigation_agent_3d.h" #include "tests/scene/test_navigation_obstacle_2d.h" #include "tests/scene/test_navigation_obstacle_3d.h" #include "tests/scene/test_navigation_region_2d.h" #include "tests/scene/test_navigation_region_3d.h" -#include "tests/scene/test_path_3d.h" -#include "tests/scene/test_primitives.h" #include "tests/servers/test_navigation_server_2d.h" #include "tests/servers/test_navigation_server_3d.h" +#endif // MODULE_NAVIGATION_ENABLED + +#include "tests/scene/test_arraymesh.h" +#include "tests/scene/test_camera_3d.h" +#include "tests/scene/test_path_3d.h" +#include "tests/scene/test_path_follow_3d.h" +#include "tests/scene/test_primitives.h" #endif // _3D_DISABLED #include "modules/modules_tests.gen.h" diff --git a/thirdparty/README.md b/thirdparty/README.md index 4572687be2..9a56f6baa4 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.14.0 (ae4e9d003c93325f1eba64319fa9852a0d764b4c, 2024) +- Version: 0.14.2 (f6c4d8a94e0b2194fe911d6e19a550683055dd50, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 6218c18e68..8c185ccbca 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.14.0" +#define THORVG_VERSION_STRING "0.14.2" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index 0b7e9771b9..47414d851a 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -281,6 +281,9 @@ public: * The rotational axis passes through the point on the object with zero coordinates. * * @param[in] degree The value of the angle in degrees. + * + * @retval Result::InsufficientCondition in case a custom transform is applied. + * @see Paint::transform() */ Result rotate(float degree) noexcept; @@ -288,6 +291,9 @@ public: * @brief Sets the scale value of the object. * * @param[in] factor The value of the scaling factor. The default value is 1. + * + * @retval Result::InsufficientCondition in case a custom transform is applied. + * @see Paint::transform() */ Result scale(float factor) noexcept; @@ -299,6 +305,9 @@ public: * * @param[in] x The value of the horizontal shift. * @param[in] y The value of the vertical shift. + * + * @retval Result::InsufficientCondition in case a custom transform is applied. + * @see Paint::transform() */ Result translate(float x, float y) noexcept; @@ -631,6 +640,8 @@ public: * The Canvas rendering can be performed asynchronously. To make sure that rendering is finished, * the sync() must be called after the draw() regardless of threading. * + * @retval Result::InsufficientCondition: The canvas is either already in sync condition or in a damaged condition (a draw is required before syncing). + * * @see Canvas::draw() */ virtual Result sync() noexcept; diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp index da48f9eae2..9d704900a5 100644 --- a/thirdparty/thorvg/src/common/tvgLines.cpp +++ b/thirdparty/thorvg/src/common/tvgLines.cpp @@ -238,7 +238,7 @@ float bezAngleAt(const Bezier& bz, float t) pt.x *= 3; pt.y *= 3; - return mathRad2Deg(atan2(pt.x, pt.y)); + return mathRad2Deg(mathAtan2(pt.y, pt.x)); } diff --git a/thirdparty/thorvg/src/common/tvgLock.h b/thirdparty/thorvg/src/common/tvgLock.h index d8bf7269f6..59f68d0d44 100644 --- a/thirdparty/thorvg/src/common/tvgLock.h +++ b/thirdparty/thorvg/src/common/tvgLock.h @@ -28,6 +28,7 @@ #define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR #include <mutex> +#include "tvgTaskScheduler.h" namespace tvg { @@ -42,13 +43,17 @@ namespace tvg { ScopedLock(Key& k) { - k.mtx.lock(); - key = &k; + if (TaskScheduler::threads() > 0) { + k.mtx.lock(); + key = &k; + } } ~ScopedLock() { - key->mtx.unlock(); + if (TaskScheduler::threads() > 0) { + key->mtx.unlock(); + } } }; diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index e99ec46681..c56b32249f 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -22,6 +22,20 @@ #include "tvgMath.h" +//see: https://en.wikipedia.org/wiki/Remez_algorithm +float mathAtan2(float y, float x) +{ + if (y == 0.0f && x == 0.0f) return 0.0f; + + auto a = std::min(fabsf(x), fabsf(y)) / std::max(fabsf(x), fabsf(y)); + auto s = a * a; + auto r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a; + if (fabsf(y) > fabsf(x)) r = 1.57079637f - r; + if (x < 0) r = 3.14159274f - r; + if (y < 0) return -r; + return r; +} + bool mathInverse(const Matrix* m, Matrix* out) { diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 3555885c8e..668260c689 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -42,6 +42,7 @@ /* General functions */ /************************************************************************/ +float mathAtan2(float y, float x); static inline float mathDeg2Rad(float degree) { @@ -79,7 +80,7 @@ bool operator==(const Matrix& lhs, const Matrix& rhs); static inline bool mathRightAngle(const Matrix* m) { - auto radian = fabsf(atan2f(m->e21, m->e11)); + auto radian = fabsf(mathAtan2(m->e21, m->e11)); if (radian < FLOAT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true; return false; } diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index ae784ccfd7..8fbf3816ea 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -647,9 +647,9 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re } } - *red = static_cast<uint8_t>(ceil(_red * 255.0f)); - *green = static_cast<uint8_t>(ceil(_green * 255.0f)); - *blue = static_cast<uint8_t>(ceil(_blue * 255.0f)); + *red = (uint8_t)nearbyint(_red * 255.0f); + *green = (uint8_t)nearbyint(_green * 255.0f); + *blue = (uint8_t)nearbyint(_blue * 255.0f); return true; } @@ -3254,19 +3254,34 @@ static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc) } -static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content) +static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content, unsigned int length) { + const char* itr = nullptr; + int sz = length; + char tagName[20] = ""; + content = _skipSpace(content, nullptr); + itr = content; + while ((itr != nullptr) && *itr != '>') itr++; + + if (itr) { + sz = itr - content; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + if ((unsigned int)sz >= sizeof(tagName)) sz = sizeof(tagName) - 1; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + } + else return; for (unsigned int i = 0; i < sizeof(groupTags) / sizeof(groupTags[0]); i++) { - if (!strncmp(content, groupTags[i].tag, groupTags[i].sz - 1)) { + if (!strncmp(tagName, groupTags[i].tag, sz)) { loader->stack.pop(); break; } } for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) { - if (!strncmp(content, graphicsTags[i].tag, graphicsTags[i].sz - 1)) { + if (!strncmp(tagName, graphicsTags[i].tag, sz)) { loader->currentGraphicsNode = nullptr; loader->stack.pop(); break; @@ -3437,7 +3452,7 @@ static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content break; } case SimpleXMLType::Close: { - _svgLoaderParserXmlClose(loader, content); + _svgLoaderParserXmlClose(loader, content, length); break; } case SimpleXMLType::Data: diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp index 63e9ce53c8..115e81aee1 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp @@ -194,10 +194,10 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P //We dont' use arccos (as per w3c doc), see //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm //Note: atan2 (0.0, 1.0) == 0.0 - at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); + at = mathAtan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at; - nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); + nat = mathAtan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at; if (sweep) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 231410cdac..05cbdc7f3a 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -23,6 +23,7 @@ #ifndef _TVG_SW_COMMON_H_ #define _TVG_SW_COMMON_H_ +#include <algorithm> #include "tvgCommon.h" #include "tvgRender.h" diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index be1662daeb..bd0b5ffdcb 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -63,6 +63,66 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f } +static uint32_t _estimateAAMargin(const Fill* fdata) +{ + constexpr float marginScalingFactor = 800.0f; + if (fdata->identifier() == TVG_CLASS_ID_RADIAL) { + auto radius = P(static_cast<const RadialGradient*>(fdata))->r; + return mathZero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius); + } + auto grad = P(static_cast<const LinearGradient*>(fdata)); + Point p1 {grad->x1, grad->y1}; + Point p2 {grad->x2, grad->y2}; + auto length = mathLength(&p1, &p2); + return mathZero(length) ? 0 : static_cast<uint32_t>(marginScalingFactor / length); +} + + +static void _adjustAAMargin(uint32_t& iMargin, uint32_t index) +{ + constexpr float threshold = 0.1f; + constexpr uint32_t iMarginMax = 40; + + auto iThreshold = static_cast<uint32_t>(index * threshold); + if (iMargin > iThreshold) iMargin = iThreshold; + if (iMargin > iMarginMax) iMargin = iMarginMax; +} + + +static inline uint32_t _alphaUnblend(uint32_t c) +{ + auto a = (c >> 24); + if (a == 255 || a == 0) return c; + auto invA = 255.0f / static_cast<float>(a); + auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA); + auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA); + auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA); + + return (a << 24) | (c0 << 16) | (c1 << 8) | c2; +} + + +static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end) +{ + if (begin == 0 || end == 0) return; + + auto i = GRADIENT_STOP_SIZE - end; + auto rgbaEnd = _alphaUnblend(fill->ctable[i]); + auto rgbaBegin = _alphaUnblend(fill->ctable[begin]); + + auto dt = 1.0f / (begin + end + 1.0f); + float t = dt; + while (i != begin) { + auto dist = 255 - static_cast<int32_t>(255 * t); + auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist); + fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24)); + + if (i == GRADIENT_STOP_SIZE) i = 0; + t += dt; + } +} + + static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity) { if (!fill->ctable) { @@ -88,6 +148,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* auto pos = 1.5f * inc; uint32_t i = 0; + //If repeat is true, anti-aliasing must be applied between the last and the first colors. + auto repeat = fill->spread == FillSpread::Repeat; + uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0; + uint32_t iAAEnd = 0; + fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a); while (pos <= pColors->offset) { @@ -97,6 +162,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* } for (uint32_t j = 0; j < cnt - 1; ++j) { + if (repeat && j == cnt - 2 && iAAEnd == 0) { + iAAEnd = iAABegin; + _adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i); + } + auto curr = colors + j; auto next = curr + 1; auto delta = 1.0f / (next->offset - curr->offset); @@ -118,14 +188,18 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* } rgba = rgba2; a = a2; + + if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1); } rgba = ALPHA_BLEND((rgba | 0xff000000), a); for (; i < GRADIENT_STOP_SIZE; ++i) fill->ctable[i] = rgba; - //Make sure the last color stop is represented at the end of the table - fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; + //For repeat fill spread apply anti-aliasing between the last and first colors, + //othewise make sure the last color stop is represented at the end of the table. + if (repeat) _applyAA(fill, iAABegin, iAAEnd); + else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; return true; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp index c162945501..e1d41a0d52 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp @@ -114,8 +114,8 @@ bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transfor //Fast track: Non-transformed image but just shifted. if (image->direct) { - image->ox = -static_cast<int32_t>(round(transform->e13)); - image->oy = -static_cast<int32_t>(round(transform->e23)); + image->ox = -static_cast<int32_t>(nearbyint(transform->e13)); + image->oy = -static_cast<int32_t>(nearbyint(transform->e23)); //Figure out the scale factor by transform matrix } else { auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21)); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp index ad5a2b7371..ae158c836a 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp @@ -164,8 +164,8 @@ void mathRotate(SwPoint& pt, SwFixed angle) auto cosv = cosf(radian); auto sinv = sinf(radian); - pt.x = SwCoord(roundf((v.x * cosv - v.y * sinv) * 64.0f)); - pt.y = SwCoord(roundf((v.x * sinv + v.y * cosv) * 64.0f)); + pt.x = SwCoord(nearbyint((v.x * cosv - v.y * sinv) * 64.0f)); + pt.y = SwCoord(nearbyint((v.x * sinv + v.y * cosv) * 64.0f)); } @@ -179,7 +179,7 @@ SwFixed mathTan(SwFixed angle) SwFixed mathAtan(const SwPoint& pt) { if (pt.zero()) return 0; - return SwFixed(atan2f(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f); + return SwFixed(mathAtan2(TO_FLOAT(pt.y), TO_FLOAT(pt.x)) * (180.0f / MATH_PI) * 65536.0f); } @@ -309,10 +309,10 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S //the rasterization region has to be rearranged. //https://github.com/Samsung/thorvg/issues/916 if (fastTrack) { - renderRegion.min.x = static_cast<SwCoord>(round(xMin / 64.0f)); - renderRegion.max.x = static_cast<SwCoord>(round(xMax / 64.0f)); - renderRegion.min.y = static_cast<SwCoord>(round(yMin / 64.0f)); - renderRegion.max.y = static_cast<SwCoord>(round(yMax / 64.0f)); + renderRegion.min.x = static_cast<SwCoord>(nearbyint(xMin / 64.0f)); + renderRegion.max.x = static_cast<SwCoord>(nearbyint(xMax / 64.0f)); + renderRegion.min.y = static_cast<SwCoord>(nearbyint(yMin / 64.0f)); + renderRegion.max.y = static_cast<SwCoord>(nearbyint(yMax / 64.0f)); } else { renderRegion.min.x = xMin >> 6; renderRegion.max.x = (xMax + 63) >> 6; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index 04f36c727f..042d1e2b44 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -383,7 +383,8 @@ static bool _rasterMattedRect(SwSurface* surface, const SwBBox& region, uint8_t auto dst = &buffer[y * surface->stride]; auto cmp = &cbuffer[y * surface->compositor->image.stride * csize]; for (uint32_t x = 0; x < w; ++x, ++dst, cmp += csize) { - *dst = INTERPOLATE(color, *dst, alpha(cmp)); + auto tmp = ALPHA_BLEND(color, alpha(cmp)); + *dst = tmp + ALPHA_BLEND(*dst, IA(tmp)); } } //8bits grayscale @@ -674,7 +675,7 @@ static bool _rasterRle(SwSurface* surface, SwRleData* rle, uint8_t r, uint8_t g, auto sy = (y) * itransform->e22 + itransform->e23 - 0.49f; \ if (sy <= -0.5f || (uint32_t)(sy + 0.5f) >= image->h) continue; \ if (scaleMethod == _interpDownScaler) { \ - auto my = (int32_t)round(sy); \ + auto my = (int32_t)nearbyint(sy); \ miny = my - (int32_t)sampleSize; \ if (miny < 0) miny = 0; \ maxy = my + (int32_t)sampleSize; \ @@ -1087,6 +1088,7 @@ static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* im static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + TVGERR("SW_ENGINE", "Not supported ScaledMaskedImage!"); #if 0 //Enable it when GRAYSCALE image is supported TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y); @@ -1100,6 +1102,11 @@ static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, c static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale scaled matted image!"); + return false; + } + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto csize = surface->compositor->image.channelSize; auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize; @@ -1130,6 +1137,11 @@ static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, c static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { + if (surface->channelSize == sizeof(uint8_t)) { + TVGERR("SW_ENGINE", "Not supported grayscale scaled blending image!"); + return false; + } + auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); @@ -1152,19 +1164,33 @@ static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity) { - auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler; auto sampleSize = _sampleSize(image->scale); int32_t miny = 0, maxy = 0; - for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) { - SCALED_IMAGE_RANGE_Y(y) - auto dst = dbuffer; - for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { - SCALED_IMAGE_RANGE_X - auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); - if (opacity < 255) src = ALPHA_BLEND(src, opacity); - *dst = src + ALPHA_BLEND(*dst, IA(src)); + //32bits channels + if (surface->channelSize == sizeof(uint32_t)) { + auto buffer = surface->buf32 + (region.min.y * surface->stride + region.min.x); + for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = buffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + if (opacity < 255) src = ALPHA_BLEND(src, opacity); + *dst = src + ALPHA_BLEND(*dst, IA(src)); + } + } + } else if (surface->channelSize == sizeof(uint8_t)) { + auto buffer = surface->buf8 + (region.min.y * surface->stride + region.min.x); + for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) { + SCALED_IMAGE_RANGE_Y(y) + auto dst = buffer; + for (auto x = region.min.x; x < region.max.x; ++x, ++dst) { + SCALED_IMAGE_RANGE_X + auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize); + *dst = MULTIPLY(A(src), opacity); + } } } return true; @@ -1173,11 +1199,6 @@ static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const M static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity) { - if (surface->channelSize == sizeof(uint8_t)) { - TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!"); - return false; - } - Matrix itransform; if (transform) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 0a3f5ef7e7..350f333405 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include <algorithm> #include "tvgMath.h" #include "tvgSwCommon.h" #include "tvgTaskScheduler.h" @@ -86,7 +87,7 @@ struct SwShapeTask : SwTask Additionally, the stroke style should not be dashed. */ bool antialiasing(float strokeWidth) { - return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim(); + return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;; } float validStrokeWidth() diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 1e5c4ef409..25c6cd90b9 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -197,7 +197,6 @@ /* Internal Class Implementation */ /************************************************************************/ -constexpr auto MAX_SPANS = 256; constexpr auto PIXEL_BITS = 8; //must be at least 6 bits! constexpr auto ONE_PIXEL = (1L << PIXEL_BITS); @@ -240,10 +239,6 @@ struct RleWorker SwOutline* outline; - SwSpan spans[MAX_SPANS]; - int spansCnt; - int ySpan; - int bandSize; int bandShoot; @@ -301,26 +296,6 @@ static inline SwCoord HYPOT(SwPoint pt) return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3))); } -static void _genSpan(SwRleData* rle, const SwSpan* spans, uint32_t count) -{ - auto newSize = rle->size + count; - - /* allocate enough memory for new spans */ - /* alloc is required to prevent free and reallocation */ - /* when the rle needs to be regenerated because of attribute change. */ - if (rle->alloc < newSize) { - rle->alloc = (newSize * 2); - //OPTIMIZE: use mempool! - rle->spans = static_cast<SwSpan*>(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); - } - - //copy the new spans to the allocated memory - SwSpan* lastSpan = rle->spans + rle->size; - memcpy(lastSpan, spans, count * sizeof(SwSpan)); - - rle->size = newSize; -} - static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount) { @@ -344,25 +319,26 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor if (coverage > 255) coverage = 255; } + if (coverage == 0) return; + //span has ushort coordinates. check limit overflow if (x >= SHRT_MAX) { - TVGERR("SW_ENGINE", "X-coordiante overflow!"); - x = SHRT_MAX; + TVGERR("SW_ENGINE", "X-coordinate overflow!"); + return; } if (y >= SHRT_MAX) { - TVGERR("SW_ENGINE", "Y Coordiante overflow!"); - y = SHRT_MAX; + TVGERR("SW_ENGINE", "Y-coordinate overflow!"); + return; } - if (coverage > 0) { - if (!rw.antiAlias) coverage = 255; - auto count = rw.spansCnt; - auto span = rw.spans + count - 1; + auto rle = rw.rle; - //see whether we can add this span to the current list - if ((count > 0) && (rw.ySpan == y) && - (span->x + span->len == x) && (span->coverage == coverage)) { + if (!rw.antiAlias) coverage = 255; + //see whether we can add this span to the current list + if (rle->size > 0) { + auto span = rle->spans + rle->size - 1; + if ((span->coverage == coverage) && (span->y == y) && (span->x + span->len == x)) { //Clip x range SwCoord xOver = 0; if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); @@ -372,35 +348,35 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor span->len += (acount + xOver); return; } + } - if (count >= MAX_SPANS) { - _genSpan(rw.rle, rw.spans, count); - rw.spansCnt = 0; - rw.ySpan = 0; - span = rw.spans; - } else { - ++span; + //span pool is full, grow it. + if (rle->size >= rle->alloc) { + auto newSize = (rle->size > 0) ? (rle->size * 2) : 256; + if (rle->alloc < newSize) { + rle->alloc = newSize; + rle->spans = static_cast<SwSpan*>(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); } + } - //Clip x range - SwCoord xOver = 0; - if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); - if (x < rw.cellMin.x) { - xOver -= (rw.cellMin.x - x); - x = rw.cellMin.x; - } + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x); + if (x < rw.cellMin.x) { + xOver -= (rw.cellMin.x - x); + x = rw.cellMin.x; + } - //Nothing to draw - if (acount + xOver <= 0) return; + //Nothing to draw + if (acount + xOver <= 0) return; - //add a span to the current list - span->x = x; - span->y = y; - span->len = (acount + xOver); - span->coverage = coverage; - ++rw.spansCnt; - rw.ySpan = y; - } + //add a span to the current list + auto span = rle->spans + rle->size; + span->x = x; + span->y = y; + span->len = (acount + xOver); + span->coverage = coverage; + rle->size++; } @@ -408,9 +384,6 @@ static void _sweep(RleWorker& rw) { if (rw.cellsCnt == 0) return; - rw.spansCnt = 0; - rw.ySpan = 0; - for (int y = 0; y < rw.yCnt; ++y) { auto cover = 0; auto x = 0; @@ -427,8 +400,6 @@ static void _sweep(RleWorker& rw) if (cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), rw.cellXCnt - x); } - - if (rw.spansCnt > 0) _genSpan(rw.rle, rw.spans, rw.spansCnt); } @@ -926,7 +897,6 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren rw.cellMax = renderRegion.max; rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; - rw.ySpan = 0; rw.outline = const_cast<SwOutline*>(outline); rw.bandSize = rw.bufferSize / (sizeof(Cell) * 2); //bandSize: 256 rw.bandShoot = 0; @@ -1019,7 +989,6 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren error: free(rw.rle); - rw.rle = nullptr; return nullptr; } diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h index 9d216e2f30..81fd1b7d6f 100644 --- a/thirdparty/thorvg/src/renderer/tvgCanvas.h +++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h @@ -129,7 +129,7 @@ struct Canvas::Impl return Result::Success; } - return Result::InsufficientCondition; + return Result::Unknown; } Result viewport(int32_t x, int32_t y, int32_t w, int32_t h) diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index ff0f75dc0f..0ce6540f20 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -87,7 +87,7 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Ren if (ptsCnt != 4) return Result::InsufficientCondition; - if (rTransform) rTransform->update(); + if (rTransform && (cmpTarget->pImpl->renderFlag & RenderUpdateFlag::Transform)) rTransform->update(); //No rotation and no skewing, still can try out clipping the rect region. auto tryClip = false; @@ -181,13 +181,14 @@ Paint* Paint::Impl::duplicate() bool Paint::Impl::rotate(float degree) { if (rTransform) { + if (rTransform->overriding) return false; if (mathEqual(degree, rTransform->degree)) return true; } else { if (mathZero(degree)) return true; rTransform = new RenderTransform(); } rTransform->degree = degree; - if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + renderFlag |= RenderUpdateFlag::Transform; return true; } @@ -196,13 +197,14 @@ bool Paint::Impl::rotate(float degree) bool Paint::Impl::scale(float factor) { if (rTransform) { + if (rTransform->overriding) return false; if (mathEqual(factor, rTransform->scale)) return true; } else { if (mathEqual(factor, 1.0f)) return true; rTransform = new RenderTransform(); } rTransform->scale = factor; - if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + renderFlag |= RenderUpdateFlag::Transform; return true; } @@ -211,14 +213,15 @@ bool Paint::Impl::scale(float factor) bool Paint::Impl::translate(float x, float y) { if (rTransform) { - if (mathEqual(x, rTransform->x) && mathEqual(y, rTransform->y)) return true; + if (rTransform->overriding) return false; + if (mathEqual(x, rTransform->m.e13) && mathEqual(y, rTransform->m.e23)) return true; } else { if (mathZero(x) && mathZero(y)) return true; rTransform = new RenderTransform(); } - rTransform->x = x; - rTransform->y = y; - if (!rTransform->overriding) renderFlag |= RenderUpdateFlag::Transform; + rTransform->m.e13 = x; + rTransform->m.e23 = y; + renderFlag |= RenderUpdateFlag::Transform; return true; } @@ -263,10 +266,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT this->renderer = renderer; } - if (renderFlag & RenderUpdateFlag::Transform) { - if (!rTransform) return nullptr; - rTransform->update(); - } + if (renderFlag & RenderUpdateFlag::Transform) rTransform->update(); /* 1. Composition Pre Processing */ RenderData trd = nullptr; //composite target render data @@ -390,28 +390,28 @@ Paint :: ~Paint() Result Paint::rotate(float degree) noexcept { if (pImpl->rotate(degree)) return Result::Success; - return Result::FailedAllocation; + return Result::InsufficientCondition; } Result Paint::scale(float factor) noexcept { if (pImpl->scale(factor)) return Result::Success; - return Result::FailedAllocation; + return Result::InsufficientCondition; } Result Paint::translate(float x, float y) noexcept { if (pImpl->translate(x, y)) return Result::Success; - return Result::FailedAllocation; + return Result::InsufficientCondition; } Result Paint::transform(const Matrix& m) noexcept { if (pImpl->transform(m)) return Result::Success; - return Result::FailedAllocation; + return Result::InsufficientCondition; } diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.h b/thirdparty/thorvg/src/renderer/tvgPaint.h index c7eb68b198..bc07ab52ab 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.h +++ b/thirdparty/thorvg/src/renderer/tvgPaint.h @@ -87,7 +87,6 @@ namespace tvg if (!rTransform) { if (mathIdentity(&m)) return true; rTransform = new RenderTransform(); - if (!rTransform) return false; } rTransform->override(m); renderFlag |= RenderUpdateFlag::Transform; @@ -98,7 +97,7 @@ namespace tvg Matrix* transform() { if (rTransform) { - rTransform->update(); + if (renderFlag & RenderUpdateFlag::Transform) rTransform->update(); return &rTransform->m; } return nullptr; diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.h b/thirdparty/thorvg/src/renderer/tvgPicture.h index 91c16eb44e..bd7021218a 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.h +++ b/thirdparty/thorvg/src/renderer/tvgPicture.h @@ -92,18 +92,19 @@ struct Picture::Impl RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) { - auto flag = load(); + auto flag = static_cast<RenderUpdateFlag>(pFlag | load()); if (surface) { + if (flag == RenderUpdateFlag::None) return rd; auto transform = resizeTransform(pTransform); - rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag)); + rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, flag); } else if (paint) { if (resizing) { loader->resize(paint, w, h); resizing = false; } needComp = needComposition(opacity) ? true : false; - rd = paint->pImpl->update(renderer, pTransform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper); + rd = paint->pImpl->update(renderer, pTransform, clips, opacity, flag, clipper); } return rd; } @@ -200,6 +201,7 @@ struct Picture::Impl if (loader) { dup->loader = loader; ++dup->loader->sharing; + PP(ret)->renderFlag |= RenderUpdateFlag::Image; } dup->surface = surface; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.cpp b/thirdparty/thorvg/src/renderer/tvgRender.cpp index 9c779f7c00..82145b9aa4 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.cpp +++ b/thirdparty/thorvg/src/renderer/tvgRender.cpp @@ -32,6 +32,20 @@ /* External Class Implementation */ /************************************************************************/ +uint32_t RenderMethod::ref() +{ + ScopedLock lock(key); + return (++refCnt); +} + + +uint32_t RenderMethod::unref() +{ + ScopedLock lock(key); + return (--refCnt); +} + + void RenderTransform::override(const Matrix& m) { this->m = m; @@ -43,13 +57,18 @@ void RenderTransform::update() { if (overriding) return; - mathIdentity(&m); + m.e11 = 1.0f; + m.e12 = 0.0f; - mathScale(&m, scale, scale); + m.e21 = 0.0f; + m.e22 = 1.0f; - mathRotate(&m, degree); + m.e31 = 0.0f; + m.e32 = 0.0f; + m.e33 = 1.0f; - mathTranslate(&m, x, y); + mathScale(&m, scale, scale); + mathRotate(&m, degree); } diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 8f28d37dbc..ff55748033 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -103,7 +103,7 @@ struct RenderRegion void intersect(const RenderRegion& rhs); void add(const RenderRegion& rhs); - bool operator==(const RenderRegion& rhs) + bool operator==(const RenderRegion& rhs) const { if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true; return false; @@ -112,9 +112,7 @@ struct RenderRegion struct RenderTransform { - Matrix m; //3x3 Matrix Elements - float x = 0.0f; - float y = 0.0f; + Matrix m; float degree = 0.0f; //rotation degree float scale = 1.0f; //scale factor bool overriding = false; //user transform? @@ -122,7 +120,11 @@ struct RenderTransform void update(); void override(const Matrix& m); - RenderTransform() {} + RenderTransform() + { + m.e13 = m.e23 = 0.0f; + } + RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs); }; @@ -246,17 +248,8 @@ private: Key key; public: - uint32_t ref() - { - ScopedLock lock(key); - return (++refCnt); - } - - uint32_t unref() - { - ScopedLock lock(key); - return (--refCnt); - } + uint32_t ref(); + uint32_t unref(); virtual ~RenderMethod() {} virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0; diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index c45995a64d..ebc0b304ab 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -96,7 +96,9 @@ struct Shape::Impl } RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) - { + { + if (static_cast<RenderUpdateFlag>(pFlag | flag) == RenderUpdateFlag::None) return rd; + if ((needComp = needComposition(opacity))) { /* Overriding opacity value. If this scene is half-translucent, It must do intermeidate composition with that opacity value. */ @@ -296,6 +298,7 @@ struct Shape::Impl if (!rs.stroke) rs.stroke = new RenderStroke(); if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill); rs.stroke->fill = p; + rs.stroke->color[3] = 0; flag |= RenderUpdateFlag::Stroke; flag |= RenderUpdateFlag::GradientStroke; diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index d08158bfe7..1a68daf3c5 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.14.0 +VERSION=0.14.2 cd thirdparty/thorvg/ || true rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ |