diff --git a/.github/actions/godot-cpp-build/action.yml b/.github/actions/godot-cpp-build/action.yml new file mode 100644 index 000000000000..9596c5a8fd78 --- /dev/null +++ b/.github/actions/godot-cpp-build/action.yml @@ -0,0 +1,39 @@ +name: Build godot-cpp +description: Build godot-cpp with the provided options. + +env: + GODOT_CPP_BRANCH: 4.3 + +inputs: + bin: + description: Path to the Godot binary. + required: true + type: string + scons-flags: + description: Additional SCons flags. + type: string + scons-cache: + description: The SCons cache path. + default: ${{ github.workspace }}/.scons_cache/ + type: string + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + repository: godotengine/godot-cpp + ref: ${{ env.GODOT_CPP_BRANCH }} + path: godot-cpp + + - name: Extract API + shell: sh + run: ${{ inputs.bin }} --headless --dump-gdextension-interface --dump-extension-api + + - name: SCons Build + shell: sh + env: + SCONS_CACHE: ${{ inputs.scons-cache }} + run: scons --directory=./godot-cpp/test "gdextension_dir=${{ github.workspace }}" ${{ inputs.scons-flags }} diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml deleted file mode 100644 index a1b085604eb7..000000000000 --- a/.github/workflows/godot_cpp_test.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: 🪲 Godot CPP -on: - workflow_call: - -# Global Settings -env: - # Used for the cache key. Add version suffix to force clean build. - GODOT_BASE_BRANCH: master - # Used for the godot-cpp checkout. - GODOT_CPP_BRANCH: 4.3 - -jobs: - godot-cpp-tests: - runs-on: ubuntu-24.04 - name: Build and test Godot CPP - timeout-minutes: 30 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: | - .github - misc/utility/problem-matchers.json - - - name: Checkout godot-cpp - uses: actions/checkout@v4 - with: - submodules: recursive - repository: godotengine/godot-cpp - ref: ${{ env.GODOT_CPP_BRANCH }} - path: godot-cpp - - - name: Setup Python and SCons - uses: ./.github/actions/godot-deps - - - name: Download GDExtension interface and API dump - uses: ./.github/actions/download-artifact - with: - name: godot-api-dump - path: ./godot-cpp/gdextension - - # TODO: Enable caching when godot-cpp has proper cache limiting. - - # - name: Restore Godot build cache - # uses: ./.github/actions/godot-cache-restore - # with: - # cache-name: godot-cpp - # continue-on-error: true - - - name: Build godot-cpp test extension - run: scons --directory=./godot-cpp/test target=template_debug dev_build=yes verbose=yes - - # - name: Save Godot build cache - # uses: ./.github/actions/godot-cache-save - # with: - # cache-name: godot-cpp - # continue-on-error: true diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 4d0a215c8010..e978f9c8203d 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -33,7 +33,9 @@ jobs: proj-conv: true api-compat: true artifact: true - cache-limit: 1 + # Validate godot-cpp compatibility on one arbitrary editor build. + godot-cpp: true + cache-limit: 2 - name: Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold) cache-name: linux-editor-double-sanitizers @@ -44,8 +46,6 @@ jobs: build-mono: false tests: true proj-test: true - # Generate an API dump for godot-cpp tests. - api-dump: true # Skip 2GiB artifact speeding up action. artifact: false cache-limit: 7 @@ -158,6 +158,13 @@ jobs: tests: ${{ matrix.tests }} scons-cache-limit: ${{ matrix.cache-limit }} + - name: Compilation (godot-cpp) + uses: ./.github/actions/godot-cpp-build + if: matrix.godot-cpp + with: + bin: ${{ matrix.bin }} + scons-flags: target=template_debug dev_build=yes verbose=yes + - name: Save Godot build cache uses: ./.github/actions/godot-cache-save with: @@ -187,12 +194,6 @@ jobs: with: name: ${{ matrix.cache-name }} - - name: Dump Godot API - uses: ./.github/actions/godot-api-dump - if: matrix.api-dump - with: - bin: ${{ matrix.bin }} - - name: Unit tests if: matrix.tests run: | diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml index 937f0e895d79..c88d44a098d6 100644 --- a/.github/workflows/runner.yml +++ b/.github/workflows/runner.yml @@ -44,15 +44,3 @@ jobs: name: 🌐 Web needs: static-checks uses: ./.github/workflows/web_builds.yml - - # Third stage: Run auxiliary tests using build artifacts from previous jobs. - - # Can be turned off for PRs that intentionally break compat with godot-cpp, - # until both the upstream PR and the matching godot-cpp changes are merged. - godot-cpp-test: - name: 🪲 Godot CPP - # This can be changed to depend on another platform, if we decide to use it for - # godot-cpp instead. Make sure to move the .github/actions/godot-api-dump step - # appropriately. - needs: linux-build - uses: ./.github/workflows/godot_cpp_test.yml diff --git a/SConstruct b/SConstruct index 94bef048a072..8f8f329f233d 100644 --- a/SConstruct +++ b/SConstruct @@ -107,22 +107,11 @@ for x in sorted(glob.glob("platform/*")): sys.path.remove(tmppath) sys.modules.pop("detect") -custom_tools = ["default"] - -platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False)) - -if platform_arg == "android": - custom_tools = ["clang", "clang++", "as", "ar", "link"] -elif platform_arg == "web": - # Use generic POSIX build toolchain for Emscripten. - custom_tools = ["cc", "c++", "ar", "link", "textfile", "zip"] -elif os.name == "nt" and methods.get_cmdline_bool("use_mingw", False): - custom_tools = ["mingw"] - # We let SCons build its default ENV as it includes OS-specific things which we don't -# want to have to pull in manually. +# want to have to pull in manually. However we enforce no "tools", which we register +# further down after parsing our platform-specific configuration. # Then we prepend PATH to make it take precedence, while preserving SCons' own entries. -env = Environment(tools=custom_tools) +env = Environment(tools=[]) env.PrependENVPath("PATH", os.getenv("PATH")) env.PrependENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) if "TERM" in os.environ: # Used for colored output. @@ -168,11 +157,7 @@ if profile: opts = Variables(customs, ARGUMENTS) # Target build options -if env.scons_version >= (4, 3): - opts.Add(["platform", "p"], "Target platform (%s)" % "|".join(platform_list), "") -else: - opts.Add("platform", "Target platform (%s)" % "|".join(platform_list), "") - opts.Add("p", "Alias for 'platform'", "") +opts.Add((["platform", "p"], "Target platform (%s)" % "|".join(platform_list), "")) opts.Add(EnumVariable("target", "Compilation target", "editor", ("editor", "template_release", "template_debug"))) opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectures, architecture_aliases)) opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) @@ -312,10 +297,7 @@ if env["import_env_vars"]: # Platform selection: validate input, and add options. -if env.scons_version < (4, 3) and not env["platform"]: - env["platform"] = env["p"] - -if env["platform"] == "": +if not env["platform"]: # Missing `platform` argument, try to detect platform automatically if ( sys.platform.startswith("linux") @@ -330,7 +312,7 @@ if env["platform"] == "": elif sys.platform == "win32": env["platform"] = "windows" - if env["platform"] != "": + if env["platform"]: print(f'Automatically detected platform: {env["platform"]}') # Deprecated aliases kept for compatibility. @@ -352,7 +334,7 @@ if env["platform"] not in platform_list: if env["platform"] == "list": print(text) - elif env["platform"] == "": + elif not env["platform"]: print_error("Could not detect platform automatically.\n" + text) else: print_error(f'Invalid target platform "{env["platform"]}".\n' + text) @@ -434,6 +416,23 @@ env.modules_detected = modules_detected opts.Update(env, {**ARGUMENTS, **env.Dictionary()}) Help(opts.GenerateHelpText(env)) + +# FIXME: Tool assignment happening at this stage is a direct consequence of getting the platform logic AFTER the SCons +# environment was already been constructed. Fixing this would require a broader refactor where all options are setup +# ahead of time with native validator/converter functions. +tmppath = "./platform/" + env["platform"] +sys.path.insert(0, tmppath) +import detect + +custom_tools = ["default"] +try: # Platform custom tools are optional + custom_tools = detect.get_tools(env) +except AttributeError: + pass +for tool in custom_tools: + env.Tool(tool) + + # add default include paths env.Prepend(CPPPATH=["#"]) @@ -515,10 +514,6 @@ if not env["deprecated"]: if env["precision"] == "double": env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) -tmppath = "./platform/" + env["platform"] -sys.path.insert(0, tmppath) -import detect - # Default num_jobs to local cpu count if not user specified. # SCons has a peculiarity where user-specified options won't be overridden # by SetOption, so we can rely on this to know if we should use our default. @@ -587,7 +582,7 @@ if env["dev_mode"]: if env["production"]: env["use_static_cpp"] = methods.get_cmdline_bool("use_static_cpp", True) env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", False) - if platform_arg == "android": + if env["platform"] == "android": env["swappy"] = methods.get_cmdline_bool("swappy", True) # LTO "auto" means we handle the preferred option in each platform detect.py. env["lto"] = ARGUMENTS.get("lto", "auto") diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 6974d98234cc..acd589783ece 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -113,12 +113,12 @@ PackedStringArray ResourceLoader::get_dependencies(const String &p_path) { } bool ResourceLoader::has_cached(const String &p_path) { - String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + String local_path = ::ResourceLoader::_validate_local_path(p_path); return ResourceCache::has(local_path); } Ref ResourceLoader::get_cached_ref(const String &p_path) { - String local_path = ProjectSettings::get_singleton()->localize_path(p_path); + String local_path = ::ResourceLoader::_validate_local_path(p_path); return ResourceCache::get_ref(local_path); } diff --git a/core/input/input.cpp b/core/input/input.cpp index 4413c426cf09..b705968b87ed 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -77,6 +77,10 @@ Input *Input::singleton = nullptr; void (*Input::set_mouse_mode_func)(Input::MouseMode) = nullptr; Input::MouseMode (*Input::get_mouse_mode_func)() = nullptr; +void (*Input::set_mouse_mode_override_func)(Input::MouseMode) = nullptr; +Input::MouseMode (*Input::get_mouse_mode_override_func)() = nullptr; +void (*Input::set_mouse_mode_override_enabled_func)(bool) = nullptr; +bool (*Input::is_mouse_mode_override_enabled_func)() = nullptr; void (*Input::warp_mouse_func)(const Vector2 &p_position) = nullptr; Input::CursorShape (*Input::get_current_cursor_shape_func)() = nullptr; void (*Input::set_custom_mouse_cursor_func)(const Ref &, Input::CursorShape, const Vector2 &) = nullptr; @@ -86,51 +90,29 @@ Input *Input::get_singleton() { } void Input::set_mouse_mode(MouseMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 5); - - if (p_mode == mouse_mode) { - return; - } - - // Allow to be set even if overridden, to see if the platform allows the mode. + ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX); set_mouse_mode_func(p_mode); - mouse_mode = get_mouse_mode_func(); - - if (mouse_mode_override_enabled) { - set_mouse_mode_func(mouse_mode_override); - } } Input::MouseMode Input::get_mouse_mode() const { - return mouse_mode; + return get_mouse_mode_func(); } -void Input::set_mouse_mode_override_enabled(bool p_enabled) { - if (p_enabled == mouse_mode_override_enabled) { - return; - } - - mouse_mode_override_enabled = p_enabled; - - if (p_enabled) { - set_mouse_mode_func(mouse_mode_override); - mouse_mode_override = get_mouse_mode_func(); - } else { - set_mouse_mode_func(mouse_mode); - } +void Input::set_mouse_mode_override(MouseMode p_mode) { + ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX); + set_mouse_mode_override_func(p_mode); } -void Input::set_mouse_mode_override(MouseMode p_mode) { - ERR_FAIL_INDEX((int)p_mode, 5); +Input::MouseMode Input::get_mouse_mode_override() const { + return get_mouse_mode_override_func(); +} - if (p_mode == mouse_mode_override) { - return; - } +void Input::set_mouse_mode_override_enabled(bool p_override_enabled) { + set_mouse_mode_override_enabled_func(p_override_enabled); +} - if (mouse_mode_override_enabled) { - set_mouse_mode_func(p_mode); - mouse_mode_override = get_mouse_mode_func(); - } +bool Input::is_mouse_mode_override_enabled() { + return is_mouse_mode_override_enabled_func(); } void Input::_bind_methods() { @@ -199,6 +181,7 @@ void Input::_bind_methods() { BIND_ENUM_CONSTANT(MOUSE_MODE_CAPTURED); BIND_ENUM_CONSTANT(MOUSE_MODE_CONFINED); BIND_ENUM_CONSTANT(MOUSE_MODE_CONFINED_HIDDEN); + BIND_ENUM_CONSTANT(MOUSE_MODE_MAX); BIND_ENUM_CONSTANT(CURSOR_ARROW); BIND_ENUM_CONSTANT(CURSOR_IBEAM); diff --git a/core/input/input.h b/core/input/input.h index 6893c4b997b4..bbe185de3cbc 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -47,12 +47,14 @@ class Input : public Object { static constexpr uint64_t MAX_EVENT = 32; public: + // Keep synced with "DisplayServer::MouseMode" enum. enum MouseMode { MOUSE_MODE_VISIBLE, MOUSE_MODE_HIDDEN, MOUSE_MODE_CAPTURED, MOUSE_MODE_CONFINED, MOUSE_MODE_CONFINED_HIDDEN, + MOUSE_MODE_MAX, }; #undef CursorShape @@ -105,10 +107,6 @@ class Input : public Object { bool legacy_just_pressed_behavior = false; bool disable_input = false; - MouseMode mouse_mode = MOUSE_MODE_VISIBLE; - bool mouse_mode_override_enabled = false; - MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE; - struct ActionState { uint64_t pressed_physics_frame = UINT64_MAX; uint64_t pressed_process_frame = UINT64_MAX; @@ -268,6 +266,10 @@ class Input : public Object { static void (*set_mouse_mode_func)(MouseMode); static MouseMode (*get_mouse_mode_func)(); + static void (*set_mouse_mode_override_func)(MouseMode); + static MouseMode (*get_mouse_mode_override_func)(); + static void (*set_mouse_mode_override_enabled_func)(bool); + static bool (*is_mouse_mode_override_enabled_func)(); static void (*warp_mouse_func)(const Vector2 &p_position); static CursorShape (*get_current_cursor_shape_func)(); @@ -286,8 +288,10 @@ class Input : public Object { public: void set_mouse_mode(MouseMode p_mode); MouseMode get_mouse_mode() const; - void set_mouse_mode_override_enabled(bool p_enabled); void set_mouse_mode_override(MouseMode p_mode); + MouseMode get_mouse_mode_override() const; + void set_mouse_mode_override_enabled(bool p_override_enabled); + bool is_mouse_mode_override_enabled(); #ifdef TOOLS_ENABLED void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const override; diff --git a/core/input/input_builders.py b/core/input/input_builders.py index 3685e726b468..ba9abcfc6c11 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -45,6 +45,7 @@ def make_default_controller_mappings(target, source, env): "Mac OS X": "#ifdef MACOS_ENABLED", "Android": "#ifdef ANDROID_ENABLED", "iOS": "#ifdef IOS_ENABLED", + "tvOS": "#ifdef TVOS_ENABLED", "Web": "#ifdef WEB_ENABLED", } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 8f873d859b65..7cc91ef4318b 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -495,7 +495,7 @@ void ResourceLoader::_run_load_task(void *p_userdata) { curr_load_task = curr_load_task_backup; } -static String _validate_local_path(const String &p_path) { +String ResourceLoader::_validate_local_path(const String &p_path) { ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path); if (uid != ResourceUID::INVALID_ID) { return ResourceUID::get_singleton()->get_id_path(uid); diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index f933e88b23bf..56052b6a6fc4 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -36,6 +36,10 @@ #include "core/object/worker_thread_pool.h" #include "core/os/thread.h" +namespace core_bind { +class ResourceLoader; +} + class ConditionVariable; template @@ -101,6 +105,7 @@ typedef void (*ResourceLoadedCallback)(Ref p_resource, const String &p class ResourceLoader { friend class LoadToken; + friend class core_bind::ResourceLoader; enum { MAX_LOADERS = 64 @@ -217,6 +222,8 @@ class ResourceLoader { static bool _ensure_load_progress(); + static String _validate_local_path(const String &p_path); + public: static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 605035c21438..58a4ec5b74da 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -141,16 +141,24 @@ struct [[nodiscard]] Quaternion { } Quaternion(const Vector3 &p_v0, const Vector3 &p_v1) { // Shortest arc. - Vector3 c = p_v0.cross(p_v1); - - if (c.is_zero_approx()) { - Vector3 axis = p_v0.get_any_perpendicular(); +#ifdef MATH_CHECKS + ERR_FAIL_COND_MSG(p_v0.is_zero_approx() || p_v1.is_zero_approx(), "The vectors must not be zero."); +#endif + constexpr real_t ALMOST_ONE = 1.0f - (real_t)CMP_EPSILON; + Vector3 n0 = p_v0.normalized(); + Vector3 n1 = p_v1.normalized(); + real_t d = n0.dot(n1); + if (abs(d) > ALMOST_ONE) { + if (d >= 0) { + return; // Vectors are same. + } + Vector3 axis = n0.get_any_perpendicular(); x = axis.x; y = axis.y; z = axis.z; w = 0; } else { - real_t d = p_v0.dot(p_v1); + Vector3 c = n0.cross(n1); real_t s = Math::sqrt((1.0f + d) * 2.0f); real_t rs = 1.0f / s; diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 325477692a6d..13775ecdde07 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -154,7 +154,7 @@ def generate_version(argcount, const=False, returns=False, required=False, compa callptrargsptr += ", " argtext += f"m_type{i + 1}" callargtext += f"m_type{i + 1} arg{i + 1}" - callsiargs += f"_to_variant(arg{i + 1})" + callsiargs += f"arg{i + 1}" callsiargptrs += f"&vargs[{i}]" callptrargs += ( f"PtrToArg::EncodeT argval{i + 1} = (PtrToArg::EncodeT)arg{i + 1};\\\n" @@ -240,37 +240,6 @@ def run(target, source, env): #define _GDVIRTUAL_GET_DEPRECATED(m_name, m_name_sn, m_compat) #endif -// MSVC WORKAROUND START -// FIXME The below helper functions are needed to work around an MSVC bug. -// They should be removed (by modifying core/object/make_virtuals.py) once the bug ceases to be triggered. -// The bug is triggered by the following code: -// `Variant(arg)` -// Through the introduction of the move constructor, MSVC forgets that `operator Variant()` -// is also a valid way to resolve this call. So for some argument types, it fails the call because -// it cannot convert to `Variant`. -// The function `_to_variant` helps the compiler select `.operator Variant()` for appropriate arguments using SFINAE. - -template -struct has_variant_operator : std::false_type {}; - -template -struct has_variant_operator().operator Variant())>> : std::true_type {}; - -// Function that is enabled if T has `.operator Variant()`. -template -_ALWAYS_INLINE_ typename std::enable_if::value, Variant>::type -_to_variant(T&& t) { - return std::forward(t).operator Variant(); -} - -// Function that is enabled if T does not have `.operator Variant()`. -template -_ALWAYS_INLINE_ typename std::enable_if::value, Variant>::type -_to_variant(T&& t) { - return Variant(std::forward(t)); -} -// MSVC WORKAROUND END - """ for i in range(max_versions + 1): diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index e52e7274006a..d573b9fe6e48 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -140,10 +140,10 @@ Each particle's initial color. If [member texture] is defined, it will be multiplied by this color. - Each particle's initial color will vary along this [GradientTexture1D] (multiplied with [member color]). + Each particle's initial color will vary along this [Gradient] (multiplied with [member color]). - Each particle's color will vary along this [Gradient] (multiplied with [member color]). + Each particle's color will vary along this [Gradient] over its lifetime (multiplied with [member color]). Damping will vary along this [Curve]. Should be a unit [Curve]. @@ -241,6 +241,7 @@ Align Y axis of particle with the direction of its velocity. + Particle system starts as if it had already run for this many seconds. diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index cf80fecca69c..170f684638b8 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -146,11 +146,11 @@ [b]Note:[/b] [member color] multiplies the particle mesh's vertex colors. To have a visible effect on a [BaseMaterial3D], [member BaseMaterial3D.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color] will have no visible effect. - Each particle's initial color will vary along this [GradientTexture1D] (multiplied with [member color]). + Each particle's initial color will vary along this [Gradient] (multiplied with [member color]). [b]Note:[/b] [member color_initial_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [BaseMaterial3D], [member BaseMaterial3D.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_initial_ramp] will have no visible effect. - Each particle's color will vary along this [GradientTexture1D] over its lifetime (multiplied with [member color]). + Each particle's color will vary along this [Gradient] over its lifetime (multiplied with [member color]). [b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [BaseMaterial3D], [member BaseMaterial3D.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect. diff --git a/doc/classes/CollisionPolygon3D.xml b/doc/classes/CollisionPolygon3D.xml index 4f5866c34846..5758e1799671 100644 --- a/doc/classes/CollisionPolygon3D.xml +++ b/doc/classes/CollisionPolygon3D.xml @@ -10,6 +10,13 @@ + + The collision shape color that is displayed in the editor, or in the running project if [b]Debug > Visible Collision Shapes[/b] is checked at the top of the editor. + [b]Note:[/b] The default value is [member ProjectSettings.debug/shapes/collision/shape_color]. The [code]Color(0, 0, 0, 0)[/code] value documented here is a placeholder, and not the actual default debug color. + + + If [code]true[/code], when the shape is displayed, it will show a solid fill color in addition to its wireframe. + Length that the resulting collision extends in either direction perpendicular to its 2D polygon. diff --git a/doc/classes/Cubemap.xml b/doc/classes/Cubemap.xml index 8a850920f74b..c55adcffe33f 100644 --- a/doc/classes/Cubemap.xml +++ b/doc/classes/Cubemap.xml @@ -7,10 +7,10 @@ A cubemap is made of 6 textures organized in layers. They are typically used for faking reflections in 3D rendering (see [ReflectionProbe]). It can be used to make an object look as if it's reflecting its surroundings. This usually delivers much better performance than other reflection methods. This resource is typically used as a uniform in custom shaders. Few core Godot methods make use of [Cubemap] resources. To create such a texture file yourself, reimport your image files using the Godot Editor import presets. The expected image order is X+, X-, Y+, Y-, Z+, Z- (in Godot's coordinate system, so Y+ is "up" and Z- is "forward"). You can use one of the following templates as a base: - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_2x3.webp]2×3 cubemap template (default layout option)[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_3x2.webp]3×2 cubemap template[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_1x6.webp]1×6 cubemap template[/url] - - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/cubemap_template_6x1.webp]6×1 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_2x3.webp]2×3 cubemap template (default layout option)[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_3x2.webp]3×2 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_1x6.webp]1×6 cubemap template[/url] + - [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/tutorials/assets_pipeline/img/cubemap_template_6x1.webp]6×1 cubemap template[/url] [b]Note:[/b] Godot doesn't support using cubemaps in a [PanoramaSkyMaterial]. To use a cubemap as a skybox, convert the default [PanoramaSkyMaterial] to a [ShaderMaterial] using the [b]Convert to ShaderMaterial[/b] resource dropdown option, then replace its code with the following: [codeblock lang=text] shader_type sky; diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 8c9855fa9180..6089f85bc774 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1968,6 +1968,9 @@ Confines the mouse cursor to the game window, and make it hidden. + + Max value of the [enum MouseMode]. + Represents the screen containing the mouse pointer. [b]Note:[/b] On Linux (Wayland), this constant always represents the screen at index [code]0[/code]. diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml index 0bbd55f00757..644cbe5b5817 100644 --- a/doc/classes/EditorInspector.xml +++ b/doc/classes/EditorInspector.xml @@ -51,7 +51,6 @@ - diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index fcabcf14600f..3900a81502c1 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -1136,6 +1136,9 @@ - [b]Launch in PiP mode[/b] will launch the Play window directly in picture-in-picture (PiP) mode if PiP mode is supported and enabled. When maximized, the Play window will occupy the same window as the Editor. [b]Note:[/b] Only available in the Android editor. + + Overrides game embedding setting for all newly opened projects. If enabled, game embedding settings are not saved. + Specifies the picture-in-picture (PiP) mode for the Play window. - [b]Disabled:[/b] PiP is disabled for the Play window. diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml index 921fb0b71464..aced51b906c7 100644 --- a/doc/classes/FileDialog.xml +++ b/doc/classes/FileDialog.xml @@ -151,6 +151,7 @@ If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File"). + The number of additional [OptionButton]s and [CheckBox]es in the dialog. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 2e12015e5f98..e0d8be915778 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -464,6 +464,9 @@ Confines the mouse cursor to the game window, and make it hidden. + + Max value of the [enum MouseMode]. + Arrow cursor. Standard, default pointing cursor. diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index dd0d6ed4117e..c6cdacb6ec25 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -277,7 +277,7 @@ If [code]false[/code], existing text cannot be modified and new text cannot be added. - If [code]false[/code], "Emoji and Symbols" menu is enabled. + If [code]true[/code], "Emoji and Symbols" menu is enabled. If [code]true[/code], the [LineEdit] width will increase to stay longer than the [member text]. It will [b]not[/b] compress if the [member text] is shortened. diff --git a/doc/classes/PhysicsDirectSpaceState2D.xml b/doc/classes/PhysicsDirectSpaceState2D.xml index 94e9096334dd..059073a936f7 100644 --- a/doc/classes/PhysicsDirectSpaceState2D.xml +++ b/doc/classes/PhysicsDirectSpaceState2D.xml @@ -37,7 +37,7 @@ [b]Note:[/b] This method does not take into account the [code]motion[/code] property of the object. The returned object is a dictionary containing the following fields: [code]collider_id[/code]: The colliding object's ID. [code]linear_velocity[/code]: The colliding object's velocity [Vector2]. If the object is an [Area2D], the result is [code](0, 0)[/code]. - [code]normal[/code]: The object's surface normal at the intersection point. + [code]normal[/code]: The collision normal of the query shape at the intersection point, pointing away from the intersecting object. [code]point[/code]: The intersection point. [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. diff --git a/doc/classes/PhysicsDirectSpaceState3D.xml b/doc/classes/PhysicsDirectSpaceState3D.xml index b00dc4236d42..2f09b4f92810 100644 --- a/doc/classes/PhysicsDirectSpaceState3D.xml +++ b/doc/classes/PhysicsDirectSpaceState3D.xml @@ -37,7 +37,7 @@ Checks the intersections of a shape, given through a [PhysicsShapeQueryParameters3D] object, against the space. If it collides with more than one shape, the nearest one is selected. The returned object is a dictionary containing the following fields: [code]collider_id[/code]: The colliding object's ID. [code]linear_velocity[/code]: The colliding object's velocity [Vector3]. If the object is an [Area3D], the result is [code](0, 0, 0)[/code]. - [code]normal[/code]: The object's surface normal at the intersection point. + [code]normal[/code]: The collision normal of the query shape at the intersection point, pointing away from the intersecting object. [code]point[/code]: The intersection point. [code]rid[/code]: The intersecting object's [RID]. [code]shape[/code]: The shape index of the colliding shape. diff --git a/doc/classes/SubViewport.xml b/doc/classes/SubViewport.xml index 605cf949c10a..9d41a5e36d63 100644 --- a/doc/classes/SubViewport.xml +++ b/doc/classes/SubViewport.xml @@ -6,6 +6,7 @@ [SubViewport] Isolates a rectangular region of a scene to be displayed independently. This can be used, for example, to display UI in 3D space. [b]Note:[/b] [SubViewport] is a [Viewport] that isn't a [Window], i.e. it doesn't draw anything by itself. To display anything, [SubViewport] must have a non-zero size and be either put inside a [SubViewportContainer] or assigned to a [ViewportTexture]. + [b]Note:[/b] [InputEvent]s are not passed to a standalone [SubViewport] by default. To ensure [InputEvent] propagation, a [SubViewport] can be placed inside of a [SubViewportContainer]. $DOCS_URL/tutorials/rendering/viewports.html diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 89e2a2017af1..3b6f81aebc62 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1300,7 +1300,7 @@ If [code]false[/code], existing text cannot be modified and new text cannot be added. - If [code]false[/code], "Emoji and Symbols" menu is enabled. + If [code]true[/code], "Emoji and Symbols" menu is enabled. If [code]true[/code], copying or cutting without a selection is performed on all lines with a caret. Otherwise, copy and cut require a selection. diff --git a/drivers/SCsub b/drivers/SCsub index 153298c99e9a..df42bcfd8bf9 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -14,7 +14,7 @@ SConscript("windows/SCsub") # Sounds drivers SConscript("alsa/SCsub") -if env["platform"] == "ios" or env["platform"] == "macos": +if env["platform"] == "ios" or env["platform"] == "tvos" or env["platform"] == "macos": SConscript("coreaudio/SCsub") SConscript("pulseaudio/SCsub") if env["platform"] == "windows": @@ -27,7 +27,7 @@ if env["xaudio2"]: Exit(255) SConscript("xaudio2/SCsub") # Shared Apple platform drivers -if env["platform"] in ["macos", "ios"]: +if env["platform"] in ["macos", "ios", "tvos"]: SConscript("apple/SCsub") # Midi drivers SConscript("alsamidi/SCsub") diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 55a6e4826050..8cbbda249b00 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -568,6 +568,8 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ // Clear out state used in 2D pass reset_canvas(); + state.current_batch_index = 0; + state.canvas_instance_batches.clear(); state.current_data_buffer_index = (state.current_data_buffer_index + 1) % state.canvas_instance_data_buffers.size(); state.current_instance_buffer_index = 0; } @@ -662,8 +664,6 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou if (index == 0) { // Nothing to render, just return. - state.current_batch_index = 0; - state.canvas_instance_batches.clear(); return; } @@ -807,8 +807,6 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou } glDisable(GL_SCISSOR_TEST); - state.current_batch_index = 0; - state.canvas_instance_batches.clear(); state.last_item_index += index; } diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 8b389f733db0..f9cd3a41b6e8 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -75,9 +75,9 @@ #endif #endif -#if !defined(IOS_ENABLED) && !defined(WEB_ENABLED) +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) && !defined(WEB_ENABLED) // We include EGL below to get debug callback on GLES2 platforms, -// but EGL is not available on iOS or the web. +// but EGL is not available on iOS, tvOS or the web. #define CAN_DEBUG #endif diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 8e3a3d4b3edf..41fa943c896c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -3991,7 +3991,7 @@ TypedArray RasterizerSceneGLES3::bake_render_uv2(RID p_base, const TypedA // Consider rendering to RGBA8 encoded as RGBE, then manually convert to RGBAH on CPU. glBindTexture(GL_TEXTURE_2D, emission_tex); if (config->float_texture_supported) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, p_image_size.width, p_image_size.height, 0, GL_RGBA, GL_FLOAT, nullptr); GLES3::Utilities::get_singleton()->texture_allocated_data(emission_tex, p_image_size.width * p_image_size.height * 16, "Lightmap emission texture"); } else { // Fallback to RGBA8 on devices that don't support rendering to floating point textures. This will look bad, but we have no choice. @@ -4094,9 +4094,9 @@ TypedArray RasterizerSceneGLES3::bake_render_uv2(RID p_base, const TypedA { tex->tex_id = emission_tex; if (config->float_texture_supported) { - tex->format = Image::FORMAT_RGBAF; + tex->format = Image::FORMAT_RGBAH; tex->real_format = Image::FORMAT_RGBAH; - tex->gl_type_cache = GL_FLOAT; + tex->gl_type_cache = GL_HALF_FLOAT; } Ref img = GLES3::TextureStorage::get_singleton()->texture_2d_get(tex_rid); GLES3::Utilities::get_singleton()->texture_free_data(emission_tex); diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 1537035c07bf..4efc48661236 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -551,7 +551,7 @@ bool ShaderGLES3::_load_from_cache(Version *p_version) { #ifdef WEB_ENABLED // not supported in webgl return false; #else -#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) +#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (RasterizerGLES3::is_gles_over_gl() && (glProgramBinary == nullptr)) { // ARB_get_program_binary extension not available. return false; } @@ -638,7 +638,7 @@ void ShaderGLES3::_save_to_cache(Version *p_version) { return; #else ERR_FAIL_COND(!shader_cache_dir_valid); -#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) +#if !defined(ANDROID_ENABLED) && !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (RasterizerGLES3::is_gles_over_gl() && (glGetProgramBinary == nullptr)) { // ARB_get_program_binary extension not available. return; } diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl index 2591937a1da8..bdc28e292e2d 100644 --- a/drivers/gles3/shaders/particles.glsl +++ b/drivers/gles3/shaders/particles.glsl @@ -453,14 +453,14 @@ void main() { vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5; - float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r; + float y = texture(height_field_texture, uvw_pos.xz).r; if (y + EPSILON > uvw_pos.y) { //inside heightfield vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos2 = (vec3(uvw_pos.x + DELTA, texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos3 = (vec3(uvw_pos.x, texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; normal = normalize(cross(pos1 - pos2, pos1 - pos3)); float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y; diff --git a/drivers/gles3/shaders/tonemap_inc.glsl b/drivers/gles3/shaders/tonemap_inc.glsl index ce98a62c23a4..dd7df09c38a3 100644 --- a/drivers/gles3/shaders/tonemap_inc.glsl +++ b/drivers/gles3/shaders/tonemap_inc.glsl @@ -86,7 +86,7 @@ vec3 tonemap_aces(vec3 color, float p_white) { // Polynomial approximation of EaryChow's AgX sigmoid curve. // x must be within the range [0.0, 1.0] -vec3 agx_default_contrast_approx(vec3 x) { +vec3 agx_contrast_approx(vec3 x) { // Generated with Excel trendline // Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps // Additional padding values were added to give correct intersections at 0.0 and 1.0 @@ -96,25 +96,21 @@ vec3 agx_default_contrast_approx(vec3 x) { return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2; } -const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3( - vec3(0.6274, 0.0691, 0.0164), - vec3(0.3293, 0.9195, 0.0880), - vec3(0.0433, 0.0113, 0.8956)); - // This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender. // This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses. // Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py vec3 tonemap_agx(vec3 color) { - const mat3 agx_inset_matrix = mat3( - 0.856627153315983, 0.137318972929847, 0.11189821299995, - 0.0951212405381588, 0.761241990602591, 0.0767994186031903, - 0.0482516061458583, 0.101439036467562, 0.811302368396859); + // Combined linear sRGB to linear Rec 2020 and Blender AgX inset matrices: + const mat3 srgb_to_rec2020_agx_inset_matrix = mat3( + 0.54490813676363087053, 0.14044005884001287035, 0.088827411851915368603, + 0.37377945959812267119, 0.75410959864013760045, 0.17887712465043811023, + 0.081384976686407536266, 0.10543358536857773485, 0.73224999956948382528); // Combined inverse AgX outset matrix and linear Rec 2020 to linear sRGB matrices. const mat3 agx_outset_rec2020_to_srgb_matrix = mat3( - 1.9648846919172409596, -0.29937618452442253746, -0.16440106280678278299, - -0.85594737466675834968, 1.3263980951083531115, -0.23819967517076844919, - -0.10883731725048386702, -0.02702191058393112346, 1.4025007379775505276); + 1.9645509602733325934, -0.29932243390911083839, -0.16436833806080403409, + -0.85585845117807513559, 1.3264510741502356555, -0.23822464068860595117, + -0.10886710826831608324, -0.027084020983874825605, 1.402665347143271889); // LOG2_MIN = -10.0 // LOG2_MAX = +6.5 @@ -122,26 +118,32 @@ vec3 tonemap_agx(vec3 color) { const float min_ev = -12.4739311883324; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY) const float max_ev = 4.02606881166759; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY) - // Do AGX in rec2020 to match Blender. - color = LINEAR_SRGB_TO_LINEAR_REC2020 * color; - - // Preventing negative values is required for the AgX inset matrix to behave correctly. - // This could also be done before the Rec. 2020 transform, allowing the transform to - // be combined with the AgX inset matrix, but doing this causes a loss of color information - // that could be correctly interpreted within the Rec. 2020 color space. - color = max(color, vec3(0.0)); - - color = agx_inset_matrix * color; + // Large negative values in one channel and large positive values in other + // channels can result in a colour that appears darker and more saturated than + // desired after passing it through the inset matrix. For this reason, it is + // best to prevent negative input values. + // This is done before the Rec. 2020 transform to allow the Rec. 2020 + // transform to be combined with the AgX inset matrix. This results in a loss + // of color information that could be correctly interpreted within the + // Rec. 2020 color space as positive RGB values, but it is less common for Godot + // to provide this function with negative sRGB values and therefore not worth + // the performance cost of an additional matrix multiplication. + // A value of 2e-10 intentionally introduces insignificant error to prevent + // log2(0.0) after the inset matrix is applied; color will be >= 1e-10 after + // the matrix transform. + color = max(color, 2e-10); + + // Do AGX in rec2020 to match Blender and then apply inset matrix. + color = srgb_to_rec2020_agx_inset_matrix * color; // Log2 space encoding. - color = max(color, 1e-10); // Prevent log2(0.0). Possibly unnecessary. - // Must be clamped because agx_blender_default_contrast_approx may not work + // Must be clamped because agx_contrast_approx may not work // well with values outside of the range [0.0, 1.0] color = clamp(log2(color), min_ev, max_ev); color = (color - min_ev) / (max_ev - min_ev); // Apply sigmoid function approximation. - color = agx_default_contrast_approx(color); + color = agx_contrast_approx(color); // Convert back to linear before applying outset matrix. color = pow(color, vec3(2.4)); @@ -149,9 +151,9 @@ vec3 tonemap_agx(vec3 color) { // Apply outset to make the result more chroma-laden and then go back to linear sRGB. color = agx_outset_rec2020_to_srgb_matrix * color; - // Simply hard clip instead of Blender's complex lusRGB.compensate_low_side. - color = max(color, vec3(0.0)); - + // Blender's lusRGB.compensate_low_side is too complex for this shader, so + // simply return the color, even if it has negative components. These negative + // components may be useful for subsequent color adjustments. return color; } diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index fb45fdeaca7b..bd6b92db74a2 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -84,14 +84,16 @@ Config::Config() { if (RasterizerGLES3::is_gles_over_gl()) { float_texture_supported = true; + float_texture_linear_supported = true; etc2_supported = false; s3tc_supported = true; rgtc_supported = true; //RGTC - core since OpenGL version 3.0 srgb_framebuffer_supported = true; } else { float_texture_supported = extensions.has("GL_EXT_color_buffer_float"); + float_texture_linear_supported = extensions.has("GL_OES_texture_float_linear"); etc2_supported = true; -#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED) // Some Android devices report support for S3TC but we don't expect that and don't export the textures. // This could be fixed but so few devices support it that it doesn't seem useful (and makes bigger APKs). // For good measure we do the same hack for iOS, just in case. @@ -124,7 +126,7 @@ Config::Config() { #else msaa_supported = true; #endif -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) #ifdef WEB_ENABLED msaa_multiview_supported = extensions.has("OCULUS_multiview"); rt_msaa_multiview_supported = msaa_multiview_supported; diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index e4dfe6a35ee9..36fae084b0e6 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -72,6 +72,7 @@ class Config { HashSet extensions; bool float_texture_supported = false; + bool float_texture_linear_supported = false; bool s3tc_supported = false; bool rgtc_supported = false; bool bptc_supported = false; diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp index 7d899fad7789..fe5606ca64f5 100644 --- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp +++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp @@ -68,7 +68,7 @@ void RenderSceneBuffersGLES3::_rt_attach_textures(GLuint p_color, GLuint p_depth ERR_PRINT_ONCE("Multiview MSAA isn't supported on this platform."); #endif } else { -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, 0, p_view_count); glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, 0, p_view_count); #else @@ -247,7 +247,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { glGenFramebuffers(1, &internal3d.fbo); glBindFramebuffer(GL_FRAMEBUFFER, internal3d.fbo); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, internal3d.color, 0, 0, view_count); glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, internal3d.depth, 0, 0, view_count); @@ -318,7 +318,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() { glBindRenderbuffer(GL_RENDERBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); -#if !defined(IOS_ENABLED) && !defined(WEB_ENABLED) +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) && !defined(WEB_ENABLED) } else if (use_multiview && !config->rt_msaa_multiview_supported) { // Render to texture extensions not supported? fall back to MSAA textures through GL_EXT_multiview_texture_multisample. msaa3d.needs_resolve = true; @@ -493,7 +493,7 @@ void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_de GLES3::Utilities::get_singleton()->texture_allocated_data(backbuffer3d.color, internal_size.x * internal_size.y * view_count * color_format_size, "3D Back buffer color texture"); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, backbuffer3d.color, 0, 0, view_count); } else { @@ -521,7 +521,7 @@ void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_de GLES3::Utilities::get_singleton()->texture_allocated_data(backbuffer3d.depth, internal_size.x * internal_size.y * view_count * depth_format_size, "3D back buffer depth texture"); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, backbuffer3d.depth, 0, 0, view_count); } else { diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index e74747954f51..357bdb10b8d0 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -334,7 +334,9 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS: /* Texture API */ -static inline Error _get_gl_uncompressed_format(Image::Format p_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type) { +static inline Error _get_gl_uncompressed_format(const Ref &p_image, Image::Format p_format, Image::Format &r_real_format, GLenum &r_gl_format, GLenum &r_gl_internal_format, GLenum &r_gl_type) { + Config *config = Config::get_singleton(); + switch (p_format) { case Image::FORMAT_L8: { if (RasterizerGLES3::is_gles_over_gl()) { @@ -389,24 +391,68 @@ static inline Error _get_gl_uncompressed_format(Image::Format p_format, GLenum & r_gl_type = GL_UNSIGNED_SHORT_5_6_5; } break; case Image::FORMAT_RF: { - r_gl_internal_format = GL_R32F; - r_gl_format = GL_RED; - r_gl_type = GL_FLOAT; + if (config->float_texture_linear_supported) { + r_gl_internal_format = GL_R32F; + r_gl_format = GL_RED; + r_gl_type = GL_FLOAT; + } else { + ERR_PRINT("R32 float texture not supported, converting to R16."); + if (p_image.is_valid()) { + p_image->convert(Image::FORMAT_RH); + } + r_real_format = Image::FORMAT_RH; + r_gl_internal_format = GL_R16F; + r_gl_format = GL_RED; + r_gl_type = GL_HALF_FLOAT; + } } break; case Image::FORMAT_RGF: { - r_gl_internal_format = GL_RG32F; - r_gl_format = GL_RG; - r_gl_type = GL_FLOAT; + if (config->float_texture_linear_supported) { + r_gl_internal_format = GL_RG32F; + r_gl_format = GL_RG; + r_gl_type = GL_FLOAT; + } else { + ERR_PRINT("RG32 float texture not supported, converting to RG16."); + if (p_image.is_valid()) { + p_image->convert(Image::FORMAT_RGH); + } + r_real_format = Image::FORMAT_RGH; + r_gl_internal_format = GL_RG16F; + r_gl_format = GL_RG; + r_gl_type = GL_HALF_FLOAT; + } } break; case Image::FORMAT_RGBF: { - r_gl_internal_format = GL_RGB32F; - r_gl_format = GL_RGB; - r_gl_type = GL_FLOAT; + if (config->float_texture_linear_supported) { + r_gl_internal_format = GL_RGB32F; + r_gl_format = GL_RGB; + r_gl_type = GL_FLOAT; + } else { + ERR_PRINT("RGB32 float texture not supported, converting to RGB16."); + if (p_image.is_valid()) { + p_image->convert(Image::FORMAT_RGBH); + } + r_real_format = Image::FORMAT_RGBH; + r_gl_internal_format = GL_RGB16F; + r_gl_format = GL_RGB; + r_gl_type = GL_HALF_FLOAT; + } } break; case Image::FORMAT_RGBAF: { - r_gl_internal_format = GL_RGBA32F; - r_gl_format = GL_RGBA; - r_gl_type = GL_FLOAT; + if (config->float_texture_linear_supported) { + r_gl_internal_format = GL_RGBA32F; + r_gl_format = GL_RGBA; + r_gl_type = GL_FLOAT; + } else { + ERR_PRINT("RGBA32 float texture not supported, converting to RGBA16."); + if (p_image.is_valid()) { + p_image->convert(Image::FORMAT_RGBAH); + } + r_real_format = Image::FORMAT_RGBAH; + r_gl_internal_format = GL_RGBA16F; + r_gl_format = GL_RGBA; + r_gl_type = GL_HALF_FLOAT; + } } break; case Image::FORMAT_RH: { r_gl_internal_format = GL_R16F; @@ -449,7 +495,7 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I r_real_format = p_format; if (!Image::is_format_compressed(p_format)) { - Error err = _get_gl_uncompressed_format(p_format, r_gl_format, r_gl_internal_format, r_gl_type); + Error err = _get_gl_uncompressed_format(p_image, p_format, r_real_format, r_gl_format, r_gl_internal_format, r_gl_type); ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("The image format %d is not supported by the Compatibility renderer.", p_format)); return p_image; } @@ -694,7 +740,7 @@ Ref TextureStorage::_get_gl_image_and_format(const Ref &p_image, I image->convert(Image::FORMAT_RG8); } - Error err = _get_gl_uncompressed_format(image->get_format(), r_gl_format, r_gl_internal_format, r_gl_type); + Error err = _get_gl_uncompressed_format(image, image->get_format(), r_real_format, r_gl_format, r_gl_internal_format, r_gl_type); ERR_FAIL_COND_V_MSG(err != OK, Ref(), vformat("The image format %d is not supported by the Compatibility renderer.", image->get_format())); r_real_format = image->get_format(); @@ -1509,8 +1555,17 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref &p_image, GLenum internal_format; bool compressed = false; + bool needs_decompress = texture->resize_to_po2; + + // Support for RGTC-compressed Texture Arrays isn't mandated by GLES3/WebGL. + if (!RasterizerGLES3::is_gles_over_gl() && texture->target == GL_TEXTURE_2D_ARRAY) { + if (p_image->get_format() == Image::FORMAT_RGTC_R || p_image->get_format() == Image::FORMAT_RGTC_RG) { + needs_decompress = true; + } + } + Image::Format real_format; - Ref img = _get_gl_image_and_format(p_image, p_image->get_format(), real_format, format, internal_format, type, compressed, texture->resize_to_po2); + Ref img = _get_gl_image_and_format(p_image, p_image->get_format(), real_format, format, internal_format, type, compressed, needs_decompress); ERR_FAIL_COND(img.is_null()); if (texture->resize_to_po2) { if (p_image->is_compressed()) { @@ -2109,7 +2164,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { GLES3::Utilities::get_singleton()->texture_allocated_data(rt->color, rt->size.x * rt->size.y * rt->view_count * rt->color_format_size, "Render target color texture"); } -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count); } else { @@ -2142,7 +2197,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) { GLES3::Utilities::get_singleton()->texture_allocated_data(rt->depth, rt->size.x * rt->size.y * rt->view_count * 3, "Render target depth texture"); } -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count); } else { @@ -2283,7 +2338,7 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->backbuffer, 0, 0, rt->view_count); } else { @@ -2307,7 +2362,7 @@ void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_s glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#ifndef IOS_ENABLED +#if !defined(IOS_ENABLED) && !defined(TVOS_ENABLED) if (use_multiview) { glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->backbuffer_depth, 0, 0, rt->view_count); } else { diff --git a/drivers/metal/metal_device_properties.mm b/drivers/metal/metal_device_properties.mm index d4d6d6bcd328..d410e1a0c209 100644 --- a/drivers/metal/metal_device_properties.mm +++ b/drivers/metal/metal_device_properties.mm @@ -51,7 +51,9 @@ #import "metal_device_properties.h" #import +#if !defined(IOS_SIMULATOR) && !defined(TVOS_SIMULATOR) #import +#endif #import #import @@ -120,10 +122,12 @@ features.needs_arg_encoders = !([p_device supportsFamily:MTLGPUFamilyMetal3] && features.argument_buffers_tier == MTLArgumentBuffersTier2); } +#if !defined(IOS_SIMULATOR) && !defined(TVOS_SIMULATOR) if (@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)) { features.metal_fx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:p_device]; features.metal_fx_temporal = [MTLFXTemporalScalerDescriptor supportsDevice:p_device]; } +#endif MTLCompileOptions *opts = [MTLCompileOptions new]; features.mslVersionEnum = opts.languageVersion; // By default, Metal uses the most recent language version. @@ -166,7 +170,7 @@ case MTLLanguageVersion1_1: setMSLVersion(1, 1); break; -#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST +#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_TV case MTLLanguageVersion1_0: setMSLVersion(1, 0); break; @@ -311,10 +315,14 @@ limits.maxDrawIndexedIndexValue = std::numeric_limits::max() - 1; +#if defined(IOS_SIMULATOR) || defined(TVOS_SIMULATOR) + { +#else if (@available(macOS 14.0, iOS 17.0, tvOS 17.0, *)) { limits.temporalScalerInputContentMinScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMinScaleForDevice:p_device]; limits.temporalScalerInputContentMaxScale = (double)[MTLFXTemporalScalerDescriptor supportedInputContentMaxScaleForDevice:p_device]; } else { +#endif // Defaults taken from macOS 14+ limits.temporalScalerInputContentMinScale = 1.0; limits.temporalScalerInputContentMaxScale = 3.0; diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm index 767424c6a48b..285971643346 100644 --- a/drivers/metal/rendering_context_driver_metal.mm +++ b/drivers/metal/rendering_context_driver_metal.mm @@ -172,9 +172,13 @@ void present(MDCommandBuffer *p_cmd_buffer) override final { count--; front = (front + 1) % frame_buffers.size(); +#if defined(IOS_SIMULATOR) || defined(TVOS_SIMULATOR) + { +#else if (vsync_mode != DisplayServer::VSYNC_DISABLED) { [p_cmd_buffer->get_command_buffer() presentDrawable:drawable afterMinimumDuration:present_minimum_duration]; } else { +#endif [p_cmd_buffer->get_command_buffer() presentDrawable:drawable]; } } diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 2e32e99c2ebb..9b18b1525ac0 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -287,7 +287,11 @@ _FORCE_INLINE_ MTLSize mipmapLevelSizeFromSize(MTLSize p_size, NSUInteger p_leve // Usage. MTLResourceOptions options = 0; +#if TARGET_OS_TV + const bool supports_memoryless = (*device_properties).features.highestFamily >= MTLGPUFamilyApple2 && (*device_properties).features.highestFamily < MTLGPUFamilyMac2; +#else const bool supports_memoryless = (*device_properties).features.highestFamily >= MTLGPUFamilyApple2 && (*device_properties).features.highestFamily < MTLGPUFamilyMac1; +#endif if (supports_memoryless && p_format.usage_bits & TEXTURE_USAGE_TRANSIENT_BIT) { options = MTLResourceStorageModeMemoryless | MTLResourceHazardTrackingModeTracked; desc.storageMode = MTLStorageModeMemoryless; @@ -4158,7 +4162,7 @@ bool isArrayTexture(MTLTextureType p_type) { error_string += "- No support for image cube arrays.\n"; } -#if defined(IOS_ENABLED) +#if defined(IOS_ENABLED) || defined(TVOS_ENABLED) // iOS platform ports currently don't exit themselves when this method returns `ERR_CANT_CREATE`. OS::get_singleton()->alert(error_string + "\nClick OK to exit (black screen will be visible)."); #else diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp index 669e6c2aa951..7198f4dd0d08 100644 --- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp +++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp @@ -725,7 +725,8 @@ Error AudioDriverPulseAudio::init_input_device() { int input_buffer_frames = closest_power_of_2(input_latency * mix_rate / 1000); int input_buffer_size = input_buffer_frames * spec.channels; - pa_buffer_attr attr; + pa_buffer_attr attr = {}; + attr.maxlength = (uint32_t)-1; attr.fragsize = input_buffer_size * sizeof(int16_t); pa_rec_str = pa_stream_new(pa_ctx, "Record", &spec, &pa_rec_map); diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index a70be9326e8b..9101039dc8c0 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -606,6 +606,8 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List & // Don't compile this code at all to avoid undefined references. // Actual virtual call goes to OS_Web. ERR_FAIL_V(ret); +#elif defined(TVOS_ENABLED) + ERR_FAIL_V(ret); #else // Create pipes. int pipe_in[2] = { -1, -1 }; @@ -690,6 +692,8 @@ Error OS_Unix::execute(const String &p_path, const List &p_arguments, St // Don't compile this code at all to avoid undefined references. // Actual virtual call goes to OS_Web. ERR_FAIL_V(ERR_BUG); +#elif defined(TVOS_ENABLED) + ERR_FAIL_V(ERR_CANT_FORK); #else if (r_pipe) { String command = "\"" + p_path + "\""; @@ -764,6 +768,8 @@ Error OS_Unix::create_process(const String &p_path, const List &p_argume // Don't compile this code at all to avoid undefined references. // Actual virtual call goes to OS_Web. ERR_FAIL_V(ERR_BUG); +#elif defined(TVOS_ENABLED) + ERR_FAIL_V(ERR_CANT_FORK); #else pid_t pid = fork(); ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK); diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub index 6ea7cc9a3b92..c90bb1ce7aa6 100644 --- a/drivers/vulkan/SCsub +++ b/drivers/vulkan/SCsub @@ -16,7 +16,7 @@ if env["use_volk"]: if env["platform"] == "android": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_ANDROID_KHR"]) -elif env["platform"] == "ios": +elif env["platform"] == "iphone" or env["platform"] == "tvos": env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK", "VK_USE_PLATFORM_METAL_EXT"]) elif env["platform"] == "linuxbsd": if env["x11"]: diff --git a/drivers/vulkan/rendering_context_driver_vulkan.cpp b/drivers/vulkan/rendering_context_driver_vulkan.cpp index 3c1aa323f45e..7292fe1283fa 100644 --- a/drivers/vulkan/rendering_context_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_context_driver_vulkan.cpp @@ -436,7 +436,7 @@ Error RenderingContextDriverVulkan::_initialize_instance_extensions() { // This extension allows us to use the properties2 features to query additional device capabilities. _register_requested_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); -#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED)) +#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED)) _register_requested_instance_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, true); #endif @@ -699,7 +699,7 @@ Error RenderingContextDriverVulkan::_initialize_instance() { VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; -#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED)) +#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED)) instance_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 45c574914d65..c0ddf89af6d2 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -622,7 +622,7 @@ Error RenderingDeviceDriverVulkan::_check_device_features() { } error_string += "\nThis is usually a hardware limitation, so updating graphics drivers won't help in most cases."; -#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED) // Android/iOS platform ports currently don't exit themselves when this method returns `ERR_CANT_CREATE`. OS::get_singleton()->alert(error_string + "\nClick OK to exit (black screen will be visible)."); #else @@ -1530,10 +1530,12 @@ RDD::BufferID RenderingDeviceDriverVulkan::buffer_create(uint64_t p_size, BitFie if (is_src && !is_dst) { // Looks like a staging buffer: CPU maps, writes sequentially, then GPU copies to VRAM. alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } if (is_dst && !is_src) { // Looks like a readback buffer: GPU copies from VRAM, then CPU maps and reads. alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; } alloc_create_info.requiredFlags = (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); } break; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 4c934104d19b..ef5d878ca0aa 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -933,12 +933,14 @@ void CodeTextEditor::_text_editor_gui_input(const Ref &p_event) { } } +#ifndef ANDROID_ENABLED Ref magnify_gesture = p_event; if (magnify_gesture.is_valid()) { _zoom_to(zoom_factor * powf(magnify_gesture->get_factor(), 0.25f)); accept_event(); return; } +#endif Ref k = p_event; @@ -1909,7 +1911,9 @@ CodeTextEditor::CodeTextEditor() { zoom_button->set_flat(true); zoom_button->set_v_size_flags(SIZE_EXPAND | SIZE_SHRINK_CENTER); zoom_button->set_tooltip_text( - TTR("Zoom factor") + "\n" + vformat(TTR("%sMouse wheel, %s/%s: Finetune\n%s: Reset"), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL), ED_GET_SHORTCUT("script_editor/zoom_in")->get_as_text(), ED_GET_SHORTCUT("script_editor/zoom_out")->get_as_text(), ED_GET_SHORTCUT("script_editor/reset_zoom")->get_as_text())); + TTR("Zoom factor") + "\n" + + // TRANSLATORS: The placeholders are keyboard shortcuts. The first one is in the form of "Ctrl+"/"Cmd+". + vformat(TTR("%sMouse wheel, %s/%s: Finetune\n%s: Reset"), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL), ED_GET_SHORTCUT("script_editor/zoom_in")->get_as_text(), ED_GET_SHORTCUT("script_editor/zoom_out")->get_as_text(), ED_GET_SHORTCUT("script_editor/reset_zoom")->get_as_text())); zoom_button->set_text("100 %"); PopupMenu *zoom_menu = zoom_button->get_popup(); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 5b89cc8e13e4..1363fca3c2c9 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -826,6 +826,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread } } else if (p_msg == "evaluation_return") { expression_evaluator->add_value(p_data); + } else if (p_msg == "window:title") { + ERR_FAIL_COND(p_data.size() != 1); + emit_signal(SNAME("remote_window_title_changed"), p_data[0]); } else { int colon_index = p_msg.find_char(':'); ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received"); @@ -1784,6 +1787,7 @@ void ScriptEditorDebugger::_bind_methods() { ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); ADD_SIGNAL(MethodInfo("remote_tree_updated")); ADD_SIGNAL(MethodInfo("remote_tree_select_requested", PropertyInfo(Variant::NODE_PATH, "path"))); + ADD_SIGNAL(MethodInfo("remote_window_title_changed", PropertyInfo(Variant::STRING, "title"))); ADD_SIGNAL(MethodInfo("output", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::INT, "level"))); ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump"))); ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars"))); diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index ae685a824b27..9881087b3e39 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -1265,41 +1265,11 @@ void EditorAudioBuses::_load_layout() { } void EditorAudioBuses::_load_default_layout() { - String layout_path = GLOBAL_GET("audio/buses/default_bus_layout"); - - Ref state; - if (ResourceLoader::exists(layout_path)) { - state = ResourceLoader::load(layout_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); - } - if (state.is_null()) { - EditorNode::get_singleton()->show_warning(vformat(TTR("There is no '%s' file."), layout_path)); - return; - } - - edited_path = layout_path; - file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file()); - AudioServer::get_singleton()->set_bus_layout(state); - _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); - callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); + open_layout(GLOBAL_GET("audio/buses/default_bus_layout")); } void EditorAudioBuses::_file_dialog_callback(const String &p_string) { - if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_OPEN_FILE) { - Ref state = ResourceLoader::load(p_string, "", ResourceFormatLoader::CACHE_MODE_IGNORE); - if (state.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("Invalid file, not an audio bus layout.")); - return; - } - - edited_path = p_string; - file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); - AudioServer::get_singleton()->set_bus_layout(state); - _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); - callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); - - } else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) { + if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) { if (new_layout) { Ref empty_state; empty_state.instantiate(); @@ -1307,18 +1277,12 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { } Error err = ResourceSaver::save(AudioServer::get_singleton()->generate_bus_layout(), p_string); - if (err != OK) { EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s"), p_string)); return; } - - edited_path = p_string; - file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); - _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); - callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } + open_layout(p_string); } void EditorAudioBuses::_bind_methods() { @@ -1330,9 +1294,10 @@ EditorAudioBuses::EditorAudioBuses() { top_hb = memnew(HBoxContainer); add_child(top_hb); + edited_path = ResourceUID::ensure_path(GLOBAL_GET("audio/buses/default_bus_layout")); + file = memnew(Label); - String layout_path = GLOBAL_GET("audio/buses/default_bus_layout"); - file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file()); + file->set_text(vformat("%s %s", TTR("Layout:"), edited_path.get_file())); file->set_clip_text(true); file->set_h_size_flags(SIZE_EXPAND_FILL); top_hb->add_child(file); @@ -1386,8 +1351,6 @@ EditorAudioBuses::EditorAudioBuses() { set_v_size_flags(SIZE_EXPAND_FILL); - edited_path = GLOBAL_GET("audio/buses/default_bus_layout"); - file_dialog = memnew(EditorFileDialog); List ext; ResourceLoader::get_recognized_extensions_for_type("AudioBusLayout", &ext); @@ -1405,14 +1368,21 @@ EditorAudioBuses::EditorAudioBuses() { void EditorAudioBuses::open_layout(const String &p_path) { EditorNode::get_bottom_panel()->make_item_visible(this); - Ref state = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + const String path = ResourceUID::ensure_path(p_path); + + if (!ResourceLoader::exists(path)) { + EditorNode::get_singleton()->show_warning(vformat(TTR(R"(Can't open audio bus layout: "%s" doesn't exist.)"), path)); + return; + } + + Ref state = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); if (state.is_null()) { - EditorNode::get_singleton()->show_warning(TTR("Invalid file, not an audio bus layout.")); + EditorNode::get_singleton()->show_warning(vformat(TTR(R"(Can't open audio bus layout: "%s" is not a valid audio bus layout.)"), path)); return; } - edited_path = p_path; - file->set_text(p_path.get_file()); + edited_path = path; + file->set_text(vformat("%s %s", TTR("Layout:"), path.get_file())); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index e5e27a11c613..b8ce3ff65bf7 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -2348,16 +2348,17 @@ void EditorFileSystem::update_files(const Vector &p_script_paths) { _save_late_updated_files(); //files need to be updated in the re-scan } - const String old_script_class_icon_path = fs->files[cpos]->script_class_icon_path; - const String old_class_name = fs->files[cpos]->script_class_name; - fs->files[cpos]->type = type; - fs->files[cpos]->resource_script_class = script_class; - fs->files[cpos]->uid = uid; - fs->files[cpos]->script_class_name = _get_global_script_class(type, file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path); - fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file); - fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); - fs->files[cpos]->deps = _get_dependencies(file); - fs->files[cpos]->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file); + EditorFileSystemDirectory::FileInfo *fi = fs->files[cpos]; + const String old_script_class_icon_path = fi->script_class_icon_path; + const String old_class_name = fi->script_class_name; + fi->type = type; + fi->resource_script_class = script_class; + fi->uid = uid; + fi->script_class_name = _get_global_script_class(type, file, &fi->script_class_extends, &fi->script_class_icon_path); + fi->import_group_file = ResourceLoader::get_import_group_file(file); + fi->modified_time = FileAccess::get_modified_time(file); + fi->deps = _get_dependencies(file); + fi->import_valid = type == "TextFile" || type == "OtherFile" || ResourceLoader::is_import_valid(file); if (uid != ResourceUID::INVALID_ID) { if (ResourceUID::get_singleton()->has_id(uid)) { @@ -2367,25 +2368,36 @@ void EditorFileSystem::update_files(const Vector &p_script_paths) { } ResourceUID::get_singleton()->update_cache(); + } else { + if (ResourceLoader::exists(file) && !ResourceLoader::has_custom_uid_support(file) && !FileAccess::exists(file + ".uid")) { + Ref f = FileAccess::open(file + ".uid", FileAccess::WRITE); + if (f.is_valid()) { + const ResourceUID::ID id = ResourceUID::get_singleton()->create_id(); + ResourceUID::get_singleton()->add_id(id, file); + f->store_line(ResourceUID::get_singleton()->id_to_text(id)); + fi->uid = id; + } + } } + // Update preview EditorResourcePreview::get_singleton()->check_for_invalidation(file); - if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(file, fs->files[cpos]->type, fs->files[cpos]->script_class_name, fs->files[cpos]->script_class_extends, fs->files[cpos]->script_class_icon_path); + if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) { + _queue_update_script_class(file, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path); } - if (fs->files[cpos]->type == SNAME("PackedScene")) { + if (fi->type == SNAME("PackedScene")) { _queue_update_scene_groups(file); } - if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Resource"))) { - files_to_update_icon_path.push_back(fs->files[cpos]); - } else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) { + if (ClassDB::is_parent_class(fi->type, SNAME("Resource"))) { + files_to_update_icon_path.push_back(fi); + } else if (old_script_class_icon_path != fi->script_class_icon_path) { update_files_icon_cache = true; } // Restore another script as the global class name if multiple scripts had the same old class name. - if (!old_class_name.is_empty() && fs->files[cpos]->script_class_name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) { + if (!old_class_name.is_empty() && fi->script_class_name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) { EditorFileSystemDirectory::FileInfo *old_fi = nullptr; String old_file = _get_file_by_class_name(filesystem, old_class_name, old_fi); if (!old_file.is_empty() && old_fi) { @@ -3012,7 +3024,7 @@ bool EditorFileSystem::_copy_directory(const String &p_from, const String &p_to, for (String F = old_dir->_get_next(); !F.is_empty(); F = old_dir->_get_next()) { if (old_dir->current_is_dir()) { success = _copy_directory(p_from.path_join(F), p_to.path_join(F), p_files) && success; - } else if (F.get_extension() != "import") { + } else if (F.get_extension() != "import" && F.get_extension() != "uid") { CopiedFile copy; copy.from = p_from.path_join(F); copy.to = p_to.path_join(F); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 3f3b06b428a6..b79d1243ba6c 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2996,6 +2996,7 @@ void EditorInspector::update_tree() { int current_focusable = -1; // Temporarily disable focus following on the root inspector to avoid jumping while the inspector is updating. + bool was_following = get_root_inspector()->is_following_focus(); get_root_inspector()->set_follow_focus(false); if (property_focusable != -1) { @@ -3024,7 +3025,7 @@ void EditorInspector::update_tree() { _clear(!object); if (!object) { - get_root_inspector()->set_follow_focus(true); + get_root_inspector()->set_follow_focus(was_following); return; } @@ -3890,7 +3891,7 @@ void EditorInspector::update_tree() { EditorNode::get_singleton()->hide_unused_editors(); } - get_root_inspector()->set_follow_focus(true); + get_root_inspector()->set_follow_focus(was_following); } void EditorInspector::update_property(const String &p_prop) { @@ -4742,6 +4743,15 @@ void EditorInspector::_notification(int p_what) { update_tree(); } } break; + + case NOTIFICATION_FOCUS_ENTER: { + set_follow_focus(true); + } break; + + case NOTIFICATION_FOCUS_EXIT: { + // Don't follow focus when the inspector is not focused. Prevents potential jumping when gaining focus. + set_follow_focus(false); + } break; } } @@ -4898,7 +4908,6 @@ EditorInspector::EditorInspector() { base_vbox->add_child(main_vbox); set_horizontal_scroll_mode(SCROLL_MODE_DISABLED); - set_follow_focus(true); changing = 0; search_box = nullptr; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 1aad74c8b187..4b7abdf2b072 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1482,16 +1482,6 @@ void EditorNode::save_resource_as(const Ref &p_resource, const String file->popup_file_dialog(); } -void EditorNode::ensure_uid_file(const String &p_new_resource_path) { - if (ResourceLoader::exists(p_new_resource_path) && !ResourceLoader::has_custom_uid_support(p_new_resource_path) && !FileAccess::exists(p_new_resource_path + ".uid")) { - Ref f = FileAccess::open(p_new_resource_path + ".uid", FileAccess::WRITE); - if (f.is_valid()) { - const ResourceUID::ID id = ResourceUID::get_singleton()->create_id(); - f->store_line(ResourceUID::get_singleton()->id_to_text(id)); - } - } -} - void EditorNode::_menu_option(int p_option) { _menu_option_confirm(p_option, false); } @@ -2233,11 +2223,6 @@ void EditorNode::_dialog_action(String p_file) { ERR_FAIL_COND(saving_resource.is_null()); save_resource_in_path(saving_resource, p_file); - if (current_menu_option == RESOURCE_SAVE_AS) { - // Create .uid file when making new Resource. - ensure_uid_file(p_file); - } - saving_resource = Ref(); ObjectID current_id = editor_history.get_current(); Object *current_obj = current_id.is_valid() ? ObjectDB::get_instance(current_id) : nullptr; diff --git a/editor/editor_node.h b/editor/editor_node.h index e9ad3492fef2..b77f6553fe96 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -746,7 +746,6 @@ class EditorNode : public Node { void save_resource_in_path(const Ref &p_resource, const String &p_path); void save_resource(const Ref &p_resource); void save_resource_as(const Ref &p_resource, const String &p_at_path = String()); - void ensure_uid_file(const String &p_new_resource_path); void show_about() { _menu_option_confirm(HELP_ABOUT, false); } diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 69b44a3e8f88..b1a4bb76f325 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2599,6 +2599,17 @@ void EditorPropertyColor::_color_changed(const Color &p_color) { get_edited_object()->set(get_edited_property(), p_color); } +void EditorPropertyColor::_picker_created() { + picker->get_popup()->connect("about_to_popup", callable_mp(this, &EditorPropertyColor::_popup_opening)); + picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed), CONNECT_DEFERRED); +} + +void EditorPropertyColor::_popup_opening() { + EditorNode::get_singleton()->setup_color_picker(picker->get_picker()); + last_color = picker->get_pick_color(); + was_checked = !is_checkable() || is_checked(); +} + void EditorPropertyColor::_popup_closed() { get_edited_object()->set(get_edited_property(), was_checked ? Variant(last_color) : Variant()); if (!picker->get_pick_color().is_equal_approx(last_color)) { @@ -2606,11 +2617,6 @@ void EditorPropertyColor::_popup_closed() { } } -void EditorPropertyColor::_picker_opening() { - last_color = picker->get_pick_color(); - was_checked = !is_checkable() || is_checked(); -} - void EditorPropertyColor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: @@ -2654,9 +2660,7 @@ EditorPropertyColor::EditorPropertyColor() { add_child(picker); picker->set_flat(true); picker->connect("color_changed", callable_mp(this, &EditorPropertyColor::_color_changed)); - picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed), CONNECT_DEFERRED); - picker->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(picker->get_picker())); - picker->get_popup()->connect("about_to_popup", callable_mp(this, &EditorPropertyColor::_picker_opening)); + picker->connect("picker_created", callable_mp(this, &EditorPropertyColor::_picker_created), CONNECT_ONE_SHOT); } ////////////// NODE PATH ////////////////////// diff --git a/editor/editor_properties.h b/editor/editor_properties.h index bcdae5342d28..6fe8cc3c3f38 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -593,8 +593,9 @@ class EditorPropertyColor : public EditorProperty { GDCLASS(EditorPropertyColor, EditorProperty); ColorPickerButton *picker = nullptr; void _color_changed(const Color &p_color); + void _picker_created(); + void _popup_opening(); void _popup_closed(); - void _picker_opening(); Color last_color; bool live_changes_enabled = true; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 948e752c2432..9663157ff990 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -417,7 +417,7 @@ void EditorPropertyArray::update_property() { } else { edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); edit->set_button_icon(Ref()); - edit->set_text(vformat(TTR("%s (size %s)"), array_type_name, itos(size))); + edit->set_text(vformat(TTR("%s (size %d)"), array_type_name, size)); } bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); @@ -952,7 +952,11 @@ void EditorPropertyDictionary::_property_changed(const String &p_property, Varia } object->set(p_property, p_value); - emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing); + bool new_item_or_key = !p_property.begins_with("indices"); + emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing || new_item_or_key); + if (new_item_or_key) { + update_property(); + } } void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) { diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index 5251f1e558d5..84367971ce5d 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -104,99 +104,16 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie, const V } } - int screen = EDITOR_GET("run/window_placement/screen"); - if (screen == -5) { - // Same as editor - screen = DisplayServer::get_singleton()->window_get_current_screen(); - } else if (screen == -4) { - // Previous monitor (wrap to the other end if needed) - screen = Math::wrapi( - DisplayServer::get_singleton()->window_get_current_screen() - 1, - 0, - DisplayServer::get_singleton()->get_screen_count()); - } else if (screen == -3) { - // Next monitor (wrap to the other end if needed) - screen = Math::wrapi( - DisplayServer::get_singleton()->window_get_current_screen() + 1, - 0, - DisplayServer::get_singleton()->get_screen_count()); + WindowPlacement window_placement = get_window_placement(); + if (window_placement.position != Point2i(INT_MAX, INT_MAX)) { + args.push_back("--position"); + args.push_back(itos(window_placement.position.x) + "," + itos(window_placement.position.y)); } - Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen); - - int window_placement = EDITOR_GET("run/window_placement/rect"); - if (screen_rect != Rect2()) { - Size2 window_size; - window_size.x = GLOBAL_GET("display/window/size/viewport_width"); - window_size.y = GLOBAL_GET("display/window/size/viewport_height"); - - Size2 desired_size; - desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); - desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); - if (desired_size.x > 0 && desired_size.y > 0) { - window_size = desired_size; - } - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) { - bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi"); - int display_scale = 1; - - if (OS::get_singleton()->is_hidpi_allowed()) { - if (hidpi_proj) { - display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale. - } else { - display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down. - } - } else { - if (hidpi_proj) { - display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up. - } else { - display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale. - } - } - screen_rect.position /= display_scale; - screen_rect.size /= display_scale; - } - - switch (window_placement) { - case 0: { // top left - args.push_back("--position"); - args.push_back(itos(screen_rect.position.x) + "," + itos(screen_rect.position.y)); - } break; - case 1: { // centered - Vector2 pos = (screen_rect.position) + ((screen_rect.size - window_size) / 2).floor(); - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - } break; - case 2: { // custom pos - Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position"); - pos += screen_rect.position; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - } break; - case 3: { // force maximized - Vector2 pos = screen_rect.position + screen_rect.size / 2; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - args.push_back("--maximized"); - } break; - case 4: { // force fullscreen - Vector2 pos = screen_rect.position + screen_rect.size / 2; - args.push_back("--position"); - args.push_back(itos(pos.x) + "," + itos(pos.y)); - args.push_back("--fullscreen"); - } break; - } - } else { - // Unable to get screen info, skip setting position. - switch (window_placement) { - case 3: { // force maximized - args.push_back("--maximized"); - } break; - case 4: { // force fullscreen - args.push_back("--fullscreen"); - } break; - } + if (window_placement.force_maximized) { + args.push_back("--maximized"); + } else if (window_placement.force_fullscreen) { + args.push_back("--fullscreen"); } List breakpoints; @@ -297,6 +214,98 @@ OS::ProcessID EditorRun::get_current_process() const { return pids.front()->get(); } +EditorRun::WindowPlacement EditorRun::get_window_placement() { + WindowPlacement placement = WindowPlacement(); + placement.screen = EDITOR_GET("run/window_placement/screen"); + if (placement.screen == -5) { + // Same as editor + placement.screen = DisplayServer::get_singleton()->window_get_current_screen(); + } else if (placement.screen == -4) { + // Previous monitor (wrap to the other end if needed) + placement.screen = Math::wrapi( + DisplayServer::get_singleton()->window_get_current_screen() - 1, + 0, + DisplayServer::get_singleton()->get_screen_count()); + } else if (placement.screen == -3) { + // Next monitor (wrap to the other end if needed) + placement.screen = Math::wrapi( + DisplayServer::get_singleton()->window_get_current_screen() + 1, + 0, + DisplayServer::get_singleton()->get_screen_count()); + } else if (placement.screen == -2) { + // Primary screen + placement.screen = DisplayServer::get_singleton()->get_primary_screen(); + } + + placement.size.x = GLOBAL_GET("display/window/size/viewport_width"); + placement.size.y = GLOBAL_GET("display/window/size/viewport_height"); + + Size2 desired_size; + desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); + desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); + if (desired_size.x > 0 && desired_size.y > 0) { + placement.size = desired_size; + } + + Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(placement.screen); + + int window_placement = EDITOR_GET("run/window_placement/rect"); + if (screen_rect != Rect2()) { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) { + bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi"); + int display_scale = 1; + + if (OS::get_singleton()->is_hidpi_allowed()) { + if (hidpi_proj) { + display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale. + } else { + display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down. + } + } else { + if (hidpi_proj) { + display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up. + } else { + display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale. + } + } + screen_rect.position /= display_scale; + screen_rect.size /= display_scale; + } + + switch (window_placement) { + case 0: { // top left + placement.position = screen_rect.position; + } break; + case 1: { // centered + placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor(); + } break; + case 2: { // custom pos + Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position"); + pos += screen_rect.position; + placement.position = pos; + } break; + case 3: { // force maximized + placement.force_maximized = true; + } break; + case 4: { // force fullscreen + placement.force_fullscreen = true; + } break; + } + } else { + // Unable to get screen info, skip setting position. + switch (window_placement) { + case 3: { // force maximized + placement.force_maximized = true; + } break; + case 4: { // force fullscreen + placement.force_fullscreen = true; + } break; + } + } + + return placement; +} + EditorRun::EditorRun() { status = STATUS_STOP; running_scene = ""; diff --git a/editor/editor_run.h b/editor/editor_run.h index 94fb977e931b..5e342944130c 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -45,6 +45,14 @@ class EditorRun { List pids; + struct WindowPlacement { + int screen = 0; + Point2i position = Point2i(INT_MAX, INT_MAX); + Size2i size; + bool force_maximized = false; + bool force_fullscreen = false; + }; + private: Status status; String running_scene; @@ -64,6 +72,8 @@ class EditorRun { int get_child_process_count() const { return pids.size(); } OS::ProcessID get_current_process() const; + static WindowPlacement get_window_placement(); + EditorRun(); }; diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index 9e1c2fea3e26..faa809154982 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -145,6 +145,8 @@ Error EditorRunNative::start_run_native(int p_id) { } run_confirmed = false; + preset->update_value_overrides(); + emit_signal(SNAME("native_run"), preset); BitField flags = 0; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 116950514396..9a5a51a18a25 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -940,6 +940,8 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2,Launch in PiP mode:3"; EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints) + EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/game_embed_mode", 0, "Use Per-Project Configuration:0,Embed Game:1,Make Game Workspace Floating:2,Disabled:3"); + int default_play_window_pip_mode = 0; #ifdef ANDROID_ENABLED default_play_window_pip_mode = 2; diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index 641c36943618..607c922c691c 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -439,6 +439,7 @@ TreeItem *EditorSettingsDialog::_create_shortcut_treeitem(TreeItem *p_parent, co TreeItem *event_item = shortcuts->create_item(shortcut_item); + // TRANSLATORS: This is the label for the main input event of a shortcut. event_item->set_text(0, shortcut_item->get_child_count() == 1 ? TTRC("Primary") : ""); event_item->set_text(1, ie->as_text()); event_item->set_auto_translate_mode(1, AUTO_TRANSLATE_MODE_DISABLED); diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 8b142198cd8c..8b44a6f88ab0 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -35,6 +35,7 @@ #include "core/extension/gdextension.h" #include "core/io/file_access_encrypted.h" #include "core/io/file_access_pack.h" // PACK_HEADER_MAGIC, PACK_FORMAT_VERSION +#include "core/io/resource_uid.h" #include "core/io/zip_io.h" #include "core/version.h" #include "editor/editor_file_system.h" @@ -49,6 +50,26 @@ #include "scene/resources/image_texture.h" #include "scene/resources/packed_scene.h" +class EditorExportSaveProxy { + HashSet saved_paths; + EditorExportPlatform::EditorExportSaveFunction save_func; + bool tracking_saves = false; + +public: + bool has_saved(const String &p_path) const { return saved_paths.has(p_path); } + + Error save_file(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total, const Vector &p_enc_in_filters, const Vector &p_enc_ex_filters, const Vector &p_key, uint64_t p_seed) { + if (tracking_saves) { + saved_paths.insert(p_path.simplify_path().trim_prefix("res://")); + } + + return save_func(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key, p_seed); + } + + EditorExportSaveProxy(EditorExportPlatform::EditorExportSaveFunction p_save_func, bool p_track_saves) : + save_func(p_save_func), tracking_saves(p_track_saves) {} +}; + static int _get_pad(int p_alignment, int p_n) { int rest = p_n % p_alignment; int pad = 0; @@ -59,17 +80,6 @@ static int _get_pad(int p_alignment, int p_n) { return pad; } -template -static bool _has_pack_path(const T &p_paths, const String &p_path) { - for (const String &E : p_paths) { - if (E.simplify_path().trim_prefix("res://") == p_path) { - return true; - } - } - - return false; -} - #define PCK_PADDING 16 bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) { @@ -955,8 +965,8 @@ Vector EditorExportPlatform::get_forced_export_files() { files.push_back(ProjectSettings::get_singleton()->get_global_class_list_path()); - String icon = GLOBAL_GET("application/config/icon"); - String splash = GLOBAL_GET("application/boot_splash/image"); + String icon = ResourceUID::ensure_path(GLOBAL_GET("application/config/icon")); + String splash = ResourceUID::ensure_path(GLOBAL_GET("application/boot_splash/image")); if (!icon.is_empty() && FileAccess::exists(icon)) { files.push_back(icon); } @@ -1139,9 +1149,10 @@ Error EditorExportPlatform::export_project_files(const Ref & } } + EditorExportSaveProxy save_proxy(p_save_func, p_remove_func != nullptr); + Error err = OK; Vector> export_plugins = EditorExport::get_singleton()->get_export_plugins(); - Vector extra_paths; struct SortByName { bool operator()(const Ref &left, const Ref &right) const { @@ -1162,12 +1173,10 @@ Error EditorExportPlatform::export_project_files(const Ref & } } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, 0, paths.size(), enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } - - extra_paths.push_back(export_plugins[i]->extra_files[j].path); } export_plugins.write[i]->_clear(); @@ -1280,7 +1289,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) { - err = p_save_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1289,8 +1298,6 @@ Error EditorExportPlatform::export_project_files(const Ref & path_remaps.push_back(path); path_remaps.push_back(export_plugins[i]->extra_files[j].path); } - - extra_paths.push_back(export_plugins[i]->extra_files[j].path); } if (export_plugins[i]->skipped) { @@ -1312,7 +1319,7 @@ Error EditorExportPlatform::export_project_files(const Ref & if (importer_type == "keep") { // Just keep file as-is. Vector array = FileAccess::get_file_as_bytes(path); - err = p_save_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; @@ -1355,13 +1362,13 @@ Error EditorExportPlatform::export_project_files(const Ref & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } // Now actual remapped file: sarr = FileAccess::get_file_as_bytes(export_path); - err = p_save_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1391,14 +1398,14 @@ Error EditorExportPlatform::export_project_files(const Ref & if (remap == "path") { String remapped_path = config->get_value("remap", remap); Vector array = FileAccess::get_file_as_bytes(remapped_path); - err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); } else if (remap.begins_with("path.")) { String feature = remap.get_slice(".", 1); if (remap_features.has(feature)) { String remapped_path = config->get_value("remap", remap); Vector array = FileAccess::get_file_as_bytes(remapped_path); - err = p_save_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); } else { // Remove paths if feature not enabled. config->erase_section_key("remap", remap); @@ -1424,7 +1431,7 @@ Error EditorExportPlatform::export_project_files(const Ref & sarr.resize(cs.size()); memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); - err = p_save_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; @@ -1445,7 +1452,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } Vector array = FileAccess::get_file_as_bytes(export_path); - err = p_save_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1509,7 +1516,7 @@ Error EditorExportPlatform::export_project_files(const Ref & new_file.write[j] = utf8[j]; } - err = p_save_func(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, from + ".remap", new_file, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1531,7 +1538,7 @@ Error EditorExportPlatform::export_project_files(const Ref & } else { array = FileAccess::get_file_as_bytes(forced_export[i]); } - err = p_save_func(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, forced_export[i], array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1540,7 +1547,7 @@ Error EditorExportPlatform::export_project_files(const Ref & Dictionary int_export = get_internal_export_files(p_preset, p_debug); for (const Variant &int_name : int_export.keys()) { const PackedByteArray &array = int_export[int_name]; - err = p_save_func(p_udata, int_name, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, int_name, array, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } @@ -1552,22 +1559,16 @@ Error EditorExportPlatform::export_project_files(const Ref & Vector data = FileAccess::get_file_as_bytes(engine_cfb); DirAccess::remove_file_or_error(engine_cfb); - err = p_save_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key, seed); + err = save_proxy.save_file(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key, seed); if (err != OK) { return err; } if (p_remove_func) { - for (const String &E : PackedData::get_singleton()->get_file_paths()) { - String simplified_path = E.simplify_path(); - if (simplified_path == config_file) { - continue; - } - - String pack_path = simplified_path.trim_suffix(".remap"); - - if (!_has_pack_path(paths, pack_path) && !_has_pack_path(extra_paths, pack_path) && !_has_pack_path(path_remaps, pack_path) && !_has_pack_path(forced_export, pack_path)) { - err = p_remove_func(p_udata, E); + HashSet currently_loaded_paths = PackedData::get_singleton()->get_file_paths(); + for (const String &path : currently_loaded_paths) { + if (!save_proxy.has_saved(path)) { + err = p_remove_func(p_udata, path); if (err != OK) { return err; } diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index ddc340ee77fb..b3cbf3906017 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -139,6 +139,65 @@ Variant EditorExportPlugin::get_option(const StringName &p_name) const { return export_preset->get(p_name); } +void EditorExportPlugin::add_tvos_framework(const String &p_path) { + tvos_frameworks.push_back(p_path); +} + +void EditorExportPlugin::add_tvos_embedded_framework(const String &p_path) { + tvos_embedded_frameworks.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_frameworks() const { + return tvos_frameworks; +} + +Vector EditorExportPlugin::get_tvos_embedded_frameworks() const { + return tvos_embedded_frameworks; +} + +void EditorExportPlugin::add_tvos_plist_content(const String &p_plist_content) { + tvos_plist_content += p_plist_content + "\n"; +} + +String EditorExportPlugin::get_tvos_plist_content() const { + return tvos_plist_content; +} + +void EditorExportPlugin::add_tvos_linker_flags(const String &p_flags) { + if (tvos_linker_flags.length() > 0) { + tvos_linker_flags += ' '; + } + tvos_linker_flags += p_flags; +} + +String EditorExportPlugin::get_tvos_linker_flags() const { + return tvos_linker_flags; +} + +void EditorExportPlugin::add_tvos_bundle_file(const String &p_path) { + tvos_bundle_files.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_bundle_files() const { + return tvos_bundle_files; +} + +void EditorExportPlugin::add_tvos_cpp_code(const String &p_code) { + tvos_cpp_code += p_code; +} + +String EditorExportPlugin::get_tvos_cpp_code() const { + return tvos_cpp_code; +} + +void EditorExportPlugin::add_tvos_project_static_lib(const String &p_path) { + tvos_project_static_libs.push_back(p_path); +} + +Vector EditorExportPlugin::get_tvos_project_static_libs() const { + return tvos_project_static_libs; +} + String EditorExportPlugin::_has_valid_export_configuration(const Ref &p_export_platform, const Ref &p_preset) { String warning; if (!supports_platform(p_export_platform)) { @@ -331,6 +390,13 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("add_ios_bundle_file", "path"), &EditorExportPlugin::add_ios_bundle_file); ClassDB::bind_method(D_METHOD("add_ios_cpp_code", "code"), &EditorExportPlugin::add_ios_cpp_code); ClassDB::bind_method(D_METHOD("add_macos_plugin_file", "path"), &EditorExportPlugin::add_macos_plugin_file); + ClassDB::bind_method(D_METHOD("add_tvos_project_static_lib", "path"), &EditorExportPlugin::add_tvos_project_static_lib); + ClassDB::bind_method(D_METHOD("add_tvos_framework", "path"), &EditorExportPlugin::add_tvos_framework); + ClassDB::bind_method(D_METHOD("add_tvos_embedded_framework", "path"), &EditorExportPlugin::add_tvos_embedded_framework); + ClassDB::bind_method(D_METHOD("add_tvos_plist_content", "plist_content"), &EditorExportPlugin::add_tvos_plist_content); + ClassDB::bind_method(D_METHOD("add_tvos_linker_flags", "flags"), &EditorExportPlugin::add_tvos_linker_flags); + ClassDB::bind_method(D_METHOD("add_tvos_bundle_file", "path"), &EditorExportPlugin::add_tvos_bundle_file); + ClassDB::bind_method(D_METHOD("add_tvos_cpp_code", "code"), &EditorExportPlugin::add_tvos_cpp_code); ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option); diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index e991a9a5e87b..e40a48a96916 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -64,6 +64,14 @@ class EditorExportPlugin : public RefCounted { Vector macos_plugin_files; + Vector tvos_frameworks; + Vector tvos_embedded_frameworks; + Vector tvos_project_static_libs; + String tvos_plist_content; + String tvos_linker_flags; + Vector tvos_bundle_files; + String tvos_cpp_code; + _FORCE_INLINE_ void _clear() { shared_objects.clear(); extra_files.clear(); @@ -78,6 +86,13 @@ class EditorExportPlugin : public RefCounted { ios_linker_flags = ""; ios_cpp_code = ""; macos_plugin_files.clear(); + + tvos_frameworks.clear(); + tvos_embedded_frameworks.clear(); + tvos_bundle_files.clear(); + tvos_plist_content = ""; + tvos_linker_flags = ""; + tvos_cpp_code = ""; } // Export @@ -105,6 +120,14 @@ class EditorExportPlugin : public RefCounted { void add_ios_cpp_code(const String &p_code); void add_macos_plugin_file(const String &p_path); + void add_tvos_framework(const String &p_path); + void add_tvos_embedded_framework(const String &p_path); + void add_tvos_project_static_lib(const String &p_path); + void add_tvos_plist_content(const String &p_plist_content); + void add_tvos_linker_flags(const String &p_flags); + void add_tvos_bundle_file(const String &p_path); + void add_tvos_cpp_code(const String &p_code); + void skip(); virtual void _export_file(const String &p_path, const String &p_type, const HashSet &p_features); @@ -183,7 +206,17 @@ class EditorExportPlugin : public RefCounted { String get_ios_linker_flags() const; Vector get_ios_bundle_files() const; String get_ios_cpp_code() const; + const Vector &get_macos_plugin_files() const; + + Vector get_tvos_frameworks() const; + Vector get_tvos_embedded_frameworks() const; + Vector get_tvos_project_static_libs() const; + String get_tvos_plist_content() const; + String get_tvos_linker_flags() const; + Vector get_tvos_bundle_files() const; + String get_tvos_cpp_code() const; + Variant get_option(const StringName &p_name) const; }; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index f626891bd8ea..b1156f61a3d9 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -303,6 +303,7 @@ void FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon(0, _get_tree_item_icon(!file_info.import_broken, file_info.type, file_info.icon_path)); if (da->is_link(file_metadata)) { file_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay"))); + // TRANSLATORS: This is a tooltip for a file that is a symbolic link to another file. file_item->set_tooltip_text(0, vformat(TTR("Link to: %s"), da->read_link(file_metadata))); } file_item->set_icon_max_width(0, icon_size); @@ -1486,6 +1487,11 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ break; } } + } else { + Ref res = ResourceCache::get_ref(old_path); + if (res.is_valid()) { + res->set_path_cache(new_path); + } } } diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index 30b618cdb4f6..a1bee6d54c37 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -39,6 +39,7 @@ #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/scroll_container.h" #include "scene/gui/split_container.h" void EditorBottomPanel::_notification(int p_what) { @@ -46,6 +47,19 @@ void EditorBottomPanel::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin"))); expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock"))); + left_button->set_button_icon(get_editor_theme_icon(SNAME("Back"))); + right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward"))); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + if (is_layout_rtl()) { + bottom_hbox->move_child(left_button, button_scroll->get_index() + 1); + bottom_hbox->move_child(right_button, 0); + } else { + bottom_hbox->move_child(right_button, button_scroll->get_index() + 1); + bottom_hbox->move_child(left_button, 0); + } } break; } } @@ -59,6 +73,33 @@ void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, b } } +void EditorBottomPanel::_scroll(bool p_right) { + HScrollBar *h_scroll = button_scroll->get_h_scroll_bar(); + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { + h_scroll->set_value(p_right ? h_scroll->get_max() : 0); + } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1)); + } else { + h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1)); + } +} + +void EditorBottomPanel::_update_scroll_buttons() { + bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width; + left_button->set_visible(show_arrows); + right_button->set_visible(show_arrows); + + if (show_arrows) { + _update_disabled_buttons(); + } +} + +void EditorBottomPanel::_update_disabled_buttons() { + HScrollBar *h_scroll = button_scroll->get_h_scroll_bar(); + left_button->set_disabled(h_scroll->get_value() == 0); + right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max()); +} + void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) { ERR_FAIL_INDEX(p_idx, items.size()); @@ -93,6 +134,7 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore if (expand_button->is_pressed()) { EditorNode::get_top_split()->hide(); } + callable_mp(button_scroll, &ScrollContainer::ensure_control_visible).call_deferred(items[p_idx].button); } else { add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles))); items[p_idx].button->set_pressed_no_signal(false); @@ -259,9 +301,35 @@ EditorBottomPanel::EditorBottomPanel() { bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon. item_vbox->add_child(bottom_hbox); + left_button = memnew(Button); + left_button->set_tooltip_text(TTR("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page.")); + left_button->set_theme_type_variation("BottomPanelButton"); + left_button->set_focus_mode(Control::FOCUS_NONE); + left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false)); + bottom_hbox->add_child(left_button); + left_button->hide(); + + button_scroll = memnew(ScrollContainer); + button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL); + button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER); + button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED); + button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED); + bottom_hbox->add_child(button_scroll); + + right_button = memnew(Button); + right_button->set_tooltip_text(TTR("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page.")); + right_button->set_theme_type_variation("BottomPanelButton"); + right_button->set_focus_mode(Control::FOCUS_NONE); + right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true)); + bottom_hbox->add_child(right_button); + right_button->hide(); + + callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred(); + button_hbox = memnew(HBoxContainer); - button_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); - bottom_hbox->add_child(button_hbox); + button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN); + button_scroll->add_child(button_hbox); editor_toaster = memnew(EditorToaster); bottom_hbox->add_child(editor_toaster); diff --git a/editor/gui/editor_bottom_panel.h b/editor/gui/editor_bottom_panel.h index e94aa941a627..c81d7da6fa8b 100644 --- a/editor/gui/editor_bottom_panel.h +++ b/editor/gui/editor_bottom_panel.h @@ -38,6 +38,7 @@ class ConfigFile; class EditorToaster; class HBoxContainer; class VBoxContainer; +class ScrollContainer; class EditorBottomPanel : public PanelContainer { GDCLASS(EditorBottomPanel, PanelContainer); @@ -53,6 +54,9 @@ class EditorBottomPanel : public PanelContainer { VBoxContainer *item_vbox = nullptr; HBoxContainer *bottom_hbox = nullptr; + Button *left_button = nullptr; + Button *right_button = nullptr; + ScrollContainer *button_scroll = nullptr; HBoxContainer *button_hbox = nullptr; EditorToaster *editor_toaster = nullptr; Button *pin_button = nullptr; @@ -63,6 +67,9 @@ class EditorBottomPanel : public PanelContainer { void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false); void _pin_button_toggled(bool p_pressed); void _expand_button_toggled(bool p_pressed); + void _scroll(bool p_right); + void _update_scroll_buttons(); + void _update_disabled_buttons(); bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control); diff --git a/editor/icons/FixedSize.svg b/editor/icons/FixedSize.svg new file mode 100644 index 000000000000..1ca5752c23ca --- /dev/null +++ b/editor/icons/FixedSize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/KeepAspect.svg b/editor/icons/KeepAspect.svg index 1cc831563fcb..f178a2aa879b 100644 --- a/editor/icons/KeepAspect.svg +++ b/editor/icons/KeepAspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/editor/icons/Stretch.svg b/editor/icons/Stretch.svg new file mode 100644 index 000000000000..f8cb75fe864a --- /dev/null +++ b/editor/icons/Stretch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index dc2fc72a48b5..036c0eca9b6b 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -750,7 +750,6 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) { object_menu = memnew(MenuButton); object_menu->set_flat(false); object_menu->set_theme_type_variation("FlatMenuButton"); - object_menu->set_shortcut_context(this); property_tools_hb->add_child(object_menu); object_menu->set_tooltip_text(TTR("Manage object properties.")); object_menu->get_popup()->connect("about_to_popup", callable_mp(this, &InspectorDock::_prepare_menu)); diff --git a/editor/plugins/embedded_process.cpp b/editor/plugins/embedded_process.cpp index 75e532513464..10c7188b5c53 100644 --- a/editor/plugins/embedded_process.cpp +++ b/editor/plugins/embedded_process.cpp @@ -50,7 +50,7 @@ void EmbeddedProcess::_notification(int p_what) { Rect2i new_global_rect = get_global_rect(); if (last_global_rect != new_global_rect) { last_global_rect = new_global_rect; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } break; @@ -60,7 +60,7 @@ void EmbeddedProcess::_notification(int p_what) { case NOTIFICATION_RESIZED: case NOTIFICATION_VISIBILITY_CHANGED: case NOTIFICATION_WM_POSITION_CHANGED: { - _queue_update_embedded_process(); + queue_update_embedded_process(); } break; case NOTIFICATION_THEME_CHANGED: { focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles)); @@ -77,7 +77,7 @@ void EmbeddedProcess::_notification(int p_what) { } } break; case NOTIFICATION_FOCUS_ENTER: { - _queue_update_embedded_process(); + queue_update_embedded_process(); } break; case NOTIFICATION_APPLICATION_FOCUS_IN: { application_has_focus = true; @@ -88,7 +88,7 @@ void EmbeddedProcess::_notification(int p_what) { // or if the current window is a different popup or secondary window. if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) { grab_focus(); - _queue_update_embedded_process(); + queue_update_embedded_process(); } } } break; @@ -102,27 +102,33 @@ void EmbeddedProcess::_notification(int p_what) { void EmbeddedProcess::set_window_size(const Size2i p_window_size) { if (window_size != p_window_size) { window_size = p_window_size; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) { if (keep_aspect != p_keep_aspect) { keep_aspect = p_keep_aspect; - _queue_update_embedded_process(); + queue_update_embedded_process(); } } Rect2i EmbeddedProcess::_get_global_embedded_window_rect() { Rect2i control_rect = get_global_rect(); - control_rect = Rect2i(control_rect.position, control_rect.size.maxi(1)); - if (keep_aspect) { - Rect2i desired_rect = control_rect; - float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y); - desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1); + control_rect = Rect2i(control_rect.position + margin_top_left, (control_rect.size - get_margins_size()).maxi(1)); + if (window_size != Size2i()) { + Rect2i desired_rect = Rect2i(); + if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) { + // Fixed at the desired size. + desired_rect.size = window_size; + } else { + float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y); + desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1); + } desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2)); return desired_rect; } else { + // Stretch, use all the control area. return control_rect; } } @@ -133,8 +139,28 @@ Rect2i EmbeddedProcess::get_screen_embedded_window_rect() { rect.position += window->get_position(); } - // Removing margins to make space for the focus border style. - return Rect2i(rect.position.x + margin_top_left.x, rect.position.y + margin_top_left.y, MAX(rect.size.x - (margin_top_left.x + margin_bottom_right.x), 1), MAX(rect.size.y - (margin_top_left.y + margin_bottom_right.y), 1)); + return rect; +} + +int EmbeddedProcess::get_margin_size(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); + + switch (p_side) { + case SIDE_LEFT: + return margin_top_left.x; + case SIDE_RIGHT: + return margin_bottom_right.x; + case SIDE_TOP: + return margin_top_left.y; + case SIDE_BOTTOM: + return margin_bottom_right.y; + } + + return 0; +} + +Size2 EmbeddedProcess::get_margins_size() { + return margin_top_left + margin_bottom_right; } bool EmbeddedProcess::is_embedding_in_progress() { @@ -145,6 +171,10 @@ bool EmbeddedProcess::is_embedding_completed() { return embedding_completed; } +int EmbeddedProcess::get_embedded_pid() const { + return current_process_id; +} + void EmbeddedProcess::embed_process(OS::ProcessID p_pid) { if (!window) { return; @@ -211,7 +241,7 @@ bool EmbeddedProcess::_is_embedded_process_updatable() { return window && current_process_id != 0 && embedding_completed; } -void EmbeddedProcess::_queue_update_embedded_process() { +void EmbeddedProcess::queue_update_embedded_process() { if (updated_embedded_process_queued || !_is_embedded_process_updatable()) { return; } @@ -283,7 +313,7 @@ void EmbeddedProcess::_check_mouse_over() { // When we already have the focus and the user moves the mouse over the embedded process, // we just need to refocus the process. if (focused) { - _queue_update_embedded_process(); + queue_update_embedded_process(); } else { grab_focus(); queue_redraw(); diff --git a/editor/plugins/embedded_process.h b/editor/plugins/embedded_process.h index d4265dc899c3..14ad44fdaedd 100644 --- a/editor/plugins/embedded_process.h +++ b/editor/plugins/embedded_process.h @@ -59,7 +59,6 @@ class EmbeddedProcess : public Control { Rect2i last_global_rect; void _try_embed_process(); - void _queue_update_embedded_process(); void _update_embedded_process(); void _timer_embedding_timeout(); void _draw(); @@ -78,10 +77,14 @@ class EmbeddedProcess : public Control { void set_window_size(const Size2i p_window_size); void set_keep_aspect(bool p_keep_aspect); + void queue_update_embedded_process(); Rect2i get_screen_embedded_window_rect(); + int get_margin_size(Side p_side) const; + Size2 get_margins_size(); bool is_embedding_in_progress(); bool is_embedding_completed(); + int get_embedded_pid() const; EmbeddedProcess(); ~EmbeddedProcess(); diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index 212957dd2d9f..6c4ecc8be9fc 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -32,13 +32,16 @@ #include "core/config/project_settings.h" #include "core/debugger/debugger_marshalls.h" +#include "core/string/translation_server.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_command_palette.h" #include "editor/editor_feature_profile.h" #include "editor/editor_interface.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/editor_string_names.h" #include "editor/gui/editor_run_bar.h" #include "editor/plugins/embedded_process.h" #include "editor/themes/editor_scale.h" @@ -203,6 +206,12 @@ void GameView::_sessions_changed() { } _update_debugger_buttons(); + + if (embedded_process->is_embedding_completed()) { + if (!embedded_script_debugger || !embedded_script_debugger->is_session_active() || embedded_script_debugger->get_remote_pid() != embedded_process->get_embedded_pid()) { + _attach_script_debugger(); + } + } } void GameView::_instance_starting_static(int p_idx, List &r_arguments) { @@ -214,13 +223,49 @@ void GameView::_instance_starting(int p_idx, List &r_arguments) { if (!is_feature_enabled) { return; } - if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) { - window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect); + if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && _get_embed_available() == EMBED_AVAILABLE) { + // Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title. + String appname = GLOBAL_GET("application/config/name"); + appname = vformat("%s (DEBUG)", TranslationServer::get_singleton()->translate(appname)); + window_wrapper->set_window_title(appname); + + _show_update_window_wrapper(); } _update_arguments_for_instance(p_idx, r_arguments); } +void GameView::_show_update_window_wrapper() { + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + Point2 position = floating_window_rect.position; + Size2i size = floating_window_rect.size; + int screen = floating_window_screen; + + Size2 wrapped_margins_size = window_wrapper->get_margins_size(); + Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position(); + offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT); + offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP); + + // Obtain the size around the embedded process control. Usually, the difference between the game view's get_size + // and the embedded control should work. However, when the control is hidden and has never been displayed, + // the size of the embedded control is not calculated. + Size2 old_min_size = embedded_process->get_custom_minimum_size(); + embedded_process->set_custom_minimum_size(Size2i()); + Size2 min_size = get_minimum_size(); + embedded_process->set_custom_minimum_size(old_min_size); + + Point2 size_diff_embedded_process = Point2(0, min_size.y) + embedded_process->get_margins_size(); + + if (placement.position != Point2i(INT_MAX, INT_MAX)) { + position = placement.position - offset_embedded_process; + screen = placement.screen; + } + if (placement.size != Size2i()) { + size = placement.size + size_diff_embedded_process + wrapped_margins_size; + } + window_wrapper->restore_window_from_saved_position(Rect2(position, size), screen, Rect2i()); +} + void GameView::_play_pressed() { if (!is_feature_enabled) { return; @@ -235,7 +280,7 @@ void GameView::_play_pressed() { screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index(); } - if (embed_on_play) { + if (embed_on_play && _get_embed_available() == EMBED_AVAILABLE) { // It's important to disable the low power mode when unfocused because otherwise // the button in the editor are not responsive and if the user moves the mouse quickly, // the mouse clicks are not registered. @@ -255,6 +300,9 @@ void GameView::_stop_pressed() { return; } + _detach_script_debugger(); + paused = false; + EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true); embedded_process->reset(); _update_ui(); @@ -272,6 +320,7 @@ void GameView::_stop_pressed() { } void GameView::_embedding_completed() { + _attach_script_debugger(); _update_ui(); } @@ -290,9 +339,18 @@ void GameView::_embedded_process_focused() { } } -void GameView::_project_settings_changed() { +void GameView::_editor_or_project_settings_changed() { // Update the window size and aspect ratio. _update_embed_window_size(); + + if (window_wrapper->get_window_enabled()) { + _show_update_window_wrapper(); + if (embedded_process->is_embedding_completed()) { + embedded_process->queue_update_embedded_process(); + } + } + + _update_ui(); } void GameView::_update_debugger_buttons() { @@ -344,35 +402,103 @@ void GameView::_embed_options_menu_menu_id_pressed(int p_id) { switch (p_id) { case EMBED_RUN_GAME_EMBEDDED: { embed_on_play = !embed_on_play; - EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + if (game_mode == 0) { // Save only if not overridden by editor. + EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play); + } } break; case EMBED_MAKE_FLOATING_ON_PLAY: { make_floating_on_play = !make_floating_on_play; - EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + if (game_mode == 0) { // Save only if not overridden by editor. + EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play); + } } break; } _update_embed_menu_options(); + _update_ui(); } -void GameView::_keep_aspect_button_pressed() { - embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); +void GameView::_size_mode_button_pressed(int size_mode) { + embed_size_mode = (EmbedSizeMode)size_mode; + EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_size_mode", size_mode); + + _update_embed_menu_options(); + _update_embed_window_size(); +} + +GameView::EmbedAvailability GameView::_get_embed_available() { + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED; + } + if (!EditorNode::get_singleton()->is_multi_window_enabled()) { + return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE; + } + + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + if (placement.force_fullscreen) { + return EMBED_NOT_AVAILABLE_FULLSCREEN; + } + if (placement.force_maximized) { + return EMBED_NOT_AVAILABLE_MAXIMIZED; + } + + DisplayServer::WindowMode window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int()); + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED) { + return EMBED_NOT_AVAILABLE_MINIMIZED; + } + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MAXIMIZED) { + return EMBED_NOT_AVAILABLE_MAXIMIZED; + } + if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_FULLSCREEN || window_mode == DisplayServer::WindowMode::WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + return EMBED_NOT_AVAILABLE_FULLSCREEN; + } + + return EMBED_AVAILABLE; } void GameView::_update_ui() { bool show_game_size = false; - if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { - state_label->set_text(TTR("Game embedding not available on your OS.")); - } else if (embedded_process->is_embedding_completed()) { - state_label->set_text(""); - show_game_size = true; - } else if (embedded_process->is_embedding_in_progress()) { - state_label->set_text(TTR("Game starting...")); - } else if (EditorRunBar::get_singleton()->is_playing()) { - state_label->set_text(TTR("Game running not embedded.")); - } else if (embed_on_play) { - state_label->set_text(TTR("Press play to start the game.")); + EmbedAvailability available = _get_embed_available(); + + switch (available) { + case EMBED_AVAILABLE: + if (embedded_process->is_embedding_completed()) { + state_label->set_text(""); + show_game_size = true; + } else if (embedded_process->is_embedding_in_progress()) { + state_label->set_text(TTR("Game starting...")); + } else if (EditorRunBar::get_singleton()->is_playing()) { + state_label->set_text(TTR("Game running not embedded.")); + } else if (embed_on_play) { + state_label->set_text(TTR("Press play to start the game.")); + } else { + state_label->set_text(TTR("Embedding is disabled.")); + } + break; + case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED: + state_label->set_text(TTR("Game embedding not available on your OS.")); + break; + case EMBED_NOT_AVAILABLE_MINIMIZED: + state_label->set_text(TTR("Game embedding not available when the game starts minimized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + case EMBED_NOT_AVAILABLE_MAXIMIZED: + state_label->set_text(TTR("Game embedding not available when the game starts maximized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + case EMBED_NOT_AVAILABLE_FULLSCREEN: + state_label->set_text(TTR("Game embedding not available when the game starts in fullscreen.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact.")); + break; + case EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE: + state_label->set_text(TTR("Game embedding not available in single window mode.")); + break; + } + + if (available == EMBED_AVAILABLE) { + if (state_label->has_theme_color_override(SceneStringName(font_color))) { + state_label->remove_theme_color_override(SceneStringName(font_color)); + } } else { - state_label->set_text(TTR("Embedding is disabled.")); + state_label->add_theme_color_override(SceneStringName(font_color), state_label->get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); } game_size_label->set_visible(show_game_size); @@ -383,22 +509,34 @@ void GameView::_update_embed_menu_options() { menu->set_item_checked(menu->get_item_index(EMBED_RUN_GAME_EMBEDDED), embed_on_play); menu->set_item_checked(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), make_floating_on_play); - // When embed is Off or in single window mode, Make floating is not available. - menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled()); + menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play); + + fixed_size_button->set_pressed(embed_size_mode == SIZE_MODE_FIXED); + keep_aspect_button->set_pressed(embed_size_mode == SIZE_MODE_KEEP_ASPECT); + stretch_button->set_pressed(embed_size_mode == SIZE_MODE_STRETCH); } void GameView::_update_embed_window_size() { - Size2 window_size; - window_size.x = GLOBAL_GET("display/window/size/viewport_width"); - window_size.y = GLOBAL_GET("display/window/size/viewport_height"); + if (paused) { + // When paused, Godot does not re-render. As a result, resizing the game window to a larger size + // causes artifacts and flickering. However, resizing to a smaller size seems fine. + // To prevent artifacts and flickering, we will force the game window to maintain its size. + // Using the same technique as SIZE_MODE_FIXED, the embedded process control will + // prevent resizing the game to a larger size while maintaining the aspect ratio. + embedded_process->set_window_size(size_paused); + embedded_process->set_keep_aspect(false); - Size2 desired_size; - desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); - desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); - if (desired_size.x > 0 && desired_size.y > 0) { - window_size = desired_size; + } else { + if (embed_size_mode == SIZE_MODE_FIXED || embed_size_mode == SIZE_MODE_KEEP_ASPECT) { + // The embedded process control will need the desired window size. + EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); + embedded_process->set_window_size(placement.size); + } else { + // Stretch... No need for the window size. + embedded_process->set_window_size(Size2i()); + } + embedded_process->set_keep_aspect(embed_size_mode == SIZE_MODE_KEEP_ASPECT); } - embedded_process->set_window_size(window_size); } void GameView::_hide_selection_toggled(bool p_pressed) { @@ -459,7 +597,9 @@ void GameView::_notification(int p_what) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect"))); hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); + fixed_size_button->set_button_icon(get_editor_theme_icon(SNAME("FixedSize"))); keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect"))); + stretch_button->set_button_icon(get_editor_theme_icon(SNAME("Stretch"))); embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera"))); @@ -469,8 +609,26 @@ void GameView::_notification(int p_what) { case NOTIFICATION_READY: { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { // Embedding available. - embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); - make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); + int game_mode = EDITOR_GET("run/window_placement/game_embed_mode"); + switch (game_mode) { + case 1: { // Embed. + embed_on_play = true; + make_floating_on_play = false; + } break; + case 2: { // Floating. + embed_on_play = true; + make_floating_on_play = true; + } break; + case 3: { // Disabled. + embed_on_play = false; + make_floating_on_play = false; + } break; + default: { + embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); + make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); + } break; + } + embed_size_mode = (EmbedSizeMode)(int)EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_size_mode", SIZE_MODE_FIXED); keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true)); _update_embed_menu_options(); @@ -479,15 +637,15 @@ void GameView::_notification(int p_what) { EditorRun::instance_starting_callback = _instance_starting_static; // Listen for project settings changes to update the window size and aspect ratio. - ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_project_settings_changed)); - - embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed)); + EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed)); } else { // Embedding not available. embedding_separator->hide(); embed_options_menu->hide(); + fixed_size_button->hide(); keep_aspect_button->hide(); - keep_aspect_button->hide(); + stretch_button->hide(); } _update_ui(); @@ -542,7 +700,6 @@ Dictionary GameView::get_state() const { void GameView::set_window_layout(Ref p_layout) { floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i()); floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1); - floating_window_screen_rect = p_layout->get_value("GameView", "floating_window_screen_rect", Rect2i()); } void GameView::get_window_layout(Ref p_layout) { @@ -552,24 +709,53 @@ void GameView::get_window_layout(Ref p_layout) { p_layout->set_value("GameView", "floating_window_rect", floating_window_rect); p_layout->set_value("GameView", "floating_window_screen", floating_window_screen); - p_layout->set_value("GameView", "floating_window_screen_rect", floating_window_screen_rect); } void GameView::_update_floating_window_settings() { if (window_wrapper->get_window_enabled()) { floating_window_rect = window_wrapper->get_window_rect(); floating_window_screen = window_wrapper->get_window_screen(); - floating_window_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(floating_window_screen); } } +void GameView::_attach_script_debugger() { + if (embedded_script_debugger) { + _detach_script_debugger(); + } + + embedded_script_debugger = nullptr; + for (int i = 0; EditorDebuggerNode::get_singleton()->get_debugger(i); i++) { + ScriptEditorDebugger *script_debugger = EditorDebuggerNode::get_singleton()->get_debugger(i); + if (script_debugger->is_session_active() && script_debugger->get_remote_pid() == embedded_process->get_embedded_pid()) { + embedded_script_debugger = script_debugger; + break; + } + } + + if (embedded_script_debugger) { + embedded_script_debugger->connect("remote_window_title_changed", callable_mp(this, &GameView::_remote_window_title_changed)); + } +} + +void GameView::_detach_script_debugger() { + if (embedded_script_debugger) { + embedded_script_debugger->disconnect("remote_window_title_changed", callable_mp(this, &GameView::_remote_window_title_changed)); + embedded_script_debugger = nullptr; + } +} + +void GameView::_remote_window_title_changed(String title) { + window_wrapper->set_window_title(title); +} + void GameView::_update_arguments_for_instance(int p_idx, List &r_arguments) { - if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + if (p_idx != 0 || !embed_on_play || _get_embed_available() != EMBED_AVAILABLE) { return; } // Remove duplicates/unwanted parameters. List::Element *E = r_arguments.front(); + List::Element *user_args_element = nullptr; while (E) { List::Element *N = E->next(); @@ -583,34 +769,58 @@ void GameView::_update_arguments_for_instance(int p_idx, List &r_argumen } } else if (E->get() == "-f" || E->get() == "--fullscreen" || E->get() == "-m" || E->get() == "--maximized" || E->get() == "-t" || E->get() == "-always-on-top") { r_arguments.erase(E); + } else if (E->get() == "--" || E->get() == "++") { + user_args_element = E; + break; } E = N; } // Add the editor window's native ID so the started game can directly set it as its parent. - r_arguments.push_back("--wid"); - r_arguments.push_back(itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id()))); + List::Element *N = r_arguments.insert_before(user_args_element, "--wid"); + N = r_arguments.insert_after(N, itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id()))); // Be sure to have the correct window size in the embedded_process control. _update_embed_window_size(); Rect2i rect = embedded_process->get_screen_embedded_window_rect(); - r_arguments.push_back("--position"); - r_arguments.push_back(itos(rect.position.x) + "," + itos(rect.position.y)); - r_arguments.push_back("--resolution"); - r_arguments.push_back(itos(rect.size.x) + "x" + itos(rect.size.y)); + N = r_arguments.insert_after(N, "--position"); + N = r_arguments.insert_after(N, itos(rect.position.x) + "," + itos(rect.position.y)); + N = r_arguments.insert_after(N, "--resolution"); + r_arguments.insert_after(N, itos(rect.size.x) + "x" + itos(rect.size.y)); } -void GameView::_window_before_closing() { +void GameView::_window_close_request() { // Before the parent window closed, we close the embedded game. That prevents // the embedded game to be seen without a parent window for a fraction of second. if (EditorRunBar::get_singleton()->is_playing() && (embedded_process->is_embedding_completed() || embedded_process->is_embedding_in_progress())) { + // Try to gracefully close the window. That way, the NOTIFICATION_WM_CLOSE_REQUEST + // notification should be propagated in the game process. embedded_process->reset(); - // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window - // actually closes. - callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred(); + + // When the embedding is not complete, we need to kill the process. + // If the game is paused, the close request will not be processed by the game, so it's better to kill the process. + if (paused || embedded_process->is_embedding_in_progress()) { + // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window + // actually closes. + callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred(); + } + } +} + +void GameView::_debugger_breaked(bool p_breaked, bool p_can_debug) { + if (p_breaked == paused) { + return; } + + paused = p_breaked; + + if (paused) { + size_paused = embedded_process->get_screen_embedded_window_rect().size; + } + + _update_embed_window_size(); } GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { @@ -728,12 +938,26 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { embedding_separator = memnew(VSeparator); main_menu_hbox->add_child(embedding_separator); + fixed_size_button = memnew(Button); + main_menu_hbox->add_child(fixed_size_button); + fixed_size_button->set_toggle_mode(true); + fixed_size_button->set_theme_type_variation("FlatButton"); + fixed_size_button->set_tooltip_text(TTR("Embedded game size is based on project settings.\nThe 'Keep Aspect' mode is used when the Game Workspace is smaller than the desired size.")); + fixed_size_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_FIXED)); + keep_aspect_button = memnew(Button); main_menu_hbox->add_child(keep_aspect_button); keep_aspect_button->set_toggle_mode(true); keep_aspect_button->set_theme_type_variation("FlatButton"); keep_aspect_button->set_tooltip_text(TTR("Keep the aspect ratio of the embedded game.")); - keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed)); + keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_KEEP_ASPECT)); + + stretch_button = memnew(Button); + main_menu_hbox->add_child(stretch_button); + stretch_button->set_toggle_mode(true); + stretch_button->set_theme_type_variation("FlatButton"); + stretch_button->set_tooltip_text(TTR("Embedded game size stretches to fit the Game Workspace.")); + stretch_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_STRETCH)); embed_options_menu = memnew(MenuButton); main_menu_hbox->add_child(embed_options_menu); @@ -767,8 +991,14 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { embedded_process->connect("embedded_process_focused", callable_mp(this, &GameView::_embedded_process_focused)); embedded_process->set_custom_minimum_size(Size2i(100, 100)); + MarginContainer *state_container = memnew(MarginContainer); + state_container->add_theme_constant_override("margin_left", 8 * EDSCALE); + state_container->add_theme_constant_override("margin_right", 8 * EDSCALE); + state_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + panel->add_child(state_container); + state_label = memnew(Label()); - panel->add_child(state_label); + state_container->add_child(state_label); state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); @@ -779,8 +1009,11 @@ GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed)); p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed)); - p_wrapper->connect("window_before_closing", callable_mp(this, &GameView::_window_before_closing)); + p_wrapper->set_override_close_request(true); + p_wrapper->connect("window_close_requested", callable_mp(this, &GameView::_window_close_request)); p_wrapper->connect("window_size_changed", callable_mp(this, &GameView::_update_floating_window_settings)); + + EditorDebuggerNode::get_singleton()->connect("breaked", callable_mp(this, &GameView::_debugger_breaked)); } /////// @@ -850,15 +1083,18 @@ void GameViewPlugin::_window_visibility_changed(bool p_visible) { } void GameViewPlugin::_save_last_editor(const String &p_editor) { - if (p_editor != get_name()) { + if (p_editor != get_plugin_name()) { last_editor = p_editor; } } void GameViewPlugin::_focus_another_editor() { if (window_wrapper->get_window_enabled()) { - ERR_FAIL_COND(last_editor.is_empty()); - EditorInterface::get_singleton()->set_main_screen_editor(last_editor); + if (last_editor.is_empty()) { + EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_2D); + } else { + EditorInterface::get_singleton()->set_main_screen_editor(last_editor); + } } } diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index 50d4170e92d3..dd384280b5fb 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -40,6 +40,7 @@ class EmbeddedProcess; class VSeparator; class WindowWrapper; +class ScriptEditorDebugger; class GameViewDebugger : public EditorDebuggerPlugin { GDCLASS(GameViewDebugger, EditorDebuggerPlugin); @@ -93,6 +94,21 @@ class GameView : public VBoxContainer { EMBED_MAKE_FLOATING_ON_PLAY, }; + enum EmbedSizeMode { + SIZE_MODE_FIXED, + SIZE_MODE_KEEP_ASPECT, + SIZE_MODE_STRETCH, + }; + + enum EmbedAvailability { + EMBED_AVAILABLE, + EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED, + EMBED_NOT_AVAILABLE_MINIMIZED, + EMBED_NOT_AVAILABLE_MAXIMIZED, + EMBED_NOT_AVAILABLE_FULLSCREEN, + EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE, + }; + inline static GameView *singleton = nullptr; Ref debugger; @@ -101,13 +117,16 @@ class GameView : public VBoxContainer { bool is_feature_enabled = true; int active_sessions = 0; int screen_index_before_start = -1; + ScriptEditorDebugger *embedded_script_debugger = nullptr; bool embed_on_play = true; bool make_floating_on_play = true; + EmbedSizeMode embed_size_mode = SIZE_MODE_FIXED; + bool paused = false; + Size2 size_paused; Rect2i floating_window_rect; int floating_window_screen = -1; - Rect2i floating_window_screen_rect; Button *suspend_button = nullptr; Button *next_frame_button = nullptr; @@ -121,10 +140,11 @@ class GameView : public VBoxContainer { MenuButton *camera_override_menu = nullptr; VSeparator *embedding_separator = nullptr; + Button *fixed_size_button = nullptr; Button *keep_aspect_button = nullptr; + Button *stretch_button = nullptr; MenuButton *embed_options_menu = nullptr; Label *game_size_label = nullptr; - Panel *panel = nullptr; EmbeddedProcess *embedded_process = nullptr; Label *state_label = nullptr; @@ -138,7 +158,7 @@ class GameView : public VBoxContainer { void _node_type_pressed(int p_option); void _select_mode_pressed(int p_option); void _embed_options_menu_menu_id_pressed(int p_id); - void _keep_aspect_button_pressed(); + void _size_mode_button_pressed(int size_mode); void _play_pressed(); static void _instance_starting_static(int p_idx, List &r_arguments); @@ -148,20 +168,27 @@ class GameView : public VBoxContainer { void _embedding_failed(); void _embedded_process_updated(); void _embedded_process_focused(); - void _project_settings_changed(); + void _editor_or_project_settings_changed(); + EmbedAvailability _get_embed_available(); void _update_ui(); void _update_embed_menu_options(); void _update_embed_window_size(); void _update_arguments_for_instance(int p_idx, List &r_arguments); + void _show_update_window_wrapper(); void _hide_selection_toggled(bool p_pressed); void _camera_override_button_toggled(bool p_pressed); void _camera_override_menu_id_pressed(int p_id); - void _window_before_closing(); + void _window_close_request(); void _update_floating_window_settings(); + void _attach_script_debugger(); + void _detach_script_debugger(); + void _remote_window_title_changed(String title); + + void _debugger_breaked(bool p_breaked, bool p_can_debug); protected: void _notification(int p_what); diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index 9189441c2bae..812cc36bf66c 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -116,9 +116,9 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p libs_added.insert(library_path); add_shared_object(library_path, tags); - if (p_features.has("ios") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + if ((p_features.has("iOS") || p_features.has("tvOS")) && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" + "extern void add_$PLATFORM_init_callback(void (*cb)());\n" "\n" "extern \"C\" void $ENTRY();\n" "void $ENTRY_init() {\n" @@ -126,15 +126,22 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p "}\n" "struct $ENTRY_struct {\n" " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" + " add_$PLATFORM_init_callback($ENTRY_init);\n" " }\n" "};\n" "$ENTRY_struct $ENTRY_struct_instance;\n\n"; additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); + + if (p_features.has("iOS")) { + additional_code = additional_code.replace("$PLATFORM", "ios"); + add_ios_cpp_code(additional_code); + add_ios_linker_flags(linker_flags); + } else if (p_features.has("tvOS")) { + additional_code = additional_code.replace("$PLATFORM", "tvos"); + add_tvos_cpp_code(additional_code); + add_tvos_linker_flags(linker_flags); + } } // Update found library info. diff --git a/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.cpp index 4f9b93c91213..2a5dd571c061 100644 --- a/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.cpp @@ -30,14 +30,44 @@ #include "collision_polygon_3d_gizmo_plugin.h" +#include "core/math/geometry_2d.h" #include "scene/3d/physics/collision_polygon_3d.h" CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() { - const Color gizmo_color = SceneTree::get_singleton()->get_debug_collisions_color(); - create_material("shape_material", gizmo_color); - const float gizmo_value = gizmo_color.get_v(); - const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); - create_material("shape_material_disabled", gizmo_color_disabled); + create_collision_material("shape_material", 2.0); + create_collision_material("shape_material_arraymesh", 0.0625); + + create_collision_material("shape_material_disabled", 0.0625); + create_collision_material("shape_material_arraymesh_disabled", 0.015625); +} + +void CollisionPolygon3DGizmoPlugin::create_collision_material(const String &p_name, float p_alpha) { + Vector> mats; + + const Color collision_color(1.0, 1.0, 1.0, p_alpha); + + for (int i = 0; i < 4; i++) { + bool instantiated = i < 2; + + Ref material; + material.instantiate(); + + Color color = collision_color; + color.a *= instantiated ? 0.25 : 1.0; + + material->set_albedo(color); + material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); + material->set_cull_mode(StandardMaterial3D::CULL_BACK); + material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + mats.push_back(material); + } + + materials[p_name] = mats; } bool CollisionPolygon3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -57,6 +87,13 @@ void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->clear(); + const Ref material = + get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); + const Ref material_arraymesh = + get_material(!polygon->is_disabled() ? "shape_material_arraymesh" : "shape_material_arraymesh_disabled", p_gizmo); + + const Color collision_color = polygon->is_disabled() ? Color(1.0, 1.0, 1.0, 0.75) : polygon->get_debug_color(); + Vector points = polygon->get_polygon(); float depth = polygon->get_depth() * 0.5; @@ -71,9 +108,125 @@ void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { lines.push_back(Vector3(points[i].x, points[i].y, -depth)); } - const Ref material = - get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo); + if (polygon->get_debug_fill_enabled()) { + Ref array_mesh; + array_mesh.instantiate(); + + Vector verts; + Vector colors; + Vector indices; + + // Determine orientation of the 2D polygon's vertices to determine + // which direction to draw outer polygons. + float signed_area = 0.0f; + for (int i = 0; i < points.size(); i++) { + const int j = (i + 1) % points.size(); + signed_area += points[i].x * points[j].y - points[j].x * points[i].y; + } + + // Generate triangles for the sides of the extruded polygon. + for (int i = 0; i < points.size(); i++) { + verts.push_back(Vector3(points[i].x, points[i].y, depth)); + verts.push_back(Vector3(points[i].x, points[i].y, -depth)); + + colors.push_back(collision_color); + colors.push_back(collision_color); + } + + for (int i = 0; i < verts.size(); i += 2) { + const int j = (i + 1) % verts.size(); + const int k = (i + 2) % verts.size(); + const int l = (i + 3) % verts.size(); + + indices.push_back(i); + if (signed_area < 0) { + indices.push_back(j); + indices.push_back(k); + } else { + indices.push_back(k); + indices.push_back(j); + } + + indices.push_back(j); + if (signed_area < 0) { + indices.push_back(l); + indices.push_back(k); + } else { + indices.push_back(k); + indices.push_back(l); + } + } + + Vector> decomp = Geometry2D::decompose_polygon_in_convex(polygon->get_polygon()); + + // Generate triangles for the bottom cap of the extruded polygon. + for (int i = 0; i < decomp.size(); i++) { + Vector cap_verts_bottom; + Vector cap_colours_bottom; + Vector cap_indices_bottom; + + const int index_offset = verts.size(); + + const Vector &convex = decomp[i]; + + for (int j = 0; j < convex.size(); j++) { + cap_verts_bottom.push_back(Vector3(convex[j].x, convex[j].y, -depth)); + cap_colours_bottom.push_back(collision_color); + } + + if (convex.size() >= 3) { + for (int j = 1; j < convex.size(); j++) { + const int k = (j + 1) % convex.size(); + + cap_indices_bottom.push_back(index_offset + 0); + cap_indices_bottom.push_back(index_offset + j); + cap_indices_bottom.push_back(index_offset + k); + } + } + verts.append_array(cap_verts_bottom); + colors.append_array(cap_colours_bottom); + indices.append_array(cap_indices_bottom); + } + + // Generate triangles for the top cap of the extruded polygon. + for (int i = 0; i < decomp.size(); i++) { + Vector cap_verts_top; + Vector cap_colours_top; + Vector cap_indices_top; + + const int index_offset = verts.size(); + + const Vector &convex = decomp[i]; + + for (int j = 0; j < convex.size(); j++) { + cap_verts_top.push_back(Vector3(convex[j].x, convex[j].y, depth)); + cap_colours_top.push_back(collision_color); + } + + if (convex.size() >= 3) { + for (int j = 1; j < convex.size(); j++) { + const int k = (j + 1) % convex.size(); + + cap_indices_top.push_back(index_offset + k); + cap_indices_top.push_back(index_offset + j); + cap_indices_top.push_back(index_offset + 0); + } + } + verts.append_array(cap_verts_top); + colors.append_array(cap_colours_top); + indices.append_array(cap_indices_top); + } + + Array a; + a.resize(Mesh::ARRAY_MAX); + a[RS::ARRAY_VERTEX] = verts; + a[RS::ARRAY_COLOR] = colors; + a[RS::ARRAY_INDEX] = indices; + array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); + + p_gizmo->add_mesh(array_mesh, material_arraymesh); + } - p_gizmo->add_lines(lines, material); + p_gizmo->add_lines(lines, material, false, collision_color); p_gizmo->add_collision_segments(lines); } diff --git a/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.h b/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.h index b8342a25ca5e..6c78a862cfc6 100644 --- a/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.h +++ b/editor/plugins/gizmos/collision_polygon_3d_gizmo_plugin.h @@ -36,6 +36,8 @@ class CollisionPolygon3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(CollisionPolygon3DGizmoPlugin, EditorNode3DGizmoPlugin); + void create_collision_material(const String &p_name, float p_alpha); + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp index 6db68f413ebd..db9a9bef04b2 100644 --- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp @@ -350,7 +350,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (cs->get_debug_fill_enabled()) { Ref array_mesh = s->get_debug_arraymesh_faces(collision_color); - if (array_mesh.is_valid()) { + if (array_mesh.is_valid() && array_mesh->get_surface_count() > 0) { p_gizmo->add_mesh(array_mesh, material_arraymesh); } } diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 61f7dd538385..c7513314017a 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -195,7 +195,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin() { #else // Disable lightmap baking if the module is disabled at compile-time. bake->set_disabled(true); -#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) +#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED) || defined(TVOS_ENABLED) bake->set_tooltip_text(vformat(TTR("Lightmaps cannot be baked on %s."), OS::get_singleton()->get_name())); #else bake->set_tooltip_text(TTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time.")); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 77dd61b26346..1a1e5697c2d2 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1170,11 +1170,10 @@ void ScriptEditor::_live_auto_reload_running_scripts() { bool ScriptEditor::_test_script_times_on_disk(Ref p_for_script) { disk_changed_list->clear(); TreeItem *r = disk_changed_list->create_item(); - disk_changed_list->set_hide_root(true); bool need_ask = false; bool need_reload = false; - bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); + bool use_autoreload = EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"); for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to(tab_container->get_tab_control(i)); @@ -1188,12 +1187,12 @@ bool ScriptEditor::_test_script_times_on_disk(Ref p_for_script) { continue; //internal script, who cares } - uint64_t last_date = edited_res->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); + uint64_t last_date = se->edited_file_data.last_modified_time; + uint64_t date = FileAccess::get_modified_time(se->edited_file_data.path); if (last_date != date) { TreeItem *ti = disk_changed_list->create_item(r); - ti->set_text(0, edited_res->get_path().get_file()); + ti->set_text(0, se->edited_file_data.path.get_file()); if (!use_autoreload || se->is_unsaved()) { need_ask = true; @@ -2231,11 +2230,6 @@ void ScriptEditor::_update_script_names() { Ref icon = se->get_theme_icon(); String path = se->get_edited_resource()->get_path(); bool saved = !path.is_empty(); - if (saved) { - // The script might be deleted, moved, or renamed, so make sure - // to update original path to previously edited resource. - se->set_meta("_edit_res_path", path); - } String name = se->get_name(); Ref