summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml2
-rw-r--r--.github/workflows/linux_builds.yml2
-rw-r--r--CONTRIBUTING.md192
-rw-r--r--COPYRIGHT.txt4
-rw-r--r--LOGO_LICENSE.md5
-rw-r--r--LOGO_LICENSE.txt5
-rw-r--r--README.md20
-rw-r--r--core/core_bind.cpp3
-rw-r--r--core/extension/gdextension.cpp26
-rw-r--r--core/io/file_access.h4
-rw-r--r--core/io/file_access_zip.cpp20
-rw-r--r--core/math/convex_hull.cpp2
-rw-r--r--core/object/callable_method_pointer.h6
-rw-r--r--core/object/message_queue.cpp4
-rw-r--r--core/object/object.cpp33
-rw-r--r--core/object/script_language.h1
-rw-r--r--core/object/script_language_extension.cpp1
-rw-r--r--core/object/script_language_extension.h10
-rw-r--r--core/object/undo_redo.cpp22
-rw-r--r--core/templates/paged_allocator.h2
-rw-r--r--core/templates/safe_refcount.h6
-rw-r--r--core/variant/callable.cpp7
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--core/variant/variant_internal.h2
-rw-r--r--doc/classes/EditorSettings.xml7
-rw-r--r--doc/classes/FileAccess.xml2
-rw-r--r--doc/classes/GPUParticles2D.xml8
-rw-r--r--doc/classes/GPUParticles3D.xml8
-rw-r--r--doc/classes/Input.xml2
-rw-r--r--doc/classes/LightmapGI.xml2
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/ParticleProcessMaterial.xml83
-rw-r--r--doc/classes/ProjectSettings.xml21
-rw-r--r--doc/classes/RefCounted.xml2
-rw-r--r--doc/classes/RenderingServer.xml24
-rw-r--r--doc/classes/ScriptLanguageExtension.xml5
-rw-r--r--doc/classes/Viewport.xml5
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp6
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h6
-rw-r--r--drivers/gles3/shaders/particles.glsl5
-rw-r--r--drivers/gles3/storage/light_storage.cpp6
-rw-r--r--drivers/gles3/storage/material_storage.cpp5
-rw-r--r--drivers/gles3/storage/particles_storage.cpp31
-rw-r--r--drivers/gles3/storage/particles_storage.h10
-rw-r--r--drivers/gles3/storage/utilities.cpp2
-rw-r--r--drivers/unix/dir_access_unix.cpp6
-rw-r--r--drivers/unix/os_unix.cpp6
-rw-r--r--drivers/vulkan/vulkan_context.cpp4
-rw-r--r--editor/animation_track_editor.cpp27
-rw-r--r--editor/code_editor.cpp3
-rw-r--r--editor/code_editor.h1
-rw-r--r--editor/connections_dialog.cpp1
-rw-r--r--editor/connections_dialog.h3
-rw-r--r--editor/debugger/script_editor_debugger.cpp2
-rw-r--r--editor/editor_file_system.cpp8
-rw-r--r--editor/editor_help.cpp18
-rw-r--r--editor/editor_inspector.cpp29
-rw-r--r--editor/editor_inspector.h3
-rw-r--r--editor/editor_node.cpp63
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/editor_plugin.cpp4
-rw-r--r--editor/editor_plugin.h1
-rw-r--r--editor/editor_run.cpp6
-rw-r--r--editor/editor_settings.cpp4
-rw-r--r--editor/editor_themes.cpp4
-rw-r--r--editor/export/export_template_manager.cpp2
-rw-r--r--editor/filesystem_dock.cpp30
-rw-r--r--editor/filesystem_dock.h2
-rw-r--r--editor/find_in_files.cpp2
-rw-r--r--editor/gui/editor_toaster.cpp2
-rw-r--r--editor/gui/editor_zoom_widget.cpp45
-rw-r--r--editor/gui/editor_zoom_widget.h7
-rw-r--r--editor/import/collada.cpp2
-rw-r--r--editor/import/post_import_plugin_skeleton_rest_fixer.cpp1
-rw-r--r--editor/import/resource_importer_shader_file.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp14
-rw-r--r--editor/plugins/debugger_editor_plugin.cpp14
-rw-r--r--editor/plugins/debugger_editor_plugin.h1
-rw-r--r--editor/plugins/editor_preview_plugins.cpp11
-rw-r--r--editor/plugins/script_editor_plugin.cpp10
-rw-r--r--editor/plugins/script_text_editor.cpp17
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp114
-rw-r--r--editor/plugins/theme_editor_plugin.h15
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp2
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp5
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp2
-rw-r--r--editor/project_manager.cpp2
-rw-r--r--editor/scene_tree_dock.cpp18
-rw-r--r--main/main.cpp18
-rw-r--r--misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj16
-rw-r--r--misc/extension_api_validation/4.1-stable.expected6
-rw-r--r--modules/enet/enet_connection.cpp4
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp3
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp16
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp77
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h9
-rw-r--r--modules/gdscript/gdscript_codegen.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp20
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp82
-rw-r--r--modules/gdscript/gdscript_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_function.h42
-rw-r--r--modules/gdscript/gdscript_parser.cpp6
-rw-r--r--modules/gdscript/gdscript_vm.cpp472
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd6
-rw-r--r--modules/gltf/gltf_document.cpp2
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp156
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h4
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl8
-rw-r--r--modules/lightmapper_rd/register_types.cpp2
-rw-r--r--modules/mono/config.py2
-rw-r--r--modules/mono/csharp_script.cpp5
-rw-r--r--modules/mono/csharp_script.h1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props8
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets58
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs7
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs78
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs290
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs1
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Compat.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs61
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs34
-rw-r--r--modules/mono/managed_callable.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp26
-rw-r--r--modules/mono/mono_gd/support/ios_support.h50
-rw-r--r--modules/mono/mono_gd/support/ios_support.mm150
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp4
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.cpp4
-rw-r--r--modules/multiplayer/multiplayer_synchronizer.h1
-rw-r--r--modules/multiplayer/scene_cache_interface.cpp52
-rw-r--r--modules/multiplayer/scene_cache_interface.h6
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp8
-rw-r--r--modules/multiplayer/scene_multiplayer.h3
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp44
-rw-r--r--modules/multiplayer/scene_replication_interface.h6
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp48
-rw-r--r--modules/multiplayer/scene_rpc_interface.h11
-rw-r--r--modules/openxr/util.h4
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--modules/webrtc/library_godot_webrtc.js20
-rw-r--r--modules/websocket/library_godot_websocket.js5
-rw-r--r--platform/android/export/export_plugin.cpp13
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt28
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java12
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotHost.java27
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotService.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java24
-rw-r--r--platform/ios/display_server_ios.mm14
-rw-r--r--platform/ios/export/export_plugin.cpp13
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp2
-rw-r--r--platform/macos/display_server_macos.h1
-rw-r--r--platform/macos/display_server_macos.mm22
-rw-r--r--platform/macos/gl_manager_macos_legacy.mm4
-rw-r--r--platform/macos/godot_window_delegate.mm9
-rw-r--r--platform/macos/os_macos.mm2
-rw-r--r--platform/web/detect.py19
-rw-r--r--platform/web/display_server_web.cpp228
-rw-r--r--platform/web/display_server_web.h31
-rw-r--r--platform/web/godot_js.h3
-rw-r--r--platform/web/godot_webgl2.h1
-rw-r--r--platform/web/javascript_bridge_singleton.cpp25
-rw-r--r--platform/web/js/engine/engine.js4
-rw-r--r--platform/web/js/libs/library_godot_audio.js9
-rw-r--r--platform/web/js/libs/library_godot_display.js40
-rw-r--r--platform/web/js/libs/library_godot_fetch.js7
-rw-r--r--platform/web/js/libs/library_godot_input.js12
-rw-r--r--platform/web/js/libs/library_godot_javascript_singleton.js10
-rw-r--r--platform/web/js/libs/library_godot_os.js13
-rw-r--r--platform/web/js/libs/library_godot_webgl2.js13
-rw-r--r--platform/web/os_web.cpp6
-rw-r--r--platform/web/os_web.h3
-rw-r--r--platform/web/web_main.cpp10
-rw-r--r--platform/windows/joypad_windows.cpp4
-rw-r--r--scene/2d/gpu_particles_2d.cpp34
-rw-r--r--scene/2d/gpu_particles_2d.h8
-rw-r--r--scene/2d/tile_map.cpp16
-rw-r--r--scene/3d/gpu_particles_3d.cpp43
-rw-r--r--scene/3d/gpu_particles_3d.h9
-rw-r--r--scene/3d/voxelizer.cpp2
-rw-r--r--scene/animation/animation_mixer.cpp21
-rw-r--r--scene/animation/animation_player.cpp4
-rw-r--r--scene/animation/tween.cpp4
-rw-r--r--scene/gui/code_edit.cpp15
-rw-r--r--scene/gui/file_dialog.cpp2
-rw-r--r--scene/gui/graph_element.cpp2
-rw-r--r--scene/gui/line_edit.cpp1
-rw-r--r--scene/gui/popup_menu.cpp18
-rw-r--r--scene/gui/rich_text_label.cpp5
-rw-r--r--scene/gui/text_edit.cpp3
-rw-r--r--scene/gui/tree.cpp26
-rw-r--r--scene/main/node.cpp6
-rw-r--r--scene/main/node.h2
-rw-r--r--scene/main/viewport.cpp17
-rw-r--r--scene/main/viewport.h4
-rw-r--r--scene/main/window.cpp2
-rw-r--r--scene/resources/animation.cpp12
-rw-r--r--scene/resources/curve.cpp2
-rw-r--r--scene/resources/immediate_mesh.cpp2
-rw-r--r--scene/resources/material.cpp2
-rw-r--r--scene/resources/material.h1
-rw-r--r--scene/resources/particle_process_material.cpp1218
-rw-r--r--scene/resources/particle_process_material.h124
-rw-r--r--scene/resources/primitive_meshes.cpp6
-rw-r--r--scene/resources/skeleton_modification_2d_physicalbones.cpp2
-rw-r--r--scene/resources/text_line.cpp6
-rw-r--r--scene/resources/text_paragraph.cpp9
-rw-r--r--servers/physics_2d/godot_area_2d.cpp16
-rw-r--r--servers/physics_2d/godot_body_2d.cpp10
-rw-r--r--servers/physics_2d/godot_physics_server_2d.cpp2
-rw-r--r--servers/physics_3d/godot_area_3d.cpp14
-rw-r--r--servers/physics_3d/godot_body_3d.cpp10
-rw-r--r--servers/physics_3d/godot_physics_server_3d.cpp14
-rw-r--r--servers/physics_3d/godot_soft_body_3d.cpp2
-rw-r--r--servers/rendering/dummy/rasterizer_canvas_dummy.h2
-rw-r--r--servers/rendering/dummy/storage/particles_storage.h3
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp18
-rw-r--r--servers/rendering/renderer_canvas_cull.h7
-rw-r--r--servers/rendering/renderer_canvas_render.h5
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp49
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h6
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl47
-rw-r--r--servers/rendering/renderer_rd/shaders/particles.glsl4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.cpp30
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.h11
-rw-r--r--servers/rendering/rendering_server_default.h6
-rw-r--r--servers/rendering/shader_language.cpp4
-rw-r--r--servers/rendering/shader_types.cpp6
-rw-r--r--servers/rendering/storage/particles_storage.h3
-rw-r--r--servers/rendering_server.cpp11
-rw-r--r--servers/rendering_server.h6
-rw-r--r--tests/scene/test_code_edit.h38
-rw-r--r--thirdparty/README.md198
-rw-r--r--thirdparty/cvtt/etc_notes.txt27
-rw-r--r--thirdparty/enet/godot.cpp2
-rw-r--r--thirdparty/misc/patches/clipper-exceptions.patch (renamed from thirdparty/misc/clipper-exceptions.patch)0
-rw-r--r--thirdparty/noise/LICENSE (renamed from thirdparty/noise/FastNoise-LICENSE)0
246 files changed, 3939 insertions, 2033 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 0ce3f68abe..449d2159f1 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -59,7 +59,7 @@ body:
- A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`).
- Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field.
- Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.**
- - **Note for C# users:** If your issue is *not* Mono-specific, please upload a minimal reproduction project written in GDScript or VisualScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a Mono setup available.
+ - **Note for C# users:** If your issue is *not* C#-specific, please upload a minimal reproduction project written in GDScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a .NET setup available.
- **If you've been asked by a maintainer to upload a minimal reproduction project, you *must* do so within 7 days.** Otherwise, your bug report will be closed as it'll be considered too difficult to diagnose.
validations:
required: true
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index fc0db1fbd6..5dd0805d9a 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -92,7 +92,7 @@ jobs:
if: ${{ matrix.proj-test }}
run: |
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
- sudo add-apt-repository ppa:kisak/kisak-mesa
+ sudo add-apt-repository ppa:kisak/turtle
sudo apt-get install -qq mesa-vulkan-drivers
- name: Free disk space on runner
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 30934dcf9b..4c76101ebc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,11 +1,17 @@
-# How to contribute efficiently
+# Contributors guidelines
+
+This document summarizes the most important points for people interested in
+contributing to Godot, especially via bug reports or pull requests.
+
+The Godot documentation has a dedicated [Contributing section](https://docs.godotengine.org/en/latest/contributing/ways_to_contribute.html)
+which details these points and more, and is a recommended read.
## Table of contents
- [Reporting bugs](#reporting-bugs)
- [Proposing features or improvements](#proposing-features-or-improvements)
- [Contributing pull requests](#contributing-pull-requests)
-- [Contributing to Godot's translation](#contributing-to-godots-translation)
+- [Contributing to Godot translations](#contributing-to-godot-translations)
- [Communicating with developers](#communicating-with-developers)
## Reporting bugs
@@ -13,16 +19,26 @@
Report bugs [here](https://github.com/godotengine/godot/issues/new?assignees=&labels=&template=bug_report.yml).
Please follow the instructions in the template when you do.
+Notably, please include a Minimal Reproduction Project (MRP), which is a small
+Godot project which reproduces the issue, with no unnecessary files included.
+Be sure to not include the `.godot` folder in the archive to save space.
+
+Make sure that the bug you are experiencing is reproducible in the latest Godot
+releases. You can find an overview of all Godot releases [on the website](https://godotengine.org/download/archive/)
+to confirm whether your current version is the latest one. It's worth testing
+against both the latest stable release and the latest dev snapshot for the next
+Godot release.
+
+If you run into a bug which wasn't present in an earlier Godot version (what we
+call a _regression_), please mention it and clarify which versions you tested
+(both the one(s) working and the one(s) exhibiting the bug).
+
## Proposing features or improvements
-**Since August 2019, the main issue tracker no longer accepts feature proposals.**
-Instead, head to the [Godot Proposals repository](https://github.com/godotengine/godot-proposals)
-and follow the instructions in the README file. High-quality feature proposals
-are more likely to be well-received by the maintainers and community, so do
-your best :)
+**The main issue tracker is for bug reports and does not accept feature proposals.**
-See [this article](https://godotengine.org/article/introducing-godot-proposals-repository)
-for detailed rationale on this change.
+Instead, head to the [Godot Proposals repository](https://github.com/godotengine/godot-proposals)
+and follow the instructions in the README file and issue template.
## Contributing pull requests
@@ -39,10 +55,9 @@ Similar rules can be applied when contributing bug fixes - it's always best to
discuss the implementation in the bug report first if you are not 100% about
what would be the best fix.
-[This blog post](https://godotengine.org/article/will-your-contribution-be-merged-heres-how-tell)
-outlines the process used by core developers when assessing PRs. We strongly
-recommend that you have a look at it to know what's important to take into
-account for a PR to be considered for merging.
+You can refer to the [Pull request review process](https://docs.godotengine.org/en/latest/contributing/workflow/pr_review_guidelines.html)
+for insights into the intended lifecycle of pull requests. This should help you
+ensure that your pull request fulfills the requirements.
In addition to the following tips, also take a look at the
[Engine development guide](https://docs.godotengine.org/en/latest/contributing/development/index.html)
@@ -50,52 +65,15 @@ for an introduction to developing on Godot.
The [Contributing docs](https://docs.godotengine.org/en/latest/contributing/ways_to_contribute.html)
also have important information on the [PR workflow](https://docs.godotengine.org/en/latest/contributing/workflow/pr_workflow.html)
-and the [code style](https://docs.godotengine.org/en/latest/contributing/development/code_style_guidelines.html) we use.
-
-### Document your changes
-
-If your pull request adds methods, properties or signals that are exposed to
-scripting APIs, you **must** update the class reference to document those.
-This is to ensure the documentation coverage doesn't decrease as contributions
-are merged.
-
-[Update documentation XML files](https://docs.godotengine.org/en/latest/contributing/documentation/updating_the_class_reference.html#updating-class-reference-when-working-on-the-engine)
-using your compiled binary, then fill in the descriptions.
-Follow the style guide described in the
-[Writing guidelines](https://docs.godotengine.org/en/latest/contributing/documentation/docs_writing_guidelines.html).
-
-If your pull request modifies parts of the code in a non-obvious way, make sure
-to add comments in the code as well. This helps other people understand the
-change without having to look at `git blame`.
-
-### Write unit tests
+(with a helpful guide for Git usage), and our [Code style guidelines](https://docs.godotengine.org/en/latest/contributing/development/code_style_guidelines.html)
+which all contributions need to follow.
-When fixing a bug or contributing a new feature, we recommend including unit
-tests in the same commit as the rest of the pull request. Unit tests are pieces
-of code that compare the output to a predetermined *expected result* to detect
-regressions. Tests are compiled and run on GitHub Actions for every commit and
-pull request.
-
-Pull requests that include tests are more likely to be merged, since we can have
-greater confidence in them not being the target of regressions in the future.
-
-For bugs, the unit tests should cover the functionality that was previously
-broken. If done well, this ensures regressions won't appear in the future
-again. For new features, the unit tests should cover the newly added
-functionality, testing both the "success" and "expected failure" cases if
-applicable.
-
-Feel free to contribute standalone pull requests to add new tests or improve
-existing tests as well.
-
-See [Unit testing](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html)
-for information on writing tests in Godot's C++ codebase.
-
-### Be nice to the Git history
+### Be mindful of your commits
Try to make simple PRs that handle one specific topic. Just like for reporting
issues, it's better to open 3 different PRs that each address a different issue
-than one big PR with three commits.
+than one big PR with three commits. This makes it easier to review, approve, and
+merge the changes independently.
When updating your fork with upstream changes, please use ``git pull --rebase``
to avoid creating "merge commits". Those commits unnecessarily pollute the git
@@ -103,16 +81,15 @@ history when coming from PRs.
Also try to make commits that bring the engine from one stable state to another
stable state, i.e. if your first commit has a bug that you fixed in the second
-commit, try to merge them together before making your pull request (see ``git
-rebase -i`` and relevant help about rebasing or amending commits on the
-Internet).
-
-This [Git style guide](https://github.com/agis-/git-style-guide) has some
-good practices to have in mind.
+commit, try to merge them together before making your pull request. This
+includes fixing build issues or typos, adding documentation, etc.
See our [PR workflow](https://docs.godotengine.org/en/latest/contributing/workflow/pr_workflow.html)
documentation for tips on using Git, amending commits and rebasing branches.
+This [Git style guide](https://github.com/agis-/git-style-guide) also has some
+good practices to have in mind.
+
### Format your commit messages with readability in mind
The way you format your commit messages is quite important to ensure that the
@@ -121,12 +98,25 @@ message is formatted as a short title (first line) and an extended description
(everything after the first line and an empty separation line).
The short title is the most important part, as it is what will appear in the
-`shortlog` changelog (one line per commit, so no description shown) or in the
-GitHub interface unless you click the "expand" button. As the name says, try to
-keep that first line under 72 characters. It should describe what the commit
-does globally, while details would go in the description. Typically, if you
-can't keep the title short because you have too much stuff to mention, it means
-you should probably split your changes in several commits :)
+changelog or in the GitHub interface unless you click the "expand" button.
+Try to keep that first line under 72 characters, but you can go slightly above
+if necessary to keep the sentence clear.
+
+It should be written in English, starting with a capital letter, and usually
+with a verb in imperative form. A typical bugfix would start with "Fix", while
+the addition of a new feature would start with "Add". A prefix can be added to
+specify the engine area affected by the commit. Some examples:
+
+- Add C# iOS support
+- Show doc tooltips when hovering properties in the theme editor
+- Fix GLES3 instanced rendering color and custom data defaults
+- Core: Fix `Object::has_method()` for script static methods
+
+If your commit fixes a reported issue, please include it in the _description_
+of the commit (not in the title) using one of the
+[GitHub closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
+such as "Fixes #1234". This will cause the issue to be closed automatically if
+the PR is merged.
Here's an example of a well-formatted commit message (note how the extended
description is also manually wrapped at 80 chars for readability):
@@ -142,23 +132,59 @@ By fixing the regulation system via an added binding to the internal feature,
this commit now ensures that Godot will not go past the ebullition temperature
of cooking oil under normal atmospheric conditions.
-Fixes #1789, long live the Realm!
+Fixes #1340.
```
**Note:** When using the GitHub online editor or its drag-and-drop
feature, *please* edit the commit title to something meaningful. Commits named
"Update my_file.cpp" won't be accepted.
-## Contributing to Godot's translation
+### Document your changes
+
+If your pull request adds methods, properties or signals that are exposed to
+scripting APIs, you **must** update the class reference to document those.
+This is to ensure the documentation coverage doesn't decrease as contributions
+are merged.
+
+[Update documentation XML files](https://docs.godotengine.org/en/latest/contributing/documentation/updating_the_class_reference.html)
+using your compiled binary, then fill in the descriptions.
+Follow the style guide described in the
+[Documentation writing guidelines](https://docs.godotengine.org/en/latest/contributing/documentation/docs_writing_guidelines.html).
+
+If your pull request modifies parts of the code in a non-obvious way, make sure
+to add comments in the code as well. This helps other people understand the
+change without having to dive into the Git history.
+
+### Write unit tests
+
+When fixing a bug or contributing a new feature, we recommend including unit
+tests in the same commit as the rest of the pull request. Unit tests are pieces
+of code that compare the output to a predetermined *expected result* to detect
+regressions. Tests are compiled and run on GitHub Actions for every commit and
+pull request.
+
+Pull requests that include tests are more likely to be merged, since we can have
+greater confidence in them not being the target of regressions in the future.
+
+For bugs, the unit tests should cover the functionality that was previously
+broken. If done well, this ensures regressions won't appear in the future
+again. For new features, the unit tests should cover the newly added
+functionality, testing both the "success" and "expected failure" cases if
+applicable.
+
+Feel free to contribute standalone pull requests to add new tests or improve
+existing tests as well.
+
+See [Unit testing](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html)
+for information on writing tests in Godot's C++ codebase.
+
+## Contributing to Godot translations
-You can contribute to Godot's translation from the [Hosted
-Weblate](https://hosted.weblate.org/projects/godot-engine/godot), an open
-source and web-based translation platform. Please refer to the [translation
-readme](editor/translations/README.md) for more information.
+You can contribute to Godot translations on [Hosted Weblate](https://hosted.weblate.org/projects/godot-engine/),
+an open source and web-based translation platform.
-You can also help translate [Godot's
-documentation](https://hosted.weblate.org/projects/godot-engine/godot-docs/)
-on Weblate.
+Please refer to our [editor and documentation localization guidelines](https://docs.godotengine.org/en/latest/contributing/documentation/editor_and_docs_localization.html)
+for an overview of the translation resources and what they correspond to.
## Communicating with developers
@@ -171,15 +197,13 @@ or a bug you want to fix), the following channels can be used:
- [Godot Contributors Chat](https://chat.godotengine.org): You will
find most core developers there, so it's the go-to platform for direct chat
- about Godot Engine development. Feel free to start discussing something there
- to get some early feedback before writing up a detailed proposal in a GitHub
- issue.
+ about Godot Engine development. Browse the [Directory](https://chat.godotengine.org/directory/channels)
+ for an overview of public channels focusing on various engine areas which you
+ might be interested in.
- [Bug tracker](https://github.com/godotengine/godot/issues): If there is an
- existing issue about a topic you want to discuss, just add a comment to it -
- many developers watch the repository and will get a notification. You can
- also create a new issue - please keep in mind to create issues only to
- discuss quite specific points about the development, and not general user
- feedback or support requests.
+ existing issue about a topic you want to discuss, you can participate directly.
+ If not, you can open a new issue. Please mind the guidelines outlined above
+ for bug reporting.
- [Feature proposals](https://github.com/godotengine/godot-proposals/issues):
To propose a new feature, we have a dedicated issue tracker for that. Don't
hesitate to start by talking about your idea on the Godot Contributors Chat
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 5d0c41698f..fe046863fc 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -383,7 +383,7 @@ License: Expat
Files: ./thirdparty/noise/FastNoiseLite.h
Comment: FastNoise Lite
Copyright: 2020, Jordan Peck and contributors
-License: MIT
+License: Expat
Files: ./thirdparty/misc/pcg.cpp
./thirdparty/misc/pcg.h
@@ -428,7 +428,7 @@ License: BSD-2-clause
Files: ./thirdparty/msdfgen/
Comment: Multi-channel signed distance field generator
Copyright: 2016-2022, Viktor Chlumsky
-License: MIT
+License: Expat
Files: ./thirdparty/nvapi/nvapi_minimal.h
Comment: Stripped down version of "nvapi.h" from the NVIDIA NVAPI SDK
diff --git a/LOGO_LICENSE.md b/LOGO_LICENSE.md
deleted file mode 100644
index 52ee37fb2f..0000000000
--- a/LOGO_LICENSE.md
+++ /dev/null
@@ -1,5 +0,0 @@
-Godot Engine Logo
-Copyright (c) 2017 Andrea Calabró
-
-This work is licensed under a Creative Commons Attribution 4.0 International
-License (CC-BY-4.0 International) <https://creativecommons.org/licenses/by/4.0/>.
diff --git a/LOGO_LICENSE.txt b/LOGO_LICENSE.txt
new file mode 100644
index 0000000000..9d7c7f00c5
--- /dev/null
+++ b/LOGO_LICENSE.txt
@@ -0,0 +1,5 @@
+Godot Engine Logo
+Copyright (c) 2017 Andrea Calabró
+
+This work is licensed under the Creative Commons Attribution 4.0 International
+license (CC BY 4.0 International): https://creativecommons.org/licenses/by/4.0/
diff --git a/README.md b/README.md
index e1cdae2f0e..46a9b2362c 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,11 @@
**[Godot Engine](https://godotengine.org) is a feature-packed, cross-platform
game engine to create 2D and 3D games from a unified interface.** It provides a
-comprehensive set of [common tools](https://godotengine.org/features), so that users can focus on making games
-without having to reinvent the wheel. Games can be exported with one click to a
-number of platforms, including the major desktop platforms (Linux, macOS,
-Windows), mobile platforms (Android, iOS), as well as Web-based platforms
-and [consoles](https://docs.godotengine.org/en/latest/tutorials/platform/consoles.html).
+comprehensive set of [common tools](https://godotengine.org/features), so that
+users can focus on making games without having to reinvent the wheel. Games can
+be exported with one click to a number of platforms, including the major desktop
+platforms (Linux, macOS, Windows), mobile platforms (Android, iOS), as well as
+Web-based platforms and [consoles](https://docs.godotengine.org/en/latest/tutorials/platform/consoles.html).
## Free, open source and community-driven
@@ -22,13 +22,14 @@ Godot is completely free and open source under the very permissive [MIT license]
No strings attached, no royalties, nothing. The users' games are theirs, down
to the last line of engine code. Godot's development is fully independent and
community-driven, empowering users to help shape their engine to match their
-expectations. It is supported by the [Software Freedom Conservancy](https://sfconservancy.org/)
+expectations. It is supported by the [Godot Foundation](https://godot.foundation/)
not-for-profit.
Before being open sourced in [February 2014](https://github.com/godotengine/godot/commit/0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac),
Godot had been developed by [Juan Linietsky](https://github.com/reduz) and
-[Ariel Manzur](https://github.com/punto-) (both still maintaining the project) for several
-years as an in-house engine, used to publish several work-for-hire titles.
+[Ariel Manzur](https://github.com/punto-) (both still maintaining the project)
+for several years as an in-house engine, used to publish several work-for-hire
+titles.
![Screenshot of a 3D scene in the Godot Engine editor](https://raw.githubusercontent.com/godotengine/godot-design/master/screenshots/editor_tps_demo_1920x1080.jpg)
@@ -37,7 +38,7 @@ years as an in-house engine, used to publish several work-for-hire titles.
### Binary downloads
Official binaries for the Godot editor and the export templates can be found
-[on the homepage](https://godotengine.org/download).
+[on the Godot website](https://godotengine.org/download).
### Compiling from source
@@ -53,6 +54,7 @@ The best way to get in touch with the core engine developers is to join the
[Godot Contributors Chat](https://chat.godotengine.org).
To get started contributing to the project, see the [contributing guide](CONTRIBUTING.md).
+This document also includes guidelines for reporting bugs.
## Documentation and demos
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 05fe393a2f..981d9b0025 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -1211,8 +1211,7 @@ void Thread::_start_func(void *ud) {
Ref<Thread> t = *tud;
memdelete(tud);
- Object *target_instance = t->target_callable.get_object();
- if (!target_instance) {
+ if (!t->target_callable.is_valid()) {
t->running.clear();
ERR_FAIL_MSG(vformat("Could not call function '%s' on previously freed instance to start thread %s.", t->target_callable.get_method(), t->get_id()));
}
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index e295a2b30a..181bf1978f 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -218,36 +218,30 @@ public:
#ifdef TOOLS_ENABLED
ERR_FAIL_COND_MSG(!valid, vformat("Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot.", name));
#endif
- ERR_FAIL_COND_MSG(vararg, "Validated methods don't have ptrcall support. This is most likely an engine bug.");
+ ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have validated call support. This is most likely an engine bug.");
GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance();
if (validated_call_func) {
// This is added here, but it's unlikely to be provided by most extensions.
validated_call_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), (GDExtensionVariantPtr)r_ret);
} else {
-#if 1
- // Slow code-path, but works for the time being.
- Callable::CallError ce;
- call(p_object, p_args, argument_count, ce);
-#else
- // This is broken, because it needs more information to do the calling properly
-
// If not provided, go via ptrcall, which is faster than resorting to regular call.
const void **argptrs = (const void **)alloca(argument_count * sizeof(void *));
for (uint32_t i = 0; i < argument_count; i++) {
argptrs[i] = VariantInternal::get_opaque_pointer(p_args[i]);
}
- bool returns = true;
- void *ret_opaque;
- if (returns) {
- ret_opaque = VariantInternal::get_opaque_pointer(r_ret);
- } else {
- ret_opaque = nullptr; // May be unnecessary as this is ignored, but just in case.
+ void *ret_opaque = nullptr;
+ if (r_ret) {
+ VariantInternal::initialize(r_ret, return_value_info.type);
+ ret_opaque = r_ret->get_type() == Variant::NIL ? r_ret : VariantInternal::get_opaque_pointer(r_ret);
}
ptrcall(p_object, argptrs, ret_opaque);
-#endif
+
+ if (r_ret && r_ret->get_type() == Variant::OBJECT) {
+ VariantInternal::update_object_id(r_ret);
+ }
}
}
@@ -782,7 +776,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) {
level_initialized = int32_t(p_level);
- ERR_FAIL_COND(initialization.initialize == nullptr);
+ ERR_FAIL_NULL(initialization.initialize);
initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level));
}
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 7b9e66bb83..7d346ca2f4 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -223,8 +223,8 @@ public:
static Vector<uint8_t> get_file_as_bytes(const String &p_path, Error *r_error = nullptr);
static String get_file_as_string(const String &p_path, Error *r_error = nullptr);
- static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path); }
- static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path); };
+ static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path, &last_file_open_error); }
+ static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path, &last_file_open_error); }
template <class T>
static void make_default(AccessType p_access) {
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index d085c29728..dd45332412 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -110,7 +110,7 @@ static void godot_free(voidpf opaque, voidpf address) {
} // extern "C"
void ZipArchive::close_handle(unzFile p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "Cannot close a file if none is open.");
+ ERR_FAIL_NULL_MSG(p_file, "Cannot close a file if none is open.");
unzCloseCurrentFile(p_file);
unzClose(p_file);
}
@@ -136,7 +136,7 @@ unzFile ZipArchive::get_file_handle(String p_file) const {
io.free_mem = godot_free;
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
- ERR_FAIL_COND_V_MSG(!pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
+ ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
unzClose(pkg);
@@ -168,7 +168,7 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint6
io.zerror_file = godot_testerror;
unzFile zfile = unzOpen2(p_path.utf8().get_data(), &io);
- ERR_FAIL_COND_V(!zfile, false);
+ ERR_FAIL_NULL_V(zfile, false);
unz_global_info64 gi;
int err = unzGetGlobalInfo64(zfile, &gi);
@@ -241,7 +241,7 @@ Error FileAccessZip::open_internal(const String &p_path, int p_mode_flags) {
ZipArchive *arch = ZipArchive::get_singleton();
ERR_FAIL_NULL_V(arch, FAILED);
zfile = arch->get_file_handle(p_path);
- ERR_FAIL_COND_V(!zfile, FAILED);
+ ERR_FAIL_NULL_V(zfile, FAILED);
int err = unzGetCurrentFileInfo64(zfile, &file_info, nullptr, 0, nullptr, 0, nullptr, 0);
ERR_FAIL_COND_V(err != UNZ_OK, FAILED);
@@ -265,28 +265,28 @@ bool FileAccessZip::is_open() const {
}
void FileAccessZip::seek(uint64_t p_position) {
- ERR_FAIL_COND(!zfile);
+ ERR_FAIL_NULL(zfile);
unzSeekCurrentFile(zfile, p_position);
}
void FileAccessZip::seek_end(int64_t p_position) {
- ERR_FAIL_COND(!zfile);
+ ERR_FAIL_NULL(zfile);
unzSeekCurrentFile(zfile, get_length() + p_position);
}
uint64_t FileAccessZip::get_position() const {
- ERR_FAIL_COND_V(!zfile, 0);
+ ERR_FAIL_NULL_V(zfile, 0);
return unztell(zfile);
}
uint64_t FileAccessZip::get_length() const {
- ERR_FAIL_COND_V(!zfile, 0);
+ ERR_FAIL_NULL_V(zfile, 0);
return file_info.uncompressed_size;
}
bool FileAccessZip::eof_reached() const {
- ERR_FAIL_COND_V(!zfile, true);
+ ERR_FAIL_NULL_V(zfile, true);
return at_eof;
}
@@ -299,7 +299,7 @@ uint8_t FileAccessZip::get_8() const {
uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
- ERR_FAIL_COND_V(!zfile, -1);
+ ERR_FAIL_NULL_V(zfile, -1);
at_eof = unzeof(zfile);
if (at_eof) {
diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp
index f8456ec998..68d995fe67 100644
--- a/core/math/convex_hull.cpp
+++ b/core/math/convex_hull.cpp
@@ -2278,7 +2278,7 @@ Error ConvexHullComputer::convex_hull(const Vector<Vector3> &p_points, Geometry3
uint32_t edges_copied = 0;
for (uint32_t i = 0; i < ch.edges.size(); i++) {
- ERR_CONTINUE(edge_faces[i] == -1); // Sanity check
+ ERR_CONTINUE(edge_faces[i] == -1); // Safety check.
uint32_t a = (&ch.edges[i])->get_source_vertex();
uint32_t b = (&ch.edges[i])->get_target_vertex();
diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h
index 2dbb7e468e..db78b982e4 100644
--- a/core/object/callable_method_pointer.h
+++ b/core/object/callable_method_pointer.h
@@ -99,7 +99,7 @@ public:
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
+ ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
#endif
call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error);
}
@@ -154,7 +154,7 @@ public:
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
+ ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
#endif
call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
}
@@ -209,7 +209,7 @@ public:
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
+ ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
#endif
call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error);
}
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp
index 506f8291eb..de71295ee5 100644
--- a/core/object/message_queue.cpp
+++ b/core/object/message_queue.cpp
@@ -36,7 +36,7 @@
#include "core/object/script_language.h"
#ifdef DEV_ENABLED
-// Includes sanity checks to ensure that a queue set as a thread singleton override
+// Includes safety checks to ensure that a queue set as a thread singleton override
// is only ever called from the thread it was set for.
#define LOCK_MUTEX \
if (this != MessageQueue::thread_singleton) { \
@@ -537,7 +537,7 @@ CallQueue::~CallQueue() {
if (!allocator_is_custom) {
memdelete(allocator);
}
- // This is done here to avoid a circular dependency between the sanity checks and the thread singleton pointer.
+ // This is done here to avoid a circular dependency between the safety checks and the thread singleton pointer.
if (this == MessageQueue::thread_singleton) {
MessageQueue::thread_singleton = nullptr;
}
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 056334c8e1..2e5b897bce 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -1109,8 +1109,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
Error err = OK;
for (const Connection &c : slot_conns) {
- Object *target = c.callable.get_object();
- if (!target) {
+ if (!c.callable.is_valid()) {
// Target might have been deleted during signal callback, this is expected and OK.
continue;
}
@@ -1133,7 +1132,8 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
continue;
}
#endif
- if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && !ClassDB::class_exists(target->get_class_name())) {
+ Object *target = c.callable.get_object();
+ if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) {
//most likely object is not initialized yet, do not throw error.
} else {
ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(c.callable, args, argc, ce) + ".");
@@ -1313,8 +1313,14 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null.");
- Object *target_object = p_callable.get_object();
- ERR_FAIL_NULL_V_MSG(target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
+ if (p_callable.is_standard()) {
+ // FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes
+ // that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found
+ // and registered soon enough this branch is needed to allow `connect()` to succeed.
+ ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
+ } else {
+ ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is not valid: " + p_callable);
+ }
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@@ -1352,6 +1358,8 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
}
}
+ Object *target_object = p_callable.get_object();
+
SignalData::Slot slot;
Connection conn;
@@ -1359,7 +1367,9 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
conn.signal = ::Signal(this, p_signal);
conn.flags = p_flags;
slot.conn = conn;
- slot.cE = target_object->connections.push_back(conn);
+ if (target_object) {
+ slot.cE = target_object->connections.push_back(conn);
+ }
if (p_flags & CONNECT_REFERENCE_COUNTED) {
slot.reference_count = 1;
}
@@ -1398,9 +1408,6 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null.");
- Object *target_object = p_callable.get_object();
- ERR_FAIL_NULL_V_MSG(target_object, false, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null.");
-
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) ||
@@ -1420,7 +1427,13 @@ bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable,
}
}
- target_object->connections.erase(slot->cE);
+ if (slot->cE) {
+ Object *target_object = p_callable.get_object();
+ if (target_object) {
+ target_object->connections.erase(slot->cE);
+ }
+ }
+
s->slot_map.erase(*p_callable.get_base_comparator());
if (s->slot_map.is_empty() && ClassDB::has_signal(get_class_name(), p_signal)) {
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 5ff7cd8582..3e4041d173 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -239,6 +239,7 @@ public:
virtual void get_reserved_words(List<String> *p_words) const = 0;
virtual bool is_control_flow_keyword(String p_string) const = 0;
virtual void get_comment_delimiters(List<String> *p_delimiters) const = 0;
+ virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const = 0;
virtual void get_string_delimiters(List<String> *p_delimiters) const = 0;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); }
virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) { return Vector<ScriptTemplate>(); }
diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp
index a07bf63a02..e326baf7eb 100644
--- a/core/object/script_language_extension.cpp
+++ b/core/object/script_language_extension.cpp
@@ -92,6 +92,7 @@ void ScriptLanguageExtension::_bind_methods() {
GDVIRTUAL_BIND(_get_reserved_words);
GDVIRTUAL_BIND(_is_control_flow_keyword, "keyword");
GDVIRTUAL_BIND(_get_comment_delimiters);
+ GDVIRTUAL_BIND(_get_doc_comment_delimiters);
GDVIRTUAL_BIND(_get_string_delimiters);
GDVIRTUAL_BIND(_make_template, "template", "class_name", "base_class_name");
GDVIRTUAL_BIND(_get_built_in_templates, "object");
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index 89bba80b90..5f3a70cad8 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -241,6 +241,16 @@ public:
}
}
+ GDVIRTUAL0RC(Vector<String>, _get_doc_comment_delimiters)
+
+ virtual void get_doc_comment_delimiters(List<String> *p_words) const override {
+ Vector<String> ret;
+ GDVIRTUAL_CALL(_get_doc_comment_delimiters, ret);
+ for (int i = 0; i < ret.size(); i++) {
+ p_words->push_back(ret[i]);
+ }
+ }
+
GDVIRTUAL0RC(Vector<String>, _get_string_delimiters)
virtual void get_string_delimiters(List<String> *p_words) const override {
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index b0f9501985..2435ab48ac 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -136,17 +136,22 @@ void UndoRedo::add_do_method(const Callable &p_callable) {
ERR_FAIL_COND(action_level <= 0);
ERR_FAIL_COND((current_action + 1) >= actions.size());
- Object *object = p_callable.get_object();
- ERR_FAIL_NULL(object);
+ ObjectID object_id = p_callable.get_object_id();
+ Object *object = ObjectDB::get_instance(object_id);
+ ERR_FAIL_COND(object_id.is_valid() && object == nullptr);
Operation do_op;
do_op.callable = p_callable;
- do_op.object = p_callable.get_object_id();
+ do_op.object = object_id;
if (Object::cast_to<RefCounted>(object)) {
do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
}
do_op.type = Operation::TYPE_METHOD;
do_op.name = p_callable.get_method();
+ if (do_op.name == StringName()) {
+ // There's no `get_method()` for custom callables, so use `operator String()` instead.
+ do_op.name = static_cast<String>(p_callable);
+ }
actions.write[current_action + 1].do_ops.push_back(do_op);
}
@@ -161,18 +166,23 @@ void UndoRedo::add_undo_method(const Callable &p_callable) {
return;
}
- Object *object = p_callable.get_object();
- ERR_FAIL_NULL(object);
+ ObjectID object_id = p_callable.get_object_id();
+ Object *object = ObjectDB::get_instance(object_id);
+ ERR_FAIL_COND(object_id.is_valid() && object == nullptr);
Operation undo_op;
undo_op.callable = p_callable;
- undo_op.object = p_callable.get_object_id();
+ undo_op.object = object_id;
if (Object::cast_to<RefCounted>(object)) {
undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
}
undo_op.type = Operation::TYPE_METHOD;
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
undo_op.name = p_callable.get_method();
+ if (undo_op.name == StringName()) {
+ // There's no `get_method()` for custom callables, so use `operator String()` instead.
+ undo_op.name = static_cast<String>(p_callable);
+ }
actions.write[current_action + 1].undo_ops.push_back(undo_op);
}
diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h
index deb2937771..72425a8c3d 100644
--- a/core/templates/paged_allocator.h
+++ b/core/templates/paged_allocator.h
@@ -144,7 +144,7 @@ public:
if (thread_safe) {
spin_lock.lock();
}
- ERR_FAIL_COND(page_pool != nullptr); //sanity check
+ ERR_FAIL_COND(page_pool != nullptr); // Safety check.
ERR_FAIL_COND(p_page_size == 0);
page_size = nearest_power_of_2_templated(p_page_size);
page_mask = page_size - 1;
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h
index bfc9f6fc9a..20fb0c6501 100644
--- a/core/templates/safe_refcount.h
+++ b/core/templates/safe_refcount.h
@@ -182,7 +182,7 @@ class SafeRefCount {
SafeNumeric<uint32_t> count;
#ifdef DEV_ENABLED
- _ALWAYS_INLINE_ void _check_unref_sanity() {
+ _ALWAYS_INLINE_ void _check_unref_safety() {
// This won't catch every misuse, but it's better than nothing.
CRASH_COND_MSG(count.get() == 0,
"Trying to unreference a SafeRefCount which is already zero is wrong and a symptom of it being misused.\n"
@@ -202,14 +202,14 @@ public:
_ALWAYS_INLINE_ bool unref() { // true if must be disposed of
#ifdef DEV_ENABLED
- _check_unref_sanity();
+ _check_unref_safety();
#endif
return count.decrement() == 0;
}
_ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of
#ifdef DEV_ENABLED
- _check_unref_sanity();
+ _check_unref_safety();
#endif
return count.decrement();
}
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index d7034f1c00..0b1174c873 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -47,6 +47,13 @@ void Callable::callp(const Variant **p_arguments, int p_argcount, Variant &r_ret
r_call_error.expected = 0;
r_return_value = Variant();
} else if (is_custom()) {
+ if (!is_valid()) {
+ r_call_error.error = CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ r_call_error.argument = 0;
+ r_call_error.expected = 0;
+ r_return_value = Variant();
+ return;
+ }
custom->call(p_arguments, p_argcount, r_return_value, r_call_error);
} else {
Object *obj = ObjectDB::get_instance(ObjectID(object));
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 63ea3274ce..09fb34e7c1 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -2117,7 +2117,7 @@ Variant::operator ::RID() const {
} else if (type == OBJECT && _get_obj().obj) {
#ifdef DEBUG_ENABLED
if (EngineDebugger::is_active()) {
- ERR_FAIL_COND_V_MSG(ObjectDB::get_instance(_get_obj().id) == nullptr, ::RID(), "Invalid pointer (object was freed).");
+ ERR_FAIL_NULL_V_MSG(ObjectDB::get_instance(_get_obj().id), ::RID(), "Invalid pointer (object was freed).");
}
#endif
Callable::CallError ce;
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 782053b613..116210e8de 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -418,7 +418,7 @@ public:
case Variant::PACKED_COLOR_ARRAY:
return get_color_array(v);
case Variant::OBJECT:
- return v->_get_obj().obj;
+ return get_object(v);
case Variant::VARIANT_MAX:
ERR_FAIL_V(nullptr);
}
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 5a0cb9fc5e..6edd8af7cc 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -500,6 +500,10 @@
If [code]true[/code], when saving a file, the editor will rename the old file to a different name, save a new file, then only remove the old file once the new file has been saved. This makes loss of data less likely to happen if the editor or operating system exits unexpectedly while saving (e.g. due to a crash or power outage).
[b]Note:[/b] On Windows, this feature can interact negatively with certain antivirus programs. In this case, you may have to set this to [code]false[/code] to prevent file locking issues.
</member>
+ <member name="filesystem/tools/oidn/oidn_denoise_path" type="String" setter="" getter="">
+ The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url].
+ To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser].
+ </member>
<member name="interface/editor/accept_dialog_cancel_ok_buttons" type="int" setter="" getter="">
How to position the Cancel and OK buttons in the editor's [AcceptDialog]s. Different platforms have different standard behaviors for this, which can be overridden using this setting. This is useful if you use Godot both on Windows and macOS/Linux and your Godot muscle memory is stronger than your OS specific one.
- [b]Auto[/b] follows the platform convention: Cancel first on macOS and Linux, OK first on Windows.
@@ -953,6 +957,9 @@
<member name="text_editor/theme/highlighting/current_line_color" type="Color" setter="" getter="">
The script editor's background color for the line the caret is currently on. This should be set to a translucent color so that it can display on top of other line color modifiers such as [member text_editor/theme/highlighting/mark_color].
</member>
+ <member name="text_editor/theme/highlighting/doc_comment_color" type="Color" setter="" getter="">
+ The script editor's documentation comment color. In GDScript, this is used for comments starting with [code]##[/code]. In C#, this is used for comments starting with [code]///[/code] or [code]/**[/code].
+ </member>
<member name="text_editor/theme/highlighting/engine_type_color" type="Color" setter="" getter="">
The script editor's engine type color ([Vector2], [Vector3], [Color], ...).
</member>
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index 42528dc287..d1b20a3890 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -155,6 +155,7 @@
<param index="0" name="path" type="String" />
<description>
Returns the whole [param path] file contents as a [PackedByteArray] without any decoding.
+ Returns an empty [PackedByteArray] if an error occurred while opening the file. You can use [method get_open_error] to check the error that occurred.
</description>
</method>
<method name="get_file_as_string" qualifiers="static">
@@ -162,6 +163,7 @@
<param index="0" name="path" type="String" />
<description>
Returns the whole [param path] file contents as a [String]. Text is interpreted as being UTF-8 encoded.
+ Returns an empty [String] if an error occurred while opening the file. You can use [method get_open_error] to check the error that occurred.
</description>
</method>
<method name="get_float" qualifiers="const">
diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml
index ee55288783..54c8689ab0 100644
--- a/doc/classes/GPUParticles2D.xml
+++ b/doc/classes/GPUParticles2D.xml
@@ -50,6 +50,10 @@
<member name="amount" type="int" setter="set_amount" getter="get_amount" default="8">
Number of particles emitted in one emission cycle.
</member>
+ <member name="amount_ratio" type="float" setter="set_amount_ratio" getter="get_amount_ratio" default="1.0">
+ The ratio of particles that should actually be emitted. If set to a value lower than [code]1.0[/code], this will set the amount of emitted particles throughout the lifetime to [code]amount * amount_ratio[/code]. Unlike changing [member amount], changing [member amount_ratio] while emitting does not affect already-emitted particles and doesn't cause the particle system to restart. [member amount_ratio] can be used to create effects that make the number of emitted particles vary over time.
+ [b]Note:[/b] Reducing the [member amount_ratio] has no performance benefit, since resources need to be allocated and processed for the total [member amount] of particles regardless of the [member amount_ratio].
+ </member>
<member name="collision_base_size" type="float" setter="set_collision_base_size" getter="get_collision_base_size" default="1.0">
Multiplier for particle's collision radius. [code]1.0[/code] corresponds to the size of the sprite.
</member>
@@ -68,6 +72,10 @@
<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta" default="true">
If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect.
</member>
+ <member name="interp_to_end" type="float" setter="set_interp_to_end" getter="get_interp_to_end" default="0.0">
+ Causes all the particles in this node to interpolate towards the end of their lifetime.
+ [b]Note[/b]: This only works when used with a [ParticleProcessMaterial]. It needs to be manually implemented for custom process shaders.
+ </member>
<member name="interpolate" type="bool" setter="set_interpolate" getter="get_interpolate" default="true">
Enables particle interpolation, which makes the particle movement smoother when their [member fixed_fps] is lower than the screen refresh rate.
</member>
diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml
index 3d7243fcbd..dc7e51b7d7 100644
--- a/doc/classes/GPUParticles3D.xml
+++ b/doc/classes/GPUParticles3D.xml
@@ -63,6 +63,10 @@
<member name="amount" type="int" setter="set_amount" getter="get_amount" default="8">
Number of particles to emit.
</member>
+ <member name="amount_ratio" type="float" setter="set_amount_ratio" getter="get_amount_ratio" default="1.0">
+ The ratio of particles that should actually be emitted. If set to a value lower than [code]1.0[/code], this will set the amount of emitted particles throughout the lifetime to [code]amount * amount_ratio[/code]. Unlike changing [member amount], changing [member amount_ratio] while emitting does not affect already-emitted particles and doesn't cause the particle system to restart. [member amount_ratio] can be used to create effects that make the number of emitted particles vary over time.
+ [b]Note:[/b] Reducing the [member amount_ratio] has no performance benefit, since resources need to be allocated and processed for the total [member amount] of particles regardless of the [member amount_ratio].
+ </member>
<member name="collision_base_size" type="float" setter="set_collision_base_size" getter="get_collision_base_size" default="0.01">
</member>
<member name="draw_order" type="int" setter="set_draw_order" getter="get_draw_order" enum="GPUParticles3D.DrawOrder" default="0">
@@ -98,6 +102,10 @@
<member name="fract_delta" type="bool" setter="set_fractional_delta" getter="get_fractional_delta" default="true">
If [code]true[/code], results in fractional delta calculation which has a smoother particles display effect.
</member>
+ <member name="interp_to_end" type="float" setter="set_interp_to_end" getter="get_interp_to_end" default="0.0">
+ Causes all the particles in this node to interpolate towards the end of their lifetime.
+ [b]Note[/b]: This only works when used with a [ParticleProcessMaterial]. It needs to be manually implemented for custom process shaders.
+ </member>
<member name="interpolate" type="bool" setter="set_interpolate" getter="get_interpolate" default="true">
Enables particle interpolation, which makes the particle movement smoother when their [member fixed_fps] is lower than the screen refresh rate.
</member>
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index f39d8788ad..e18413810b 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -67,7 +67,7 @@
<param index="0" name="action" type="StringName" />
<param index="1" name="exact_match" type="bool" default="false" />
<description>
- Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis as the keyboard, the value returned will be 0 or 1.
+ Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis such as the keyboard, the value returned will be 0 or 1.
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
</description>
</method>
diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml
index 5a050eb256..3dad9b7e56 100644
--- a/doc/classes/LightmapGI.xml
+++ b/doc/classes/LightmapGI.xml
@@ -25,7 +25,7 @@
The [CameraAttributes] resource that specifies exposure levels to bake at. Auto-exposure and non exposure properties will be ignored. Exposure settings should be used to reduce the dynamic range present when baking. If exposure is too high, the [LightmapGI] will have banding artifacts or may have over-exposure artifacts.
</member>
<member name="denoiser_strength" type="float" setter="set_denoiser_strength" getter="get_denoiser_strength" default="0.1">
- The strength of denoising step applied to the generated lightmaps. Only effective if [member use_denoiser] is [code]true[/code].
+ The strength of denoising step applied to the generated lightmaps. Only effective if [member use_denoiser] is [code]true[/code] and [member ProjectSettings.rendering/lightmapping/denoising/denoiser] is set to JNLM.
</member>
<member name="directional" type="bool" setter="set_directional" getter="is_directional" default="false">
If [code]true[/code], bakes lightmaps to contain directional information as spherical harmonics. This results in more realistic lighting appearance, especially with normal mapped materials and for lights that have their direct light baked ([member Light3D.light_bake_mode] set to [constant Light3D.BAKE_STATIC]). The directional information is also used to provide rough reflections for static and dynamic objects. This has a small run-time performance cost as the shader has to perform more work to interpret the direction information from the lightmap. Directional lightmaps also take longer to bake and result in larger file sizes.
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index ae2ca324e3..46c87d63b3 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -334,7 +334,7 @@
case "Linux":
case "FreeBSD":
case "NetBSD":
- case "OpenBSD"
+ case "OpenBSD":
case "BSD":
GD.Print("Linux/BSD");
break;
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 0f6cc3b29e..b7f55184f0 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -71,6 +71,9 @@
</method>
</methods>
<members>
+ <member name="alpha_curve" type="Texture2D" setter="set_alpha_curve" getter="get_alpha_curve">
+ The alpha value of each particle's color will be multiplied by this [CurveTexture] over its lifetime.
+ </member>
<member name="angle_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
Each particle's rotation will be animated along this [CurveTexture].
</member>
@@ -151,6 +154,18 @@
<member name="direction" type="Vector3" setter="set_direction" getter="get_direction" default="Vector3(1, 0, 0)">
Unit vector specifying the particles' emission direction.
</member>
+ <member name="directional_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
+ A curve that specifies the velocity along each of the axes of the particle system along its lifetime.
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
+ <member name="directional_velocity_max" type="float" setter="set_param_max" getter="get_param_max">
+ Maximum directional velocity value, which is multiplied by [member directional_velocity_curve].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
+ <member name="directional_velocity_min" type="float" setter="set_param_min" getter="get_param_min">
+ Minimum directional velocity value, which is multiplied by [member directional_velocity_curve].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
<member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents">
The box's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX].
</member>
@@ -158,6 +173,10 @@
Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
[b]Note:[/b] [member emission_color_texture] 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 emission_color_texture] will have no visible effect.
</member>
+ <member name="emission_curve" type="Texture2D" setter="set_emission_curve" getter="get_emission_curve">
+ Each particle's color will be multiplied by this [CurveTexture] over its lifetime.
+ [b]Note:[/b] This property won't have a visible effect unless the render material is marked as unshaded.
+ </member>
<member name="emission_normal_texture" type="Texture2D" setter="set_emission_normal_texture" getter="get_emission_normal_texture">
Particle velocity and rotation will be set by sampling this texture at the same point as the [member emission_point_texture]. Used only in [constant EMISSION_SHAPE_DIRECTED_POINTS]. Can be created automatically from mesh or node by selecting "Create Emission Points from Mesh/Node" under the "Particles" tool in the toolbar.
</member>
@@ -182,6 +201,12 @@
<member name="emission_shape" type="int" setter="set_emission_shape" getter="get_emission_shape" enum="ParticleProcessMaterial.EmissionShape" default="0">
Particles will be emitted inside this region. Use [enum EmissionShape] constants for values.
</member>
+ <member name="emission_shape_offset" type="Vector3" setter="set_emission_shape_offset" getter="get_emission_shape_offset" default="Vector3(0, 0, 0)">
+ The offset for the [member emission_shape], in local space.
+ </member>
+ <member name="emission_shape_scale" type="Vector3" setter="set_emission_shape_scale" getter="get_emission_shape_scale" default="Vector3(1, 1, 1)">
+ The scale of the [member emission_shape], in local space.
+ </member>
<member name="emission_sphere_radius" type="float" setter="set_emission_sphere_radius" getter="get_emission_sphere_radius">
The sphere's radius if [member emission_shape] is set to [constant EMISSION_SHAPE_SPHERE].
</member>
@@ -200,6 +225,9 @@
<member name="hue_variation_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
Minimum equivalent of [member hue_variation_max].
</member>
+ <member name="inherit_velocity_ratio" type="float" setter="set_inherit_velocity_ratio" getter="get_inherit_velocity_ratio" default="0.0">
+ Percentage of the velocity of the respective [GPUParticles2D] or [GPUParticles3D] inherited by each particle when spawning.
+ </member>
<member name="initial_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0">
Maximum initial velocity magnitude for each particle. Direction comes from [member direction] and [member spread].
</member>
@@ -220,17 +248,23 @@
</member>
<member name="orbit_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
Each particle's orbital velocity will vary along this [CurveTexture].
+ [b]Note:[/b] For 3D orbital velocity, use a [CurveXYZTexture].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
</member>
- <member name="orbit_velocity_max" type="float" setter="set_param_max" getter="get_param_max">
+ <member name="orbit_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0">
Maximum orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second.
- Only available when [member particle_flag_disable_z] is [code]true[/code].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
</member>
- <member name="orbit_velocity_min" type="float" setter="set_param_min" getter="get_param_min">
+ <member name="orbit_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
Minimum equivalent of [member orbit_velocity_max].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
</member>
<member name="particle_flag_align_y" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false">
Align Y axis of particle with the direction of its velocity.
</member>
+ <member name="particle_flag_damping_as_friction" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false">
+ Changes the behavior of the damping properties from a linear deceleration to a deceleration based on speed percentage.
+ </member>
<member name="particle_flag_disable_z" type="bool" setter="set_particle_flag" getter="get_particle_flag" default="false">
If [code]true[/code], particles will not move on the z axis.
</member>
@@ -246,6 +280,18 @@
<member name="radial_accel_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
Minimum equivalent of [member radial_accel_max].
</member>
+ <member name="radial_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
+ A [CurveTexture] that defines the velocity over the particle's lifetime away (or toward) the [member velocity_pivot].
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
+ <member name="radial_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0">
+ Maximum radial velocity applied to each particle. Makes particles move away from the [member velocity_pivot], or toward it if negative.
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
+ <member name="radial_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
+ Minimum radial velocity applied to each particle. Makes particles move away from the [member velocity_pivot], or toward it if negative.
+ [b]Note:[/b] Animated velocities will not be affected by damping, use [member velocity_limit_curve] instead.
+ </member>
<member name="scale_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
Each particle's scale will vary along this [CurveTexture]. If a [CurveXYZTexture] is supplied instead, the scale will be separated per-axis.
</member>
@@ -255,6 +301,17 @@
<member name="scale_min" type="float" setter="set_param_min" getter="get_param_min" default="1.0">
Minimum equivalent of [member scale_max].
</member>
+ <member name="scale_over_velocity_curve" type="Texture2D" setter="set_param_texture" getter="get_param_texture">
+ Either a [CurveTexture] or a [CurveXYZTexture] that scales each particle based on its velocity.
+ </member>
+ <member name="scale_over_velocity_max" type="float" setter="set_param_max" getter="get_param_max" default="0.0">
+ Maximum velocity value reference for [member scale_over_velocity_curve].
+ [member scale_over_velocity_curve] will be interpolated between [member scale_over_velocity_min] and [member scale_over_velocity_max].
+ </member>
+ <member name="scale_over_velocity_min" type="float" setter="set_param_min" getter="get_param_min" default="0.0">
+ Minimum velocity value reference for [member scale_over_velocity_curve].
+ [member scale_over_velocity_curve] will be interpolated between [member scale_over_velocity_min] and [member scale_over_velocity_max].
+ </member>
<member name="spread" type="float" setter="set_spread" getter="get_spread" default="45.0">
Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees.
</member>
@@ -317,6 +374,11 @@
<member name="turbulence_noise_strength" type="float" setter="set_turbulence_noise_strength" getter="get_turbulence_noise_strength" default="1.0">
The turbulence noise strength. Increasing this will result in a stronger, more contrasting, flow pattern.
</member>
+ <member name="velocity_limit_curve" type="Texture2D" setter="set_velocity_limit_curve" getter="get_velocity_limit_curve">
+ A [CurveTexture] that defines the maximum velocity of a particle during its lifetime.
+ </member>
+ <member name="velocity_pivot" type="Vector3" setter="set_velocity_pivot" getter="get_velocity_pivot" default="Vector3(0, 0, 0)">
+ </member>
</members>
<constants>
<constant name="PARAM_INITIAL_LINEAR_VELOCITY" value="0" enum="Parameter">
@@ -355,7 +417,16 @@
<constant name="PARAM_ANIM_OFFSET" value="11" enum="Parameter">
Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set animation offset properties.
</constant>
- <constant name="PARAM_MAX" value="15" enum="Parameter">
+ <constant name="PARAM_RADIAL_VELOCITY" value="15" enum="Parameter">
+ Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set radial velocity properties.
+ </constant>
+ <constant name="PARAM_DIRECTIONAL_VELOCITY" value="16" enum="Parameter">
+ Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set directional velocity properties.
+ </constant>
+ <constant name="PARAM_SCALE_OVER_VELOCITY" value="17" enum="Parameter">
+ Use with [method set_param_min], [method set_param_max], and [method set_param_texture] to set scale over velocity properties.
+ </constant>
+ <constant name="PARAM_MAX" value="18" enum="Parameter">
Represents the size of the [enum Parameter] enum.
</constant>
<constant name="PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY" value="0" enum="ParticleFlags">
@@ -367,7 +438,9 @@
<constant name="PARTICLE_FLAG_DISABLE_Z" value="2" enum="ParticleFlags">
Use with [method set_particle_flag] to set [member particle_flag_disable_z].
</constant>
- <constant name="PARTICLE_FLAG_MAX" value="3" enum="ParticleFlags">
+ <constant name="PARTICLE_FLAG_DAMPING_AS_FRICTION" value="3" enum="ParticleFlags">
+ </constant>
+ <constant name="PARTICLE_FLAG_MAX" value="4" enum="ParticleFlags">
Represents the size of the [enum ParticleFlags] enum.
</constant>
<constant name="EMISSION_SHAPE_POINT" value="0" enum="EmissionShape">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index b49de068fd..a79d31f7c6 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -418,6 +418,12 @@
<member name="compression/formats/zstd/window_log_size" type="int" setter="" getter="" default="27">
Largest size limit (in power of 2) allowed when compressing using long-distance matching with Zstandard. Higher values can result in better compression, but will require more memory when compressing and decompressing.
</member>
+ <member name="debug/canvas_items/debug_redraw_color" type="Color" setter="" getter="" default="Color(1, 0.2, 0.2, 0.5)">
+ If canvas item redraw debugging is active, this color will be flashed on canvas items when they redraw.
+ </member>
+ <member name="debug/canvas_items/debug_redraw_time" type="float" setter="" getter="" default="1.0">
+ If canvas item redraw debugging is active, this will be the time the flash will last each time they redraw.
+ </member>
<member name="debug/file_logging/enable_file_logging" type="bool" setter="" getter="" default="false">
If [code]true[/code], logs all output to files.
</member>
@@ -1309,6 +1315,12 @@
<member name="input_devices/pen_tablet/driver.windows" type="String" setter="" getter="">
Override for [member input_devices/pen_tablet/driver] on Windows.
</member>
+ <member name="input_devices/pointing/android/enable_long_press_as_right_click" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], long press events on an Android touchscreen are transformed into right click events.
+ </member>
+ <member name="input_devices/pointing/android/enable_pan_and_scale_gestures" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], multi-touch pan and scale gestures are enabled on Android devices.
+ </member>
<member name="input_devices/pointing/emulate_mouse_from_touch" type="bool" setter="" getter="" default="true">
If [code]true[/code], sends mouse input events when tapping or swiping on the touchscreen.
</member>
@@ -2434,6 +2446,15 @@
<member name="rendering/lightmapping/bake_quality/ultra_quality_ray_count" type="int" setter="" getter="" default="1024">
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_ULTRA].
</member>
+ <member name="rendering/lightmapping/denoising/denoiser" type="int" setter="" getter="" default="0">
+ Denoiser tool used for denoising lightmaps.
+ Using [url=https://www.openimagedenoise.org/]OpenImageDenoise[/url] (OIDN) requires configuring a path to an OIDN executable in the editor settings at [member EditorSettings.filesystem/tools/oidn/oidn_denoise_path]. OIDN can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]OpenImageDenoise's downloads page[/url].
+ OIDN will use GPU acceleration when available. Unlike JNLM which uses compute shaders for acceleration, OIDN uses vendor-specific acceleration methods. For GPU acceleration to be available, the following libraries must be installed on the system depending on your GPU:
+ - NVIDIA GPUs: CUDA libraries
+ - AMD GPUs: HIP libraries
+ - Intel GPUs: SYCL libraries
+ If no GPU acceleration is configured on the system, multi-threaded CPU-based denoising will be performed instead. This CPU-based denoising is significantly slower than the JNLM denoiser in most cases.
+ </member>
<member name="rendering/lightmapping/primitive_meshes/texel_size" type="float" setter="" getter="" default="0.2">
The texel_size that is used to calculate the [member Mesh.lightmap_size_hint] on [PrimitiveMesh] resources if [member PrimitiveMesh.add_uv2] is enabled.
</member>
diff --git a/doc/classes/RefCounted.xml b/doc/classes/RefCounted.xml
index eaf32a7b54..fe10f84449 100644
--- a/doc/classes/RefCounted.xml
+++ b/doc/classes/RefCounted.xml
@@ -37,7 +37,7 @@
<return type="bool" />
<description>
Decrements the internal reference counter. Use this only if you really know what you are doing.
- Returns [code]true[/code] if the decrement was successful, [code]false[/code] otherwise.
+ Returns [code]true[/code] if the object should be freed after the decrement, [code]false[/code] otherwise.
</description>
</method>
</methods>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 24b409d941..dd279206ab 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2627,6 +2627,14 @@
Sets the number of particles to be drawn and allocates the memory for them. Equivalent to [member GPUParticles3D.amount].
</description>
</method>
+ <method name="particles_set_amount_ratio">
+ <return type="void" />
+ <param index="0" name="particles" type="RID" />
+ <param index="1" name="ratio" type="float" />
+ <description>
+ Sets the amount ratio for particles to be emitted. Equivalent to [member GPUParticles3D.amount_ratio].
+ </description>
+ </method>
<method name="particles_set_collision_base_size">
<return type="void" />
<param index="0" name="particles" type="RID" />
@@ -2675,6 +2683,14 @@
Sets the [Transform3D] that will be used by the particles when they first emit.
</description>
</method>
+ <method name="particles_set_emitter_velocity">
+ <return type="void" />
+ <param index="0" name="particles" type="RID" />
+ <param index="1" name="velocity" type="Vector3" />
+ <description>
+ Sets the velocity of a particle node, that will be used by [member ParticleProcessMaterial.inherit_velocity_ratio].
+ </description>
+ </method>
<method name="particles_set_emitting">
<return type="void" />
<param index="0" name="particles" type="RID" />
@@ -2707,6 +2723,14 @@
If [code]true[/code], uses fractional delta which smooths the movement of the particles. Equivalent to [member GPUParticles3D.fract_delta].
</description>
</method>
+ <method name="particles_set_interp_to_end">
+ <return type="void" />
+ <param index="0" name="particles" type="RID" />
+ <param index="1" name="factor" type="float" />
+ <description>
+ Sets the value that informs a [ParticleProcessMaterial] to rush all particles towards the end of their lifetime.
+ </description>
+ </method>
<method name="particles_set_interpolate">
<return type="void" />
<param index="0" name="particles" type="RID" />
diff --git a/doc/classes/ScriptLanguageExtension.xml b/doc/classes/ScriptLanguageExtension.xml
index e10cb89e20..1a61618b53 100644
--- a/doc/classes/ScriptLanguageExtension.xml
+++ b/doc/classes/ScriptLanguageExtension.xml
@@ -140,6 +140,11 @@
<description>
</description>
</method>
+ <method name="_get_doc_comment_delimiters" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <description>
+ </description>
+ </method>
<method name="_get_extension" qualifiers="virtual const">
<return type="String" />
<description>
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index 1b5f7148ac..0a3ecef39b 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -258,8 +258,11 @@
<member name="debug_draw" type="int" setter="set_debug_draw" getter="get_debug_draw" enum="Viewport.DebugDraw" default="0">
The overlay mode for test rendered geometry in debug purposes.
</member>
+ <member name="disable_2d" type="bool" setter="set_disable_2d" getter="is_2d_disabled" default="false">
+ If [code]true[/code], disables 2D rendering while keeping 3D rendering. See also [member disable_3d].
+ </member>
<member name="disable_3d" type="bool" setter="set_disable_3d" getter="is_3d_disabled" default="false">
- Disable 3D rendering (but keep 2D rendering).
+ If [code]true[/code], disables 3D rendering while keeping 2D rendering. See also [member disable_2d].
</member>
<member name="fsr_sharpness" type="float" setter="set_fsr_sharpness" getter="get_fsr_sharpness" default="0.2">
Determines how sharp the upscaled image will be when using the FSR upscaling mode. Sharpness halves with every whole number. Values go from 0.0 (sharpest) to 2.0. Values above 2.0 won't make a visible difference.
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 7fce06c133..2db17e96f7 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -866,7 +866,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
state.instance_data_array[r_index].lights[2] = lights[2];
state.instance_data_array[r_index].lights[3] = lights[3];
- state.instance_data_array[r_index].flags = base_flags | (state.instance_data_array[r_index == 0 ? 0 : r_index - 1].flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); //reset on each command for sanity, keep canvastexture binding config
+ state.instance_data_array[r_index].flags = base_flags | (state.instance_data_array[r_index == 0 ? 0 : r_index - 1].flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); // Reset on each command for safety, keep canvastexture binding config.
Color blend_color = base_color;
GLES3::CanvasShaderData::BlendMode blend_mode = p_blend_mode;
@@ -1236,7 +1236,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
}
void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
- ERR_FAIL_COND(!state.canvas_instance_batches[state.current_batch_index].command);
+ ERR_FAIL_NULL(state.canvas_instance_batches[state.current_batch_index].command);
// Used by Polygon and Mesh.
static const GLenum prim[5] = { GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP };
@@ -2157,7 +2157,7 @@ void RasterizerCanvasGLES3::_bind_canvas_texture(RID p_texture, RS::CanvasItemTe
GLES3::Texture *t = texture_storage->get_texture(p_texture);
if (t) {
- ERR_FAIL_COND(!t->canvas_texture);
+ ERR_FAIL_NULL(t->canvas_texture);
ct = t->canvas_texture;
if (t->render_target) {
t->render_target->used_in_frame = true;
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index c1b3e20e33..94c771cde7 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -367,6 +367,12 @@ public:
void set_time(double p_time);
+ virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) override {
+ if (p_enabled) {
+ WARN_PRINT_ONCE("Debug CanvasItem Redraw is not available yet when using the GL Compatibility backend.");
+ }
+ }
+
static RasterizerCanvasGLES3 *get_singleton();
RasterizerCanvasGLES3();
~RasterizerCanvasGLES3();
diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl
index 40881a1808..64ef26b075 100644
--- a/drivers/gles3/shaders/particles.glsl
+++ b/drivers/gles3/shaders/particles.glsl
@@ -78,7 +78,7 @@ layout(std140) uniform FrameData { //ubo:0
float delta;
float particle_size;
- float pad0;
+ float amount_ratio;
float pad1;
float pad2;
@@ -89,6 +89,9 @@ layout(std140) uniform FrameData { //ubo:0
mat4 emission_transform;
+ vec3 emitter_velocity;
+ float interp_to_end;
+
Attractor attractors[MAX_ATTRACTORS];
Collider colliders[MAX_COLLIDERS];
};
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index ff06fbfa41..6d4d23bd10 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -34,6 +34,7 @@
#include "../rasterizer_gles3.h"
#include "../rasterizer_scene_gles3.h"
#include "config.h"
+#include "core/config/project_settings.h"
#include "texture_storage.h"
using namespace GLES3;
@@ -46,6 +47,11 @@ LightStorage *LightStorage::get_singleton() {
LightStorage::LightStorage() {
singleton = this;
+
+ directional_shadow.size = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/size");
+ directional_shadow.use_16_bits = GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/16_bits");
+
+ // lightmap_probe_capture_update_speed = GLOBAL_GET("rendering/lightmapping/probe_capture/update_speed");
}
LightStorage::~LightStorage() {
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 016f554368..b3a3506d40 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1395,6 +1395,7 @@ MaterialStorage::MaterialStorage() {
actions.renames["DELTA"] = "local_delta";
actions.renames["NUMBER"] = "particle_number";
actions.renames["INDEX"] = "index";
+ actions.renames["AMOUNT_RATIO"] = "amount_ratio";
//actions.renames["GRAVITY"] = "current_gravity";
actions.renames["EMISSION_TRANSFORM"] = "emission_transform";
actions.renames["RANDOM_SEED"] = "random_seed";
@@ -1407,6 +1408,8 @@ MaterialStorage::MaterialStorage() {
actions.renames["COLLISION_NORMAL"] = "collision_normal";
actions.renames["COLLISION_DEPTH"] = "collision_depth";
actions.renames["ATTRACTOR_FORCE"] = "attractor_force";
+ actions.renames["EMITTER_VELOCITY"] = "emitter_velocity";
+ actions.renames["INTERPOLATE_TO_END"] = "interp_to_end";
// These are unsupported, but may be used by users. To avoid compile time overhead, we add the stub only when used.
actions.renames["FLAG_EMIT_POSITION"] = "uint(1)";
@@ -2396,7 +2399,7 @@ void MaterialStorage::material_set_shader(RID p_material, RID p_shader) {
return;
}
- ERR_FAIL_COND(shader->data == nullptr);
+ ERR_FAIL_NULL(shader->data);
material->data = material_data_request_func[shader->mode](shader->data);
material->data->self = p_material;
diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp
index 0a6f02511c..296d721cd1 100644
--- a/drivers/gles3/storage/particles_storage.cpp
+++ b/drivers/gles3/storage/particles_storage.cpp
@@ -190,6 +190,13 @@ void ParticlesStorage::particles_set_amount(RID p_particles, int p_amount) {
particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
}
+void ParticlesStorage::particles_set_amount_ratio(RID p_particles, float p_amount_ratio) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->amount_ratio = p_amount_ratio;
+}
+
void ParticlesStorage::particles_set_lifetime(RID p_particles, double p_lifetime) {
Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL(particles);
@@ -431,6 +438,20 @@ void ParticlesStorage::particles_set_emission_transform(RID p_particles, const T
particles->emission_transform = p_transform;
}
+void ParticlesStorage::particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->emitter_velocity = p_velocity;
+}
+
+void ParticlesStorage::particles_set_interp_to_end(RID p_particles, float p_interp) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->interp_to_end = p_interp;
+}
+
int ParticlesStorage::particles_get_draw_passes(RID p_particles) const {
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, 0);
@@ -507,9 +528,13 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta
frame_params.cycle = p_particles->cycle_number;
frame_params.frame = p_particles->frame_counter++;
- frame_params.pad0 = 0;
+ frame_params.amount_ratio = p_particles->amount_ratio;
frame_params.pad1 = 0;
frame_params.pad2 = 0;
+ frame_params.interp_to_end = p_particles->interp_to_end;
+ frame_params.emitter_velocity[0] = p_particles->emitter_velocity.x;
+ frame_params.emitter_velocity[1] = p_particles->emitter_velocity.y;
+ frame_params.emitter_velocity[2] = p_particles->emitter_velocity.z;
{ //collision and attractors
@@ -775,7 +800,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p
LocalVector<ParticleInstanceData3D> particle_vector;
particle_vector.resize(particles->amount);
particle_array = particle_vector.ptr();
- glGetBufferSubData(GL_ARRAY_BUFFER, 0, particles->amount * sizeof(ParticleInstanceData3D), particle_array);
+ godot_webgl2_glGetBufferSubData(GL_ARRAY_BUFFER, 0, particles->amount * sizeof(ParticleInstanceData3D), particle_array);
#endif
SortArray<ParticleInstanceData3D, ParticlesViewSort> sorter;
sorter.compare.z_dir = axis;
@@ -1133,7 +1158,7 @@ void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) {
LocalVector<ParticleInstanceData> particle_vector;
particle_vector.resize(particles->amount);
particle_array = particle_vector.ptr();
- glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array);
+ godot_webgl2_glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array);
#endif
uint32_t lifetime_split = (MIN(int(particles->amount * particles->sort_buffer_phase), particles->amount - 1) + 1) % particles->amount;
diff --git a/drivers/gles3/storage/particles_storage.h b/drivers/gles3/storage/particles_storage.h
index 5146dc5329..d4c4b497ff 100644
--- a/drivers/gles3/storage/particles_storage.h
+++ b/drivers/gles3/storage/particles_storage.h
@@ -128,7 +128,7 @@ private:
float delta;
float particle_size;
- float pad0;
+ float amount_ratio;
float pad1;
float pad2;
@@ -138,6 +138,8 @@ private:
uint32_t frame;
float emission_transform[16];
+ float emitter_velocity[3];
+ float interp_to_end;
Attractor attractors[MAX_ATTRACTORS];
Collider colliders[MAX_COLLIDERS];
@@ -149,6 +151,7 @@ private:
double inactive_time = 0.0;
bool emitting = false;
bool one_shot = false;
+ float amount_ratio = 1.0;
int amount = 0;
double lifetime = 1.0;
double pre_process_time = 0.0;
@@ -229,6 +232,8 @@ private:
bool clear = true;
Transform3D emission_transform;
+ Vector3 emitter_velocity;
+ float interp_to_end;
HashSet<RID> collisions;
@@ -313,6 +318,7 @@ public:
virtual void particles_emit(RID p_particles, const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) override;
virtual void particles_set_emitting(RID p_particles, bool p_emitting) override;
virtual void particles_set_amount(RID p_particles, int p_amount) override;
+ virtual void particles_set_amount_ratio(RID p_particles, float p_amount_ratio) override;
virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override;
virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override;
virtual void particles_set_pre_process_time(RID p_particles, double p_time) override;
@@ -347,6 +353,8 @@ public:
virtual AABB particles_get_aabb(RID p_particles) const override;
virtual void particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) override;
+ virtual void particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) override;
+ virtual void particles_set_interp_to_end(RID p_particles, float p_interp) override;
virtual bool particles_get_emitting(RID p_particles) override;
virtual int particles_get_draw_passes(RID p_particles) const override;
diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp
index 07df445018..7deeefc37d 100644
--- a/drivers/gles3/storage/utilities.cpp
+++ b/drivers/gles3/storage/utilities.cpp
@@ -115,7 +115,7 @@ Vector<uint8_t> Utilities::buffer_get_data(GLenum p_target, GLuint p_buffer, uin
#if defined(__EMSCRIPTEN__)
{
uint8_t *w = ret.ptrw();
- glGetBufferSubData(p_target, 0, p_buffer_size, w);
+ godot_webgl2_glGetBufferSubData(p_target, 0, p_buffer_size, w);
}
#else
void *data = glMapBufferRange(p_target, 0, p_buffer_size, GL_MAP_READ_BIT);
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index a162f46103..095991e27b 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -339,7 +339,7 @@ Error DirAccessUnix::change_dir(String p_dir) {
// prev_dir is the directory we are changing out of
String prev_dir;
char real_current_dir_name[2048];
- ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG);
+ ERR_FAIL_NULL_V(getcwd(real_current_dir_name, 2048), ERR_BUG);
if (prev_dir.parse_utf8(real_current_dir_name) != OK) {
prev_dir = real_current_dir_name; //no utf8, maybe latin?
}
@@ -361,7 +361,7 @@ Error DirAccessUnix::change_dir(String p_dir) {
String base = _get_root_path();
if (!base.is_empty() && !try_dir.begins_with(base)) {
- ERR_FAIL_COND_V(getcwd(real_current_dir_name, 2048) == nullptr, ERR_BUG);
+ ERR_FAIL_NULL_V(getcwd(real_current_dir_name, 2048), ERR_BUG);
String new_dir;
new_dir.parse_utf8(real_current_dir_name);
@@ -496,7 +496,7 @@ DirAccessUnix::DirAccessUnix() {
// set current directory to an absolute path of the current directory
char real_current_dir_name[2048];
- ERR_FAIL_COND(getcwd(real_current_dir_name, 2048) == nullptr);
+ ERR_FAIL_NULL(getcwd(real_current_dir_name, 2048));
if (current_dir.parse_utf8(real_current_dir_name) != OK) {
current_dir = real_current_dir_name;
}
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 4d9549c5a6..581daaca05 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -81,6 +81,10 @@
#include <time.h>
#include <unistd.h>
+#ifndef RTLD_DEEPBIND
+#define RTLD_DEEPBIND 0
+#endif
+
#if defined(MACOS_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)
// Random location for getentropy. Fitting.
#include <sys/random.h>
@@ -646,7 +650,7 @@ Error OS_Unix::open_dynamic_library(const String p_path, void *&p_library_handle
path = get_executable_path().get_base_dir().path_join("../lib").path_join(p_path.get_file());
}
- p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
+ p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW | RTLD_DEEPBIND);
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
if (r_resolved_path != nullptr) {
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index 5bf988afb2..890fd7277f 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -1487,7 +1487,7 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) {
#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
{ \
fp##entrypoint = (PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk" #entrypoint); \
- ERR_FAIL_COND_V_MSG(fp##entrypoint == nullptr, ERR_CANT_CREATE, \
+ ERR_FAIL_NULL_V_MSG(fp##entrypoint, ERR_CANT_CREATE, \
"vkGetInstanceProcAddr failed to find vk" #entrypoint); \
}
@@ -1689,7 +1689,7 @@ Error VulkanContext::_initialize_queues(VkSurfaceKHR p_surface) {
if (!g_gdpa) \
g_gdpa = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(inst, "vkGetDeviceProcAddr"); \
fp##entrypoint = (PFN_vk##entrypoint)g_gdpa(dev, "vk" #entrypoint); \
- ERR_FAIL_COND_V_MSG(fp##entrypoint == nullptr, ERR_CANT_CREATE, \
+ ERR_FAIL_NULL_V_MSG(fp##entrypoint, ERR_CANT_CREATE, \
"vkGetDeviceProcAddr failed to find vk" #entrypoint); \
}
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 919e335d21..c555ca2109 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -2695,18 +2695,25 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
AnimationPlayer *ap = ape->get_player();
if (ap) {
NodePath npath = animation->track_get_path(track);
- Node *nd = ap->get_node(ap->get_root_node())->get_node(NodePath(npath.get_concatenated_names()));
- StringName prop = npath.get_concatenated_subnames();
- PropertyInfo prop_info;
- ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
+ Node *a_ap_root_node = ap->get_node(ap->get_root_node());
+ Node *nd = nullptr;
+ // We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node.
+ if (a_ap_root_node) {
+ nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names()));
+ }
+ if (nd) {
+ StringName prop = npath.get_concatenated_subnames();
+ PropertyInfo prop_info;
+ ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
#ifdef DISABLE_DEPRECATED
- bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians_as_degrees") != -1;
+ bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians_as_degrees") != -1;
#else
- bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians") != -1;
+ bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians") != -1;
#endif // DISABLE_DEPRECATED
- if (is_angle) {
- menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinearAngle")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE);
- menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubicAngle")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE);
+ if (is_angle) {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("InterpLinearAngle")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE);
+ menu->add_icon_item(get_editor_theme_icon(SNAME("InterpCubicAngle")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE);
+ }
}
}
}
@@ -3903,7 +3910,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
ERR_FAIL_NULL(root);
ERR_FAIL_COND(history->get_path_size() == 0);
Object *obj = ObjectDB::get_instance(history->get_path_object(0));
- ERR_FAIL_COND(!Object::cast_to<Node>(obj));
+ ERR_FAIL_NULL(Object::cast_to<Node>(obj));
// Let's build a node path.
Node *node = Object::cast_to<Node>(obj);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 64467bc254..cd6f672b4b 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -951,6 +951,8 @@ void CodeTextEditor::_complete_request() {
font_color = get_theme_color(e.theme_color_name, SNAME("Editor"));
} else if (e.insert_text.begins_with("\"") || e.insert_text.begins_with("\'")) {
font_color = completion_string_color;
+ } else if (e.insert_text.begins_with("##") || e.insert_text.begins_with("///")) {
+ font_color = completion_doc_comment_color;
} else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) {
font_color = completion_comment_color;
}
@@ -1026,6 +1028,7 @@ void CodeTextEditor::update_editor_settings() {
completion_font_color = EDITOR_GET("text_editor/theme/highlighting/completion_font_color");
completion_string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
completion_comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
+ completion_doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
// Appearance: Caret
text_editor->set_caret_type((TextEdit::CaretType)EDITOR_GET("text_editor/appearance/caret/type").operator int());
diff --git a/editor/code_editor.h b/editor/code_editor.h
index c18154a1ef..43173bd475 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -188,6 +188,7 @@ class CodeTextEditor : public VBoxContainer {
Color completion_font_color;
Color completion_string_color;
Color completion_comment_color;
+ Color completion_doc_comment_color;
CodeTextEditorCodeCompleteFunc code_complete_func;
void *code_complete_ud = nullptr;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 662356378d..fcaec600b5 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -32,7 +32,6 @@
#include "core/config/project_settings.h"
#include "core/templates/hash_set.h"
-#include "editor/doc_tools.h"
#include "editor/editor_help.h"
#include "editor/editor_inspector.h"
#include "editor/editor_node.h"
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index 7316a770ec..2fd4778389 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -187,8 +187,7 @@ public:
//////////////////////////////////////////
-// Custom Tree needed to use a RichTextLabel as tooltip control
-// when display signal documentation.
+// Custom `Tree` needed to use `EditorHelpTooltip` to display signal documentation.
class ConnectionsDockTree : public Tree {
virtual Control *make_custom_tooltip(const String &p_text) const;
};
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 6cdcf1a6d0..ab8df824e8 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -1605,7 +1605,7 @@ void ScriptEditorDebugger::_breakpoints_item_rmb_selected(const Vector2 &p_pos,
breakpoints_menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete All Breakpoints in:") + " " + file, ACTION_DELETE_BREAKPOINTS_IN_FILE);
breakpoints_menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete All Breakpoints"), ACTION_DELETE_ALL_BREAKPOINTS);
- breakpoints_menu->set_position(breakpoints_tree->get_global_position() + p_pos);
+ breakpoints_menu->set_position(get_screen_position() + get_local_mouse_position());
breakpoints_menu->popup();
}
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 41616d50cd..db54179633 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -2510,17 +2510,13 @@ bool EditorFileSystem::_scan_extensions() {
bool needs_restart = false;
for (int i = 0; i < extensions_added.size(); i++) {
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]);
- if (st == GDExtensionManager::LOAD_STATUS_FAILED) {
- EditorNode::get_singleton()->add_io_error("Error loading extension: " + extensions_added[i]);
- } else if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
needs_restart = true;
}
}
for (int i = 0; i < extensions_removed.size(); i++) {
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
- if (st == GDExtensionManager::LOAD_STATUS_FAILED) {
- EditorNode::get_singleton()->add_io_error("Error removing extension: " + extensions_added[i]);
- } else if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
needs_restart = true;
}
}
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 6004591bb2..6e3b5b7b9e 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -2264,7 +2264,12 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control
p_rt->push_strikethrough();
pos = brk_end + 1;
tag_stack.push_front(tag);
-
+ } else if (tag == "lb") {
+ p_rt->add_text("[");
+ pos = brk_end + 1;
+ } else if (tag == "rb") {
+ p_rt->add_text("]");
+ pos = brk_end + 1;
} else if (tag == "url") {
int end = bbcode.find("[", brk_end);
if (end == -1) {
@@ -2850,7 +2855,7 @@ void EditorHelpTooltip::_notification(int p_what) {
// `p_text` is expected to be something like these:
// - `class|Control||`;
// - `property|Control|size|`;
-// - `signal|Control|gui_input|(event: InputEvent)`
+// - `signal|Control|gui_input|(event: InputEvent)`.
void EditorHelpTooltip::parse_tooltip(const String &p_text) {
tooltip_text = p_text;
@@ -2875,7 +2880,11 @@ void EditorHelpTooltip::parse_tooltip(const String &p_text) {
if (type == "property") {
description = get_property_description(class_name, property_name);
- formatted_text = TTR("Property:");
+ if (property_name.begins_with("metadata/")) {
+ formatted_text = TTR("Metadata:");
+ } else {
+ formatted_text = TTR("Property:");
+ }
} else if (type == "method") {
description = get_method_description(class_name, property_name);
formatted_text = TTR("Method:");
@@ -2890,7 +2899,8 @@ void EditorHelpTooltip::parse_tooltip(const String &p_text) {
}
}
- formatted_text += " [u][b]" + title + "[/b][/u]" + property_args + "\n";
+ // Metadata special handling replaces "Property:" with "Metadata": above.
+ formatted_text += " [u][b]" + title.trim_prefix("metadata/") + "[/b][/u]" + property_args.replace("[", "[lb]") + "\n";
formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description;
set_text(formatted_text);
}
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 395f4faa39..e1a7d8f111 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -906,12 +906,24 @@ void EditorProperty::_update_pin_flags() {
}
Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
- EditorHelpTooltip *tooltip = memnew(EditorHelpTooltip(p_text));
+ EditorHelpBit *tooltip = nullptr;
+
+ if (has_doc_tooltip) {
+ tooltip = memnew(EditorHelpTooltip(p_text));
+ }
if (object->has_method("_get_property_warning")) {
String warn = object->call("_get_property_warning", property);
if (!warn.is_empty()) {
- tooltip->set_text(tooltip->get_rich_text()->get_text() + "\n[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
+ String prev_text;
+ if (tooltip == nullptr) {
+ tooltip = memnew(EditorHelpBit());
+ tooltip->set_text(p_text);
+ tooltip->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
+ } else {
+ prev_text = tooltip->get_rich_text()->get_text() + "\n";
+ }
+ tooltip->set_text(prev_text + "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
}
}
@@ -1148,8 +1160,7 @@ void EditorInspectorCategory::_notification(int p_what) {
}
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
- // Far from perfect solution, as there's nothing that prevents a category from having a name that starts with that.
- return p_text.begins_with("class|") ? memnew(EditorHelpTooltip(p_text)) : nullptr;
+ return doc_class_name.is_empty() ? nullptr : memnew(EditorHelpTooltip(p_text));
}
Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -3316,6 +3327,7 @@ void EditorInspector::update_tree() {
} else {
ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|");
}
+ ep->has_doc_tooltip = true;
}
ep->set_doc_path(doc_path);
@@ -3391,11 +3403,16 @@ Object *EditorInspector::get_edited_object() {
return object;
}
+Object *EditorInspector::get_next_edited_object() {
+ return next_object;
+}
+
void EditorInspector::edit(Object *p_object) {
if (object == p_object) {
return;
}
+ next_object = p_object; // Some plugins need to know the next edited object when clearing the inspector.
if (object) {
_clear();
object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
@@ -3412,6 +3429,10 @@ void EditorInspector::edit(Object *p_object) {
object->connect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
update_tree();
}
+
+ // Keep it available until the end so it works with both main and sub inspectors.
+ next_object = nullptr;
+
emit_signal(SNAME("edited_object_changed"));
}
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index b5f0cec80b..e36606c080 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -74,6 +74,7 @@ private:
StringName property;
String property_path;
String doc_path;
+ bool has_doc_tooltip = false;
int property_usage;
@@ -473,6 +474,7 @@ class EditorInspector : public ScrollContainer {
void _clear(bool p_hide_plugins = true);
Object *object = nullptr;
+ Object *next_object = nullptr;
//
@@ -576,6 +578,7 @@ public:
void update_property(const String &p_prop);
void edit(Object *p_object);
Object *get_edited_object();
+ Object *get_next_edited_object();
void set_keying(bool p_active);
void set_read_only(bool p_read_only);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 0037b356d0..665e609cb0 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -790,7 +790,7 @@ void EditorNode::_notification(int p_what) {
}
void EditorNode::_update_update_spinner() {
- update_spinner->set_visible(EDITOR_GET("interface/editor/show_update_spinner"));
+ update_spinner->set_visible(!RenderingServer::get_singleton()->canvas_item_get_debug_redraw() && EDITOR_GET("interface/editor/show_update_spinner"));
const bool update_continuously = EDITOR_GET("interface/editor/update_continuously");
PopupMenu *update_popup = update_spinner->get_popup();
@@ -851,6 +851,10 @@ void EditorNode::_plugin_over_edit(EditorPlugin *p_plugin, Object *p_object) {
}
}
+void EditorNode::_plugin_over_self_own(EditorPlugin *p_plugin) {
+ active_plugins[p_plugin->get_instance_id()].insert(p_plugin);
+}
+
void EditorNode::_resources_changed(const Vector<String> &p_resources) {
List<Ref<Resource>> changed;
@@ -2135,7 +2139,12 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
for (EditorPlugin *plugin : active_plugins[owner_id]) {
if (!available_plugins.has(plugin)) {
to_remove.push_back(plugin);
- _plugin_over_edit(plugin, nullptr);
+ if (plugin->can_auto_hide()) {
+ _plugin_over_edit(plugin, nullptr);
+ } else {
+ // If plugin can't be hidden, make it own itself and become responsible for closing.
+ _plugin_over_self_own(plugin);
+ }
}
}
@@ -2151,6 +2160,12 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
continue;
}
+ if (active_plugins.has(plugin->get_instance_id())) {
+ // Plugin is already active, but as self-owning, so it needs a separate check.
+ plugin->edit(p_object);
+ continue;
+ }
+
// If plugin is already associated with another owner, remove it from there first.
for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
if (kv.key != owner_id) {
@@ -2214,7 +2229,11 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
if (p_editing_owner) {
const ObjectID id = p_editing_owner->get_instance_id();
for (EditorPlugin *plugin : active_plugins[id]) {
- _plugin_over_edit(plugin, nullptr);
+ if (plugin->can_auto_hide()) {
+ _plugin_over_edit(plugin, nullptr);
+ } else {
+ _plugin_over_self_own(plugin);
+ }
}
active_plugins.erase(id);
} else {
@@ -2222,10 +2241,23 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
// This is to sweep properties that were removed from the inspector.
List<ObjectID> to_remove;
for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
- if (!ObjectDB::get_instance(kv.key)) {
+ const Object *context = ObjectDB::get_instance(kv.key);
+ if (context) {
+ // In case of self-owning plugins, they are disabled here if they can auto hide.
+ const EditorPlugin *self_owning = Object::cast_to<EditorPlugin>(context);
+ if (self_owning && self_owning->can_auto_hide()) {
+ context = nullptr;
+ }
+ }
+
+ if (!context) {
to_remove.push_back(kv.key);
for (EditorPlugin *plugin : kv.value) {
- _plugin_over_edit(plugin, nullptr);
+ if (plugin->can_auto_hide()) {
+ _plugin_over_edit(plugin, nullptr);
+ } else {
+ _plugin_over_self_own(plugin);
+ }
}
}
}
@@ -3700,16 +3732,6 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
Error err;
Ref<PackedScene> sdata = ResourceLoader::load(lpath, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
- if (!sdata.is_valid()) {
- _dialog_display_load_error(lpath, err);
- opening_prev = false;
-
- if (prev != -1) {
- _set_current_scene(prev);
- editor_data.remove_scene(idx);
- }
- return ERR_FILE_NOT_FOUND;
- }
if (!p_ignore_broken_deps && dependency_errors.has(lpath)) {
current_menu_option = -1;
@@ -3727,6 +3749,17 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
return ERR_FILE_MISSING_DEPENDENCIES;
}
+ if (!sdata.is_valid()) {
+ _dialog_display_load_error(lpath, err);
+ opening_prev = false;
+
+ if (prev != -1) {
+ _set_current_scene(prev);
+ editor_data.remove_scene(idx);
+ }
+ return ERR_FILE_NOT_FOUND;
+ }
+
dependency_errors.erase(lpath); // At least not self path.
for (KeyValue<String, HashSet<String>> &E : dependency_errors) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 72134e283b..4e36c19e96 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -550,6 +550,7 @@ private:
void _remove_plugin_from_enabled(const String &p_name);
void _plugin_over_edit(EditorPlugin *p_plugin, Object *p_object);
+ void _plugin_over_self_own(EditorPlugin *p_plugin);
void _fs_changed();
void _resources_reimported(const Vector<String> &p_resources);
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 2726a781b4..b43e7eba9c 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -335,6 +335,10 @@ bool EditorPlugin::handles(Object *p_object) const {
return success;
}
+bool EditorPlugin::can_auto_hide() const {
+ return true;
+}
+
Dictionary EditorPlugin::get_state() const {
Dictionary state;
GDVIRTUAL_CALL(_get_state, state);
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 62ed432ecc..8870ef425e 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -178,6 +178,7 @@ public:
virtual void selected_notify() {} //notify that it was raised by the user, not the editor
virtual void edit(Object *p_object);
virtual bool handles(Object *p_object) const;
+ virtual bool can_auto_hide() const;
virtual Dictionary get_state() const; //save editor state so it can't be reloaded when reloading scene
virtual void set_state(const Dictionary &p_state); //restore editor state (likely was saved with the scene)
virtual void clear(); // clear any temporary data in the editor, reset it (likely new scene or load another scene)
diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp
index 42cd858581..00db344b6a 100644
--- a/editor/editor_run.cpp
+++ b/editor/editor_run.cpp
@@ -110,6 +110,8 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
bool debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false);
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
bool debug_avoidance = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_avoidance", false);
+ bool debug_canvas_redraw = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_canvas_redraw", false);
+
if (debug_collisions) {
args.push_back("--debug-collisions");
}
@@ -126,6 +128,10 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
args.push_back("--debug-avoidance");
}
+ if (debug_canvas_redraw) {
+ args.push_back("--debug-canvas-item-redraw");
+ }
+
if (p_write_movie != "") {
args.push_back("--write-movie");
args.push_back(p_write_movie);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 89a94fe0da..1eaeee97a5 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -519,6 +519,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
+ // Tools (denoise)
+ EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/tools/oidn/oidn_denoise_path", "", "", PROPERTY_USAGE_DEFAULT)
+
/* Docks */
// SceneTree
@@ -821,6 +824,7 @@ void EditorSettings::_load_godot2_text_editor_theme() {
_initial_set("text_editor/theme/highlighting/engine_type_color", Color(0.51, 0.83, 1.0));
_initial_set("text_editor/theme/highlighting/user_type_color", Color(0.42, 0.67, 0.93));
_initial_set("text_editor/theme/highlighting/comment_color", Color(0.4, 0.4, 0.4));
+ _initial_set("text_editor/theme/highlighting/doc_comment_color", Color(0.5, 0.6, 0.7));
_initial_set("text_editor/theme/highlighting/string_color", Color(0.94, 0.43, 0.75));
_initial_set("text_editor/theme/highlighting/background_color", Color(0.13, 0.12, 0.15));
_initial_set("text_editor/theme/highlighting/completion_background_color", Color(0.17, 0.16, 0.2));
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 0e7525875d..f5be91c6de 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -2215,7 +2215,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// adaptive script theme constants
// for comments and elements with lower relevance
- const Color dim_color = Color(font_color.r, font_color.g, font_color.b, 0.5);
+ const Color dim_color = Color(font_color, 0.5);
const float mono_value = mono_color.r;
const Color alpha1 = Color(mono_value, mono_value, mono_value, 0.07);
@@ -2229,6 +2229,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
const Color engine_type_color = dark_theme ? Color(0.56, 1, 0.86) : Color(0.11, 0.55, 0.4);
const Color user_type_color = dark_theme ? Color(0.78, 1, 0.93) : Color(0.18, 0.45, 0.4);
const Color comment_color = dark_theme ? dim_color : Color(0.08, 0.08, 0.08, 0.5);
+ const Color doc_comment_color = dark_theme ? Color(0.6, 0.7, 0.8, 0.8) : Color(0.15, 0.15, 0.4, 0.7);
const Color string_color = dark_theme ? Color(1, 0.93, 0.63) : Color(0.6, 0.42, 0);
// Use the brightest background color on a light theme (which generally uses a negative contrast rate).
@@ -2272,6 +2273,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
setting->set_initial_value("text_editor/theme/highlighting/engine_type_color", engine_type_color, true);
setting->set_initial_value("text_editor/theme/highlighting/user_type_color", user_type_color, true);
setting->set_initial_value("text_editor/theme/highlighting/comment_color", comment_color, true);
+ setting->set_initial_value("text_editor/theme/highlighting/doc_comment_color", doc_comment_color, true);
setting->set_initial_value("text_editor/theme/highlighting/string_color", string_color, true);
setting->set_initial_value("text_editor/theme/highlighting/background_color", te_background_color, true);
setting->set_initial_value("text_editor/theme/highlighting/completion_background_color", completion_background_color, true);
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 3a034c8dcc..26ed7c46fb 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -686,7 +686,7 @@ Error ExportTemplateManager::install_android_template_from_file(const String &p_
zlib_filefunc_def io = zipio_create_io(&io_fa);
unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
- ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
+ ERR_FAIL_NULL_V_MSG(pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
int ret = unzGoToFirstFile(pkg);
int total_files = 0;
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index be06a3932c..fb4f5efc25 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1729,12 +1729,12 @@ void FileSystemDock::_folder_removed(String p_folder) {
void FileSystemDock::_rename_operation_confirm() {
String new_name;
- TreeItem *s = tree->get_selected();
- int col_index = tree->get_selected_column();
+ TreeItem *ti = tree->get_edited();
+ int col_index = tree->get_edited_column();
- if (tree->has_focus()) {
- new_name = s->get_text(col_index).strip_edges();
- } else if (files->has_focus()) {
+ if (ti) {
+ new_name = ti->get_text(col_index).strip_edges();
+ } else {
new_name = files->get_edit_text().strip_edges();
}
String old_name = to_rename.is_file ? to_rename.path.get_file() : to_rename.path.left(-1).get_file();
@@ -1757,10 +1757,10 @@ void FileSystemDock::_rename_operation_confirm() {
}
// Restore original name.
- if (rename_error && tree->has_focus()) {
- s->set_text(col_index, old_name);
- return;
- } else if (rename_error && files->has_focus()) {
+ if (rename_error) {
+ if (ti) {
+ ti->set_text(col_index, old_name);
+ }
return;
}
@@ -1783,7 +1783,7 @@ void FileSystemDock::_rename_operation_confirm() {
if (da->file_exists(new_path) || da->dir_exists(new_path)) {
#endif
EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists."));
- s->set_text(col_index, old_name);
+ ti->set_text(col_index, old_name);
return;
}
@@ -1803,7 +1803,7 @@ void FileSystemDock::_rename_operation_confirm() {
EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
- if (tree->has_focus()) {
+ if (ti) {
current_path = new_path;
current_path_line_edit->set_text(current_path);
}
@@ -2004,13 +2004,13 @@ void FileSystemDock::_before_move(HashMap<String, ResourceUID::ID> &r_uids, Vect
EditorNode::get_singleton()->save_scene_list(r_file_owners);
}
-Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion) const {
+Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion, bool p_include_unselected_cursor) const {
// Build a list of selected items with the active one at the first position.
Vector<String> selected_strings;
TreeItem *favorites_item = tree->get_root()->get_first_child();
TreeItem *cursor_item = tree->get_selected();
- if (cursor_item && cursor_item->is_selected(0) && cursor_item != favorites_item) {
+ if (cursor_item && (p_include_unselected_cursor || cursor_item->is_selected(0)) && cursor_item != favorites_item) {
selected_strings.push_back(cursor_item->get_metadata(0));
}
@@ -2059,6 +2059,10 @@ void FileSystemDock::_tree_rmb_option(int p_option) {
tree->get_selected()->set_collapsed_recursive(p_option == FOLDER_COLLAPSE_ALL);
}
} break;
+ case FILE_RENAME: {
+ selected_strings = _tree_get_selected(false, true);
+ [[fallthrough]];
+ }
default: {
_file_option(p_option, selected_strings);
} break;
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 1e04b6a4ff..104def71c8 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -351,7 +351,7 @@ private:
void _update_display_mode(bool p_force = false);
- Vector<String> _tree_get_selected(bool remove_self_inclusion = true) const;
+ Vector<String> _tree_get_selected(bool remove_self_inclusion = true, bool p_include_unselected_cursor = false) const;
bool _is_file_type_disabled_by_feature_profile(const StringName &p_class);
diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp
index 5506f4a6bc..a14df01858 100644
--- a/editor/find_in_files.cpp
+++ b/editor/find_in_files.cpp
@@ -604,6 +604,8 @@ FindInFilesPanel::FindInFilesPanel() {
_results_display->set_select_mode(Tree::SELECT_ROW);
_results_display->set_allow_rmb_select(true);
_results_display->set_allow_reselect(true);
+ _results_display->add_theme_constant_override("inner_item_margin_left", 0);
+ _results_display->add_theme_constant_override("inner_item_margin_right", 0);
_results_display->create_item(); // Root
vbc->add_child(_results_display);
diff --git a/editor/gui/editor_toaster.cpp b/editor/gui/editor_toaster.cpp
index 0d468bc0a3..1abb591508 100644
--- a/editor/gui/editor_toaster.cpp
+++ b/editor/gui/editor_toaster.cpp
@@ -149,7 +149,7 @@ void EditorToaster::_notification(int p_what) {
void EditorToaster::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
// This may be called from a thread. Since we will deal with non-thread-safe elements,
// we have to put it in the queue for safety.
- callable_mp_static(&EditorToaster::_error_handler_impl).bind(p_file, p_line, p_error, p_errorexp, p_editor_notify, p_type).call_deferred();
+ callable_mp_static(&EditorToaster::_error_handler_impl).bind(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type).call_deferred();
}
void EditorToaster::_error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type) {
diff --git a/editor/gui/editor_zoom_widget.cpp b/editor/gui/editor_zoom_widget.cpp
index e292dc99ac..5c936a75f2 100644
--- a/editor/gui/editor_zoom_widget.cpp
+++ b/editor/gui/editor_zoom_widget.cpp
@@ -70,12 +70,36 @@ float EditorZoomWidget::get_zoom() {
}
void EditorZoomWidget::set_zoom(float p_zoom) {
- if (p_zoom > 0 && p_zoom != zoom) {
- zoom = p_zoom;
+ float new_zoom = CLAMP(p_zoom, min_zoom, max_zoom);
+ if (zoom != new_zoom) {
+ zoom = new_zoom;
_update_zoom_label();
}
}
+float EditorZoomWidget::get_min_zoom() {
+ return min_zoom;
+}
+
+float EditorZoomWidget::get_max_zoom() {
+ return max_zoom;
+}
+
+void EditorZoomWidget::setup_zoom_limits(float p_min, float p_max) {
+ ERR_FAIL_COND(p_min < 0 || p_min > p_max);
+
+ min_zoom = p_min;
+ max_zoom = p_max;
+
+ if (zoom > max_zoom) {
+ set_zoom(max_zoom);
+ emit_signal(SNAME("zoom_changed"), zoom);
+ } else if (zoom < min_zoom) {
+ set_zoom(min_zoom);
+ emit_signal(SNAME("zoom_changed"), zoom);
+ }
+}
+
void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_integer_only) {
// Remove editor scale from the index computation.
const float zoom_noscale = zoom / MAX(1, EDSCALE);
@@ -97,7 +121,7 @@ void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_inte
}
} else {
if (p_increment_count >= 1) {
- // Zooming. Convert the current zoom into a denominator.
+ // Zooming in. Convert the current zoom into a denominator.
float new_zoom = 1.0 / Math::ceil(1.0 / zoom_noscale - p_increment_count);
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
// New zoom is identical to the old zoom, so try again.
@@ -106,7 +130,7 @@ void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_inte
}
set_zoom(new_zoom * MAX(1, EDSCALE));
} else {
- // Dezooming. Convert the current zoom into a denominator.
+ // Zooming out. Convert the current zoom into a denominator.
float new_zoom = 1.0 / Math::floor(1.0 / zoom_noscale - p_increment_count);
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
// New zoom is identical to the old zoom, so try again.
@@ -118,9 +142,9 @@ void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_inte
}
} else {
// Base increment factor defined as the twelveth root of two.
- // This allow a smooth geometric evolution of the zoom, with the advantage of
+ // This allows for a smooth geometric evolution of the zoom, with the advantage of
// visiting all integer power of two scale factors.
- // note: this is analogous to the 'semitones' interval in the music world
+ // Note: this is analogous to the 'semitone' interval in the music world
// In order to avoid numerical imprecisions, we compute and edit a zoom index
// with the following relation: zoom = 2 ^ (index / 12)
@@ -179,10 +203,11 @@ EditorZoomWidget::EditorZoomWidget() {
zoom_reset = memnew(Button);
zoom_reset->set_flat(true);
- zoom_reset->add_theme_style_override("normal", memnew(StyleBoxEmpty));
- zoom_reset->add_theme_style_override("hover", memnew(StyleBoxEmpty));
- zoom_reset->add_theme_style_override("focus", memnew(StyleBoxEmpty));
- zoom_reset->add_theme_style_override("pressed", memnew(StyleBoxEmpty));
+ Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
+ zoom_reset->add_theme_style_override("normal", empty_stylebox);
+ zoom_reset->add_theme_style_override("hover", empty_stylebox);
+ zoom_reset->add_theme_style_override("focus", empty_stylebox);
+ zoom_reset->add_theme_style_override("pressed", empty_stylebox);
add_child(zoom_reset);
zoom_reset->add_theme_constant_override("outline_size", Math::ceil(2 * EDSCALE));
zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
diff --git a/editor/gui/editor_zoom_widget.h b/editor/gui/editor_zoom_widget.h
index be54043d93..6b2fe4d3e9 100644
--- a/editor/gui/editor_zoom_widget.h
+++ b/editor/gui/editor_zoom_widget.h
@@ -42,6 +42,8 @@ class EditorZoomWidget : public HBoxContainer {
Button *zoom_plus = nullptr;
float zoom = 1.0;
+ float min_zoom = 1.0 / 128;
+ float max_zoom = 128.0;
void _update_zoom_label();
void _button_zoom_minus();
void _button_zoom_reset();
@@ -57,6 +59,11 @@ public:
float get_zoom();
void set_zoom(float p_zoom);
void set_zoom_by_increments(int p_increment_count, bool p_integer_only = false);
+
+ float get_min_zoom();
+ float get_max_zoom();
+ // It's best to setup simultaneously, so min < max can be checked easily.
+ void setup_zoom_limits(float p_min, float p_max);
// Sets the shortcut context for the zoom buttons. By default their context is this EditorZoomWidget control.
void set_shortcut_context(Node *p_node) const;
};
diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp
index d27b0aea40..be63973ea7 100644
--- a/editor/import/collada.cpp
+++ b/editor/import/collada.cpp
@@ -2197,7 +2197,7 @@ bool Collada::_move_geometry_to_skeletons(VisualScene *p_vscene, Node *p_node, L
ERR_FAIL_COND_V(!state.scene_map.has(nodeid), false); //weird, it should have it...
NodeJoint *nj = dynamic_cast<NodeJoint *>(state.scene_map[nodeid]);
ERR_FAIL_NULL_V(nj, false);
- ERR_FAIL_COND_V(!nj->owner, false); //weird, node should have a skeleton owner
+ ERR_FAIL_NULL_V(nj->owner, false); // Weird, node should have a skeleton owner.
NodeSkeleton *sk = nj->owner;
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
index cd31499b80..3390bf4ed4 100644
--- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
@@ -242,6 +242,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
if (rot_track == -1) {
int track = anim->add_track(Animation::TYPE_ROTATION_3D);
anim->track_set_path(track, insert_path);
+ anim->track_set_imported(track, true);
anim->rotation_track_insert_key(track, 0, src_skeleton->get_bone_rest(src_idx).basis.get_rotation_quaternion());
}
}
diff --git a/editor/import/resource_importer_shader_file.cpp b/editor/import/resource_importer_shader_file.cpp
index 1275e5b85a..bde2e3d0bf 100644
--- a/editor/import/resource_importer_shader_file.cpp
+++ b/editor/import/resource_importer_shader_file.cpp
@@ -96,7 +96,7 @@ Error ResourceImporterShaderFile::import(const String &p_source_file, const Stri
Error err;
Ref<FileAccess> file = FileAccess::open(p_source_file, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_CANT_OPEN);
- ERR_FAIL_COND_V(!file.operator->(), ERR_CANT_OPEN);
+ ERR_FAIL_COND_V(!file.is_valid(), ERR_CANT_OPEN);
String file_txt = file->get_as_utf8_string();
Ref<RDShaderFile> shader_file;
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 4bfa6adaae..55d45fdd2e 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -60,11 +60,6 @@
#include "scene/resources/packed_scene.h"
#include "scene/resources/style_box_texture.h"
-// Min and Max are power of two in order to play nicely with successive increment.
-// That way, we can naturally reach a 100% zoom from boundaries.
-constexpr real_t MIN_ZOOM = 1. / 128;
-constexpr real_t MAX_ZOOM = 128;
-
#define RULER_WIDTH (15 * EDSCALE)
constexpr real_t SCALE_HANDLE_DISTANCE = 25;
constexpr real_t MOVE_HANDLE_DISTANCE = 25;
@@ -4115,10 +4110,9 @@ void CanvasItemEditor::_update_scroll(real_t) {
}
void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
- p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
+ p_zoom = CLAMP(p_zoom, zoom_widget->get_min_zoom(), zoom_widget->get_max_zoom());
if (p_zoom == zoom) {
- zoom_widget->set_zoom(p_zoom);
return;
}
@@ -4128,12 +4122,12 @@ void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
view_offset += p_position / prev_zoom - p_position / zoom;
// We want to align in-scene pixels to screen pixels, this prevents blurry rendering
- // in small details (texts, lines).
+ // of small details (texts, lines).
// This correction adds a jitter movement when zooming, so we correct only when the
// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
const real_t closest_zoom_factor = Math::round(zoom);
if (Math::is_zero_approx(zoom - closest_zoom_factor)) {
- // make sure scene pixel at view_offset is aligned on a screen pixel
+ // Make sure scene pixel at view_offset is aligned on a screen pixel.
Vector2 view_offset_int = view_offset.floor();
Vector2 view_offset_frac = view_offset - view_offset_int;
view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
@@ -5147,9 +5141,9 @@ CanvasItemEditor::CanvasItemEditor() {
button_center_view->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(VIEW_CENTER_TO_SELECTION));
zoom_widget = memnew(EditorZoomWidget);
- controls_hb->add_child(zoom_widget);
zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
zoom_widget->set_shortcut_context(this);
+ controls_hb->add_child(zoom_widget);
zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));
panner.instantiate();
diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp
index 15829a55de..b636ec4f5c 100644
--- a/editor/plugins/debugger_editor_plugin.cpp
+++ b/editor/plugins/debugger_editor_plugin.cpp
@@ -79,6 +79,10 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, avoidance objects shapes, radius and velocities will be visible in the running project."));
debug_menu->add_separator();
+ debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_canvas_redraw", TTR("Debug CanvasItem Redraws")), RUN_DEBUG_CANVAS_REDRAW);
+ debug_menu->set_item_tooltip(-1,
+ TTR("When this option is enabled, redraw requests of 2D objects will become visible (as a short flash) in the running project.\nThis is useful to troubleshoot low processor mode."));
+ debug_menu->add_separator();
debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG);
debug_menu->set_item_tooltip(-1,
TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled."));
@@ -175,6 +179,12 @@ void DebuggerEditorPlugin::_menu_option(int p_option) {
EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_avoidance", !ischecked);
} break;
+ case RUN_DEBUG_CANVAS_REDRAW: {
+ bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_CANVAS_REDRAW));
+ debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_CANVAS_REDRAW), !ischecked);
+ EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_canvas_redraw", !ischecked);
+
+ } break;
case RUN_RELOAD_SCRIPTS: {
bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS));
debug_menu->set_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked);
@@ -213,6 +223,7 @@ void DebuggerEditorPlugin::_update_debug_options() {
bool check_debug_paths = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_paths", false);
bool check_debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
bool check_debug_avoidance = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_avoidance", false);
+ bool check_debug_canvas_redraw = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_canvas_redraw", false);
bool check_live_debug = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_live_debug", true);
bool check_reload_scripts = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_reload_scripts", true);
bool check_server_keep_open = EditorSettings::get_singleton()->get_project_metadata("debug_options", "server_keep_open", false);
@@ -236,6 +247,9 @@ void DebuggerEditorPlugin::_update_debug_options() {
if (check_debug_avoidance) {
_menu_option(RUN_DEBUG_AVOIDANCE);
}
+ if (check_debug_canvas_redraw) {
+ _menu_option(RUN_DEBUG_CANVAS_REDRAW);
+ }
if (check_live_debug) {
_menu_option(RUN_LIVE_DEBUG);
}
diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h
index eb8da7ca8e..8d65dbd2e4 100644
--- a/editor/plugins/debugger_editor_plugin.h
+++ b/editor/plugins/debugger_editor_plugin.h
@@ -52,6 +52,7 @@ private:
RUN_DEBUG_PATHS,
RUN_DEBUG_NAVIGATION,
RUN_DEBUG_AVOIDANCE,
+ RUN_DEBUG_CANVAS_REDRAW,
RUN_DEPLOY_REMOTE_DEBUG,
RUN_RELOAD_SCRIPTS,
SERVER_KEEP_OPEN,
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 1872857130..afc72cdde6 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -496,6 +496,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from,
Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color");
Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
+ Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
if (bg_color.a == 0) {
bg_color = Color(0, 0, 0, 0);
@@ -513,6 +514,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from,
bool in_control_flow_keyword = false;
bool in_keyword = false;
bool in_comment = false;
+ bool in_doc_comment = false;
for (int i = 0; i < code.length(); i++) {
char32_t c = code[i];
if (c > 32) {
@@ -520,11 +522,17 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from,
Color color = text_color;
if (c == '#') {
- in_comment = true;
+ if (i < code.length() - 1 && code[i + 1] == '#') {
+ in_doc_comment = true;
+ } else {
+ in_comment = true;
+ }
}
if (in_comment) {
color = comment_color;
+ } else if (in_doc_comment) {
+ color = doc_comment_color;
} else {
if (is_symbol(c)) {
//make symbol a little visible
@@ -569,6 +577,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from,
if (c == '\n') {
in_comment = false;
+ in_doc_comment = false;
col = x0;
line++;
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index ec927f6c5d..79e260ef35 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -191,6 +191,16 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
highlighter->add_color_region(beg, end, comment_color, end.is_empty());
}
+ /* Doc comments */
+ const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
+ List<String> doc_comments;
+ scr->get_language()->get_doc_comment_delimiters(&doc_comments);
+ for (const String &doc_comment : doc_comments) {
+ String beg = doc_comment.get_slice(" ", 0);
+ String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String();
+ highlighter->add_color_region(beg, end, doc_comment_color, end.is_empty());
+ }
+
/* Strings */
const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
List<String> strings;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 5322f3c813..5b67c6d509 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -234,9 +234,10 @@ void ScriptTextEditor::_set_theme_for_script() {
}
}
+ text_edit->clear_comment_delimiters();
+
List<String> comments;
script->get_language()->get_comment_delimiters(&comments);
- text_edit->clear_comment_delimiters();
for (const String &comment : comments) {
String beg = comment.get_slice(" ", 0);
String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
@@ -246,6 +247,18 @@ void ScriptTextEditor::_set_theme_for_script() {
text_edit->add_auto_brace_completion_pair(beg, end);
}
}
+
+ List<String> doc_comments;
+ script->get_language()->get_doc_comment_delimiters(&doc_comments);
+ for (const String &doc_comment : doc_comments) {
+ String beg = doc_comment.get_slice(" ", 0);
+ String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String();
+ text_edit->add_comment_delimiter(beg, end, end.is_empty());
+
+ if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {
+ text_edit->add_auto_brace_completion_pair(beg, end);
+ }
+ }
}
void ScriptTextEditor::_show_errors_panel(bool p_show) {
@@ -779,7 +792,7 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo
return;
}
- ERR_FAIL_COND(!get_tree());
+ ERR_FAIL_NULL(get_tree());
HashSet<Ref<Script>> scripts;
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index e6bb5532a3..20b91d8bfd 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -363,7 +363,7 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
void Skeleton3DEditor::create_physical_skeleton() {
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ERR_FAIL_COND(!get_tree());
+ ERR_FAIL_NULL(get_tree());
Node *owner = get_tree()->get_edited_scene_root();
const int bone_count = skeleton->get_bone_count();
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 821d8151a4..9ba5f2faf1 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -31,12 +31,14 @@
#include "theme_editor_plugin.h"
#include "core/os/keyboard.h"
+#include "editor/editor_help.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_picker.h"
#include "editor/editor_scale.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
+#include "editor/inspector_dock.h"
#include "editor/progress_dialog.h"
#include "scene/gui/check_button.h"
#include "scene/gui/color_picker.h"
@@ -2259,6 +2261,10 @@ ThemeTypeDialog::ThemeTypeDialog() {
///////////////////////
+Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const {
+ return memnew(EditorHelpTooltip(p_text));
+}
+
VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) {
VBoxContainer *items_tab = memnew(VBoxContainer);
items_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE);
@@ -2413,11 +2419,13 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_
item_name_container->set_stretch_ratio(2.0);
item_control->add_child(item_name_container);
- Label *item_name = memnew(Label);
+ Label *item_name = memnew(ThemeItemLabel);
item_name->set_h_size_flags(SIZE_EXPAND_FILL);
item_name->set_clip_text(true);
item_name->set_text(p_item_name);
- item_name->set_tooltip_text(p_item_name);
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name + "|");
+ item_name->set_mouse_filter(Control::MOUSE_FILTER_STOP);
item_name_container->add_child(item_name);
if (p_editable) {
@@ -2477,7 +2485,6 @@ void ThemeTypeEditor::_add_focusable(Control *p_control) {
void ThemeTypeEditor::_update_type_items() {
bool show_default = show_default_items_button->is_pressed();
- List<StringName> names;
focusables.clear();
@@ -3528,6 +3535,16 @@ void ThemeEditor::_theme_edit_button_cbk() {
theme_edit_dialog->popup_centered(Size2(850, 700) * EDSCALE);
}
+void ThemeEditor::_theme_close_button_cbk() {
+ plugin->make_visible(false); // Enables auto hide.
+ if (theme.is_valid() && InspectorDock::get_inspector_singleton()->get_edited_object() == theme.ptr()) {
+ EditorNode::get_singleton()->push_item(nullptr);
+ } else {
+ theme = Ref<Theme>();
+ EditorNode::get_singleton()->hide_unused_editors(plugin);
+ }
+}
+
void ThemeEditor::_add_preview_button_cbk() {
preview_scene_dialog->popup_file_dialog();
}
@@ -3645,6 +3662,12 @@ ThemeEditor::ThemeEditor() {
theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk).bind(true));
top_menu->add_child(theme_save_as_button);
+ Button *theme_close_button = memnew(Button);
+ theme_close_button->set_text(TTR("Close"));
+ theme_close_button->set_flat(true);
+ theme_close_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_close_button_cbk));
+ top_menu->add_child(theme_close_button);
+
top_menu->add_child(memnew(VSeparator));
Button *theme_edit_button = memnew(Button);
@@ -3711,20 +3734,12 @@ ThemeEditor::ThemeEditor() {
///////////////////////
-void ThemeEditorPlugin::edit(Object *p_node) {
- if (Object::cast_to<Theme>(p_node)) {
- theme_editor->edit(Object::cast_to<Theme>(p_node));
- } else {
- // We intentionally keep a reference to the last used theme to work around
- // the the editor being hidden while base resources are edited. Uncomment
- // the following line again and remove this comment once that bug has been
- // fixed (scheduled for Godot 4.1 in PR 73098):
- // theme_editor->edit(Ref<Theme>());
- }
+void ThemeEditorPlugin::edit(Object *p_object) {
+ theme_editor->edit(Ref<Theme>(p_object));
}
-bool ThemeEditorPlugin::handles(Object *p_node) const {
- return Object::cast_to<Theme>(p_node) != nullptr;
+bool ThemeEditorPlugin::handles(Object *p_object) const {
+ return Object::cast_to<Theme>(p_object) != nullptr;
}
void ThemeEditorPlugin::make_visible(bool p_visible) {
@@ -3740,8 +3755,77 @@ void ThemeEditorPlugin::make_visible(bool p_visible) {
}
}
+bool ThemeEditorPlugin::can_auto_hide() const {
+ Ref<Theme> edited_theme = theme_editor->theme;
+ if (edited_theme.is_null()) {
+ return true;
+ }
+
+ Ref<Resource> edited_resource = Ref<Resource>(InspectorDock::get_inspector_singleton()->get_next_edited_object());
+ if (edited_resource.is_null()) {
+ return true;
+ }
+
+ // Don't hide if edited resource used by this theme.
+ Ref<StyleBox> sbox = edited_resource;
+ if (sbox.is_valid()) {
+ List<StringName> type_list;
+ edited_theme->get_stylebox_type_list(&type_list);
+
+ for (const StringName &E : type_list) {
+ List<StringName> list;
+ edited_theme->get_stylebox_list(E, &list);
+
+ for (const StringName &F : list) {
+ if (edited_theme->get_stylebox(F, E) == sbox) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ Ref<Texture2D> tex = edited_resource;
+ if (tex.is_valid()) {
+ List<StringName> type_list;
+ edited_theme->get_icon_type_list(&type_list);
+
+ for (const StringName &E : type_list) {
+ List<StringName> list;
+ edited_theme->get_icon_list(E, &list);
+
+ for (const StringName &F : list) {
+ if (edited_theme->get_icon(F, E) == tex) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ Ref<Font> fnt = edited_resource;
+ if (fnt.is_valid()) {
+ List<StringName> type_list;
+ edited_theme->get_font_type_list(&type_list);
+
+ for (const StringName &E : type_list) {
+ List<StringName> list;
+ edited_theme->get_font_list(E, &list);
+
+ for (const StringName &F : list) {
+ if (edited_theme->get_font(F, E) == fnt) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return true;
+}
+
ThemeEditorPlugin::ThemeEditorPlugin() {
theme_editor = memnew(ThemeEditor);
+ theme_editor->plugin = this;
theme_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE);
button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Theme"), theme_editor);
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index 077ce8e8f7..33accf587a 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -47,6 +47,7 @@ class OptionButton;
class PanelContainer;
class TabBar;
class TabContainer;
+class ThemeEditorPlugin;
class TextureRect;
class ThemeItemImportTree : public VBoxContainer {
@@ -320,6 +321,11 @@ public:
ThemeTypeDialog();
};
+// Custom `Label` needed to use `EditorHelpTooltip` to display theme item documentation.
+class ThemeItemLabel : public Label {
+ virtual Control *make_custom_tooltip(const String &p_text) const;
+};
+
class ThemeTypeEditor : public MarginContainer {
GDCLASS(ThemeTypeEditor, MarginContainer);
@@ -419,6 +425,9 @@ public:
class ThemeEditor : public VBoxContainer {
GDCLASS(ThemeEditor, VBoxContainer);
+ friend class ThemeEditorPlugin;
+ ThemeEditorPlugin *plugin = nullptr;
+
Ref<Theme> theme;
TabBar *preview_tabs = nullptr;
@@ -433,6 +442,7 @@ class ThemeEditor : public VBoxContainer {
void _theme_save_button_cbk(bool p_save_as);
void _theme_edit_button_cbk();
+ void _theme_close_button_cbk();
void _add_preview_button_cbk();
void _preview_scene_dialog_cbk(const String &p_path);
@@ -462,9 +472,10 @@ class ThemeEditorPlugin : public EditorPlugin {
public:
virtual String get_name() const override { return "Theme"; }
bool has_main_screen() const override { return false; }
- virtual void edit(Object *p_node) override;
- virtual bool handles(Object *p_node) const override;
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
virtual void make_visible(bool p_visible) override;
+ virtual bool can_auto_hide() const override;
ThemeEditorPlugin();
};
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
index b55be8b3f8..039213e545 100644
--- a/editor/plugins/tiles/tile_atlas_view.cpp
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -90,8 +90,6 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() {
}
void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) {
- // Don't allow zoom to go below 1% or above 10000%
- zoom_widget->set_zoom(CLAMP(zoom_widget->get_zoom(), 0.01f, 100.f));
float zoom = zoom_widget->get_zoom();
// Compute the minimum sizes.
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index d634d957b3..ad4844fe3e 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -361,8 +361,8 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) {
}
undo_redo->add_do_method(base_control, "queue_redraw");
undo_redo->add_do_method(this, "emit_signal", "polygons_changed");
- for (const PackedVector2Array &polygon : polygons) {
- undo_redo->add_undo_method(this, "set_polygon", polygon);
+ for (unsigned int i = 0; i < polygons.size(); i++) {
+ undo_redo->add_undo_method(this, "set_polygon", i, polygons[i]);
}
undo_redo->add_undo_method(base_control, "queue_redraw");
undo_redo->add_undo_method(this, "emit_signal", "polygons_changed");
@@ -931,6 +931,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
snap_subdivision->connect("value_changed", callable_mp(this, &GenericTilePolygonEditor::_store_snap_options).unbind(1));
editor_zoom_widget = memnew(EditorZoomWidget);
+ editor_zoom_widget->setup_zoom_limits(0.125, 128.0);
editor_zoom_widget->set_position(Vector2(5, 5));
editor_zoom_widget->connect("zoom_changed", callable_mp(this, &GenericTilePolygonEditor::_zoom_changed).unbind(1));
editor_zoom_widget->set_shortcut_context(this);
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index afc929b547..6bac62d861 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -44,7 +44,7 @@
#include "scene/gui/separator.h"
#define CHECK_PLUGIN_INITIALIZED() \
- ERR_FAIL_COND_MSG(!EditorVCSInterface::get_singleton(), "No VCS plugin is initialized. Select a Version Control Plugin from Project menu.");
+ ERR_FAIL_NULL_MSG(EditorVCSInterface::get_singleton(), "No VCS plugin is initialized. Select a Version Control Plugin from Project menu.");
VersionControlEditorPlugin *VersionControlEditorPlugin::singleton = nullptr;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index e7fe9a353c..b04773d371 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -397,7 +397,7 @@ void ProjectDialog::_nonempty_confirmation_ok_pressed() {
}
void ProjectDialog::_renderer_selected() {
- ERR_FAIL_COND(!renderer_button_group->get_pressed_button());
+ ERR_FAIL_NULL(renderer_button_group->get_pressed_button());
String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method"));
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index e7727aec4b..a38487635f 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -651,8 +651,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
Node *top_node = selection[i];
Node *bottom_node = selection[selection.size() - 1 - i];
- ERR_FAIL_COND(!top_node->get_parent());
- ERR_FAIL_COND(!bottom_node->get_parent());
+ ERR_FAIL_NULL(top_node->get_parent());
+ ERR_FAIL_NULL(bottom_node->get_parent());
int bottom_node_pos = bottom_node->get_index(false);
int top_node_pos_next = top_node->get_index(false) + (MOVING_DOWN ? 1 : -1);
@@ -1934,6 +1934,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
p_nodes.sort_custom<Node::Comparator>(); //Makes result reliable.
+ const int first_idx = p_position_in_parent == -1 ? p_new_parent->get_child_count(false) : p_position_in_parent;
+ int nodes_before = first_idx;
bool no_change = true;
for (int ni = 0; ni < p_nodes.size(); ni++) {
if (p_nodes[ni] == p_new_parent) {
@@ -1942,7 +1944,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
// `move_child` + `get_index` doesn't really work for internal nodes.
ERR_FAIL_COND_MSG(p_nodes[ni]->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to move internal node, this is not supported.");
- if (p_nodes[ni]->get_parent() != p_new_parent || p_position_in_parent + ni != p_nodes[ni]->get_index(false)) {
+ if (p_nodes[ni]->get_index(false) < first_idx) {
+ nodes_before--;
+ }
+
+ if (p_nodes[ni]->get_parent() != p_new_parent) {
+ no_change = false;
+ }
+ }
+
+ for (int ni = 0; ni < p_nodes.size() && no_change; ni++) {
+ if (p_nodes[ni]->get_index(false) != nodes_before + ni) {
no_change = false;
}
}
diff --git a/main/main.cpp b/main/main.cpp
index 8e217575c1..72a5210f12 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -213,6 +213,7 @@ static bool debug_collisions = false;
static bool debug_paths = false;
static bool debug_navigation = false;
static bool debug_avoidance = false;
+static bool debug_canvas_item_redraw = false;
#endif
static int max_fps = -1;
static int frame_delay = 0;
@@ -486,6 +487,7 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n");
OS::get_singleton()->print(" --debug-avoidance Show navigation avoidance debug visuals when running the scene.\n");
OS::get_singleton()->print(" --debug-stringnames Print all StringName allocations to stdout when the engine quits.\n");
+ OS::get_singleton()->print(" --debug-canvas-item-redraw Display a rectangle each time a canvas item requests a redraw (useful to troubleshoot low processor mode).\n");
#endif
OS::get_singleton()->print(" --max-fps <fps> Set a maximum number of frames per second rendered (can be used to limit power usage). A value of 0 results in unlimited framerate.\n");
OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds). Do not use as a FPS limiter; use --max-fps instead.\n");
@@ -1455,6 +1457,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
debug_navigation = true;
} else if (I->get() == "--debug-avoidance") {
debug_avoidance = true;
+ } else if (I->get() == "--debug-canvas-item-redraw") {
+ debug_canvas_item_redraw = true;
} else if (I->get() == "--debug-stringnames") {
StringName::set_debug_stringnames(true);
#endif
@@ -1542,7 +1546,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->print("Missing <path> argument for --benchmark-file <path>.\n");
goto error;
}
-#if defined(TOOLS_ENABLED) && !defined(GDSCRIPT_NO_LSP)
+#if defined(TOOLS_ENABLED) && defined(MODULE_GDSCRIPT_ENABLED) && !defined(GDSCRIPT_NO_LSP)
} else if (I->get() == "--lsp-port") {
if (I->next()) {
int port_override = I->next()->get().to_int();
@@ -1556,7 +1560,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->print("Missing <port> argument for --lsp-port <port>.\n");
goto error;
}
-#endif // TOOLS_ENABLED && !GDSCRIPT_NO_LSP
+#endif // TOOLS_ENABLED && MODULE_GDSCRIPT_ENABLED && !GDSCRIPT_NO_LSP
} else if (I->get() == "--" || I->get() == "++") {
adding_user_args = true;
} else {
@@ -2565,6 +2569,9 @@ Error Main::setup2() {
id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true)));
}
+ GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false);
+ GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false);
+
MAIN_PRINT("Main: Load Translations and Remaps");
translation_server->setup(); //register translations, load them, etc.
@@ -2857,7 +2864,11 @@ bool Main::start() {
}
#ifdef TOOLS_ENABLED
+#ifdef MODULE_GDSCRIPT_ENABLED
if (!doc_tool_path.is_empty() && gdscript_docs_path.is_empty()) {
+#else
+ if (!doc_tool_path.is_empty()) {
+#endif
// Needed to instance editor-only classes for their default values
Engine::get_singleton()->set_editor_hint(true);
@@ -3100,6 +3111,9 @@ bool Main::start() {
NavigationServer3D::get_singleton()->set_active(true);
NavigationServer3D::get_singleton()->set_debug_enabled(true);
}
+ if (debug_canvas_item_redraw) {
+ RenderingServer::get_singleton()->canvas_item_set_debug_redraw(true);
+ }
#endif
if (single_threaded_scene) {
diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
index cc2b1c64f8..27bc2844d5 100644
--- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
+++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
@@ -255,7 +255,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
- OTHER_LDFLAGS = "-ld_classic $linker_flags";
+ "LD_CLASSIC_1000" = "";
+ "LD_CLASSIC_1100" = "";
+ "LD_CLASSIC_1200" = "";
+ "LD_CLASSIC_1300" = "";
+ "LD_CLASSIC_1400" = "";
+ "LD_CLASSIC_1500" = "-ld_classic";
+ OTHER_LDFLAGS = "$(LD_CLASSIC_$(XCODE_VERSION_MAJOR)) $linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "$targeted_device_family";
};
@@ -294,7 +300,13 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
- OTHER_LDFLAGS = "-ld_classic $linker_flags";
+ "LD_CLASSIC_1000" = "";
+ "LD_CLASSIC_1100" = "";
+ "LD_CLASSIC_1200" = "";
+ "LD_CLASSIC_1300" = "";
+ "LD_CLASSIC_1400" = "";
+ "LD_CLASSIC_1500" = "-ld_classic";
+ OTHER_LDFLAGS = "$(LD_CLASSIC_$(XCODE_VERSION_MAJOR)) $linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "$targeted_device_family";
VALIDATE_PRODUCT = YES;
diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected
index b3b1f36573..a9720d2a88 100644
--- a/misc/extension_api_validation/4.1-stable.expected
+++ b/misc/extension_api_validation/4.1-stable.expected
@@ -287,3 +287,9 @@ Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/shader_ge
Validate extension JSON: Error: Field 'classes/SurfaceTool/methods/commit/arguments/1': meta changed value in new API, from "uint32" to "uint64".
Surface format was increased to 64 bits from 32 bits. Compatibility methods registered.
+
+GH-79527
+--------
+Validate extension JSON: Error: Field 'classes/ParticleProcessMaterial/properties/orbit_velocity_curve': type changed value in new API, from "CurveTexture" to "CurveTexture,CurveXYZTexture".
+
+Added accepted curve type from only CurveTexture to CurveXYZTexture.
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp
index 94473c76c0..2ccfd5d326 100644
--- a/modules/enet/enet_connection.cpp
+++ b/modules/enet/enet_connection.cpp
@@ -252,7 +252,7 @@ int ENetConnection::get_max_channels() const {
int ENetConnection::get_local_port() const {
ERR_FAIL_NULL_V_MSG(host, 0, "The ENetConnection instance isn't currently active.");
- ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound");
+ ERR_FAIL_COND_V_MSG(!(host->socket), 0, "The ENetConnection instance isn't currently bound.");
ENetAddress address;
ERR_FAIL_COND_V_MSG(enet_socket_get_address(host->socket, &address), 0, "Unable to get socket address");
return address.port;
@@ -344,7 +344,7 @@ void ENetConnection::_broadcast(int p_channel, PackedByteArray p_packet, int p_f
void ENetConnection::socket_send(const String &p_address, int p_port, const PackedByteArray &p_packet) {
ERR_FAIL_NULL_MSG(host, "The ENetConnection instance isn't currently active.");
- ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound");
+ ERR_FAIL_COND_MSG(!(host->socket), "The ENetConnection instance isn't currently bound.");
ERR_FAIL_COND_MSG(p_port < 1 || p_port > 65535, "The remote port number must be between 1 and 65535 (inclusive).");
IPAddress ip;
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index cffd661261..c3979dd290 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -304,7 +304,8 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c
method_doc.qualifiers = m_func->is_static ? "static" : "";
if (m_func->return_type) {
- _doctype_from_gdtype(m_func->return_type->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
+ // `m_func->return_type->get_datatype()` is a metatype.
+ _doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true);
} else if (!m_func->body->has_return) {
// If no `return` statement, then return type is `void`, not `Variant`.
method_doc.return_type = "void";
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 45ac142eaa..144dd41f1a 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -149,7 +149,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
// Don't skip comments, for highlighting markers.
- if (color_regions[in_region].start_key == "#") {
+ if (color_regions[in_region].start_key.begins_with("#")) {
break;
}
if (from + end_key_length > line_length) {
@@ -171,7 +171,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
// Don't skip comments, for highlighting markers.
- if (j == line_length && color_regions[in_region].start_key != "#") {
+ if (j == line_length && !color_regions[in_region].start_key.begins_with("#")) {
continue;
}
}
@@ -193,7 +193,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
- if (color_regions[in_region].start_key == "#") {
+ if (color_regions[in_region].start_key.begins_with("#")) {
int marker_start_pos = from;
int marker_len = 0;
while (from <= line_length) {
@@ -740,6 +740,16 @@ void GDScriptSyntaxHighlighter::_update_cache() {
add_color_region(beg, end, comment_color, end.is_empty());
}
+ /* Doc comments */
+ const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color");
+ List<String> doc_comments;
+ gdscript->get_doc_comment_delimiters(&doc_comments);
+ for (const String &doc_comment : doc_comments) {
+ String beg = doc_comment.get_slice(" ", 0);
+ String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String();
+ add_color_region(beg, end, doc_comment_color, end.is_empty());
+ }
+
/* Strings */
string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
List<String> strings;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index eb8e95025a..7cde0fb978 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -501,6 +501,7 @@ public:
virtual void get_reserved_words(List<String> *p_words) const override;
virtual bool is_control_flow_keyword(String p_keywords) const override;
virtual void get_comment_delimiters(List<String> *p_delimiters) const override;
+ virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const override;
virtual void get_string_delimiters(List<String> *p_delimiters) const override;
virtual bool is_using_templates() override;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 8394fce9b3..25e20c0e76 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -400,7 +400,6 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
}
function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
function->_instruction_args_size = instr_args_max;
- function->_ptrcall_args_size = ptrcall_max;
#ifdef DEBUG_ENABLED
function->operator_names = operator_names;
@@ -1225,75 +1224,35 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target,
ct.cleanup();
}
-void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
-#define CASE_TYPE(m_type) \
- case Variant::m_type: \
- append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \
- break
-
- bool is_ptrcall = true;
-
- if (p_method->has_return()) {
- MethodInfo info;
- ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info);
- switch (info.return_val.type) {
- CASE_TYPE(BOOL);
- CASE_TYPE(INT);
- CASE_TYPE(FLOAT);
- CASE_TYPE(STRING);
- CASE_TYPE(VECTOR2);
- CASE_TYPE(VECTOR2I);
- CASE_TYPE(RECT2);
- CASE_TYPE(RECT2I);
- CASE_TYPE(VECTOR3);
- CASE_TYPE(VECTOR3I);
- CASE_TYPE(TRANSFORM2D);
- CASE_TYPE(PLANE);
- CASE_TYPE(AABB);
- CASE_TYPE(BASIS);
- CASE_TYPE(TRANSFORM3D);
- CASE_TYPE(COLOR);
- CASE_TYPE(STRING_NAME);
- CASE_TYPE(NODE_PATH);
- CASE_TYPE(RID);
- CASE_TYPE(QUATERNION);
- CASE_TYPE(OBJECT);
- CASE_TYPE(CALLABLE);
- CASE_TYPE(SIGNAL);
- CASE_TYPE(DICTIONARY);
- CASE_TYPE(ARRAY);
- CASE_TYPE(PACKED_BYTE_ARRAY);
- CASE_TYPE(PACKED_INT32_ARRAY);
- CASE_TYPE(PACKED_INT64_ARRAY);
- CASE_TYPE(PACKED_FLOAT32_ARRAY);
- CASE_TYPE(PACKED_FLOAT64_ARRAY);
- CASE_TYPE(PACKED_STRING_ARRAY);
- CASE_TYPE(PACKED_VECTOR2_ARRAY);
- CASE_TYPE(PACKED_VECTOR3_ARRAY);
- CASE_TYPE(PACKED_COLOR_ARRAY);
- default:
- append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size());
- is_ptrcall = false;
- break;
+void GDScriptByteCodeGenerator::write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) {
+ Variant::Type return_type = Variant::NIL;
+ bool has_return = p_method->has_return();
+
+ if (has_return) {
+ PropertyInfo return_info = p_method->get_return_info();
+ return_type = return_info.type;
+ }
+
+ CallTarget ct = get_call_target(p_target, return_type);
+
+ if (has_return) {
+ Variant::Type temp_type = temporaries[ct.target.address].type;
+ if (temp_type != return_type) {
+ write_type_adjust(ct.target, return_type);
}
- } else {
- append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size());
}
+ GDScriptFunction::Opcode code = p_method->has_return() ? GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN : GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN;
+ append_opcode_and_argcount(code, 2 + p_arguments.size());
+
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
append(p_base);
- CallTarget ct = get_call_target(p_target);
append(ct.target);
append(p_arguments.size());
append(p_method);
ct.cleanup();
- if (is_ptrcall) {
- alloc_ptrcall(p_arguments.size());
- }
-
-#undef CASE_TYPE
}
void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) {
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 671dea5d6d..9bface6136 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -97,7 +97,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
int max_locals = 0;
int current_line = 0;
int instr_args_max = 0;
- int ptrcall_max = 0;
#ifdef DEBUG_ENABLED
List<int> temp_stack;
@@ -346,12 +345,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
return pos;
}
- void alloc_ptrcall(int p_params) {
- if (p_params >= ptrcall_max) {
- ptrcall_max = p_params;
- }
- }
-
CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL);
int address_of(const Address &p_address) {
@@ -519,7 +512,7 @@ public:
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
- virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
+ virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index cf17353dec..7ad8f841aa 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -129,7 +129,7 @@ public:
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
- virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index f417d323db..bf648abc9e 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -229,13 +229,13 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp
}
}
-static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
+static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) {
if (p_method->is_vararg()) {
- // ptrcall won't work with vararg methods.
+ // Validated call won't work with vararg methods.
return false;
}
if (p_method->get_argument_count() != p_arguments.size()) {
- // ptrcall won't work with default arguments.
+ // Validated call won't work with default arguments.
return false;
}
MethodInfo info;
@@ -636,9 +636,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
self.mode = GDScriptCodeGenerator::Address::SELF;
MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name);
- if (_can_use_ptrcall(method, arguments)) {
- // Exact arguments, use ptrcall.
- gen->write_call_ptrcall(result, self, method, arguments);
+ if (_can_use_validate_call(method, arguments)) {
+ // Exact arguments, use validated call.
+ gen->write_call_method_bind_validated(result, self, method, arguments);
} else {
// Not exact arguments, but still can use method bind call.
gen->write_call_method_bind(result, self, method, arguments);
@@ -686,9 +686,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
}
if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) {
MethodBind *method = ClassDB::get_method(class_name, call->function_name);
- if (_can_use_ptrcall(method, arguments)) {
- // Exact arguments, use ptrcall.
- gen->write_call_ptrcall(result, base, method, arguments);
+ if (_can_use_validate_call(method, arguments)) {
+ // Exact arguments, use validated call.
+ gen->write_call_method_bind_validated(result, base, method, arguments);
} else {
// Not exact arguments, but still can use method bind call.
gen->write_call_method_bind(result, base, method, arguments);
@@ -733,7 +733,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script));
MethodBind *get_node_method = ClassDB::get_method("Node", "get_node");
- gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
+ gen->write_call_method_bind_validated(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args);
return result;
} break;
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 438ec02740..26f7cb7537 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -670,10 +670,29 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 4 + argc;
} break;
- case OPCODE_CALL_PTRCALL_NO_RETURN: {
+
+ case OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN: {
int instr_var_args = _code_ptr[++ip];
+ text += "call method-bind validated (return) ";
+ MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
+ int argc = _code_ptr[ip + 1 + instr_var_args];
+ text += DADDR(2 + argc) + " = ";
+ text += DADDR(1 + argc) + ".";
+ text += method->get_name();
+ text += "(";
+ for (int i = 0; i < argc; i++) {
+ if (i > 0)
+ text += ", ";
+ text += DADDR(1 + i);
+ }
+ text += ")";
+ incr = 5 + argc;
+ } break;
- text += "call-ptrcall (no return) ";
+ case OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN: {
+ int instr_var_args = _code_ptr[++ip];
+
+ text += "call method-bind validated (no return) ";
MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]];
@@ -694,65 +713,6 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr = 5 + argc;
} break;
-#define DISASSEMBLE_PTRCALL(m_type) \
- case OPCODE_CALL_PTRCALL_##m_type: { \
- int instr_var_args = _code_ptr[++ip]; \
- text += "call-ptrcall (return "; \
- text += #m_type; \
- text += ") "; \
- MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \
- int argc = _code_ptr[ip + 1 + instr_var_args]; \
- text += DADDR(2 + argc) + " = "; \
- text += DADDR(1 + argc) + "."; \
- text += method->get_name(); \
- text += "("; \
- for (int i = 0; i < argc; i++) { \
- if (i > 0) \
- text += ", "; \
- text += DADDR(1 + i); \
- } \
- text += ")"; \
- incr = 5 + argc; \
- } break
-
- DISASSEMBLE_PTRCALL(BOOL);
- DISASSEMBLE_PTRCALL(INT);
- DISASSEMBLE_PTRCALL(FLOAT);
- DISASSEMBLE_PTRCALL(STRING);
- DISASSEMBLE_PTRCALL(VECTOR2);
- DISASSEMBLE_PTRCALL(VECTOR2I);
- DISASSEMBLE_PTRCALL(RECT2);
- DISASSEMBLE_PTRCALL(RECT2I);
- DISASSEMBLE_PTRCALL(VECTOR3);
- DISASSEMBLE_PTRCALL(VECTOR3I);
- DISASSEMBLE_PTRCALL(TRANSFORM2D);
- DISASSEMBLE_PTRCALL(VECTOR4);
- DISASSEMBLE_PTRCALL(VECTOR4I);
- DISASSEMBLE_PTRCALL(PLANE);
- DISASSEMBLE_PTRCALL(AABB);
- DISASSEMBLE_PTRCALL(BASIS);
- DISASSEMBLE_PTRCALL(TRANSFORM3D);
- DISASSEMBLE_PTRCALL(PROJECTION);
- DISASSEMBLE_PTRCALL(COLOR);
- DISASSEMBLE_PTRCALL(STRING_NAME);
- DISASSEMBLE_PTRCALL(NODE_PATH);
- DISASSEMBLE_PTRCALL(RID);
- DISASSEMBLE_PTRCALL(QUATERNION);
- DISASSEMBLE_PTRCALL(OBJECT);
- DISASSEMBLE_PTRCALL(CALLABLE);
- DISASSEMBLE_PTRCALL(SIGNAL);
- DISASSEMBLE_PTRCALL(DICTIONARY);
- DISASSEMBLE_PTRCALL(ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY);
- DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY);
-
case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: {
int instr_var_args = _code_ptr[++ip];
int argc = _code_ptr[ip + 1 + instr_var_args];
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 9cd3560063..e40f692889 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -54,6 +54,10 @@ void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const
p_delimiters->push_back("#");
}
+void GDScriptLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const {
+ p_delimiters->push_back("##");
+}
+
void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("\" \"");
p_delimiters->push_back("' '");
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index e984d97149..c9b543fbb9 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -241,45 +241,8 @@ public:
OPCODE_CALL_METHOD_BIND_RET,
OPCODE_CALL_BUILTIN_STATIC,
OPCODE_CALL_NATIVE_STATIC,
- // ptrcall have one instruction per return type.
- OPCODE_CALL_PTRCALL_NO_RETURN,
- OPCODE_CALL_PTRCALL_BOOL,
- OPCODE_CALL_PTRCALL_INT,
- OPCODE_CALL_PTRCALL_FLOAT,
- OPCODE_CALL_PTRCALL_STRING,
- OPCODE_CALL_PTRCALL_VECTOR2,
- OPCODE_CALL_PTRCALL_VECTOR2I,
- OPCODE_CALL_PTRCALL_RECT2,
- OPCODE_CALL_PTRCALL_RECT2I,
- OPCODE_CALL_PTRCALL_VECTOR3,
- OPCODE_CALL_PTRCALL_VECTOR3I,
- OPCODE_CALL_PTRCALL_TRANSFORM2D,
- OPCODE_CALL_PTRCALL_VECTOR4,
- OPCODE_CALL_PTRCALL_VECTOR4I,
- OPCODE_CALL_PTRCALL_PLANE,
- OPCODE_CALL_PTRCALL_QUATERNION,
- OPCODE_CALL_PTRCALL_AABB,
- OPCODE_CALL_PTRCALL_BASIS,
- OPCODE_CALL_PTRCALL_TRANSFORM3D,
- OPCODE_CALL_PTRCALL_PROJECTION,
- OPCODE_CALL_PTRCALL_COLOR,
- OPCODE_CALL_PTRCALL_STRING_NAME,
- OPCODE_CALL_PTRCALL_NODE_PATH,
- OPCODE_CALL_PTRCALL_RID,
- OPCODE_CALL_PTRCALL_OBJECT,
- OPCODE_CALL_PTRCALL_CALLABLE,
- OPCODE_CALL_PTRCALL_SIGNAL,
- OPCODE_CALL_PTRCALL_DICTIONARY,
- OPCODE_CALL_PTRCALL_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY,
- OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY,
+ OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN,
+ OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN,
OPCODE_AWAIT,
OPCODE_AWAIT_RESUME,
OPCODE_CREATE_LAMBDA,
@@ -425,7 +388,6 @@ private:
int _argument_count = 0;
int _stack_size = 0;
int _instruction_args_size = 0;
- int _ptrcall_args_size = 0;
SelfList<GDScriptFunction> function_list{ this };
mutable Variant nil;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 057c2b49ab..db7b3e7ace 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -32,10 +32,6 @@
#include "gdscript.h"
-#ifdef DEBUG_ENABLED
-#include "gdscript_warning.h"
-#endif
-
#include "core/config/project_settings.h"
#include "core/io/file_access.h"
#include "core/io/resource_loader.h"
@@ -5410,7 +5406,7 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
}
void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
- ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree.");
+ ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree.");
if (p_parser.is_tool()) {
push_line("@tool");
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 5ecae08f6c..75dc2e4f8b 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -187,191 +187,155 @@ void (*type_init_function_table[])(Variant *) = {
};
#if defined(__GNUC__)
-#define OPCODES_TABLE \
- static const void *switch_table_ops[] = { \
- &&OPCODE_OPERATOR, \
- &&OPCODE_OPERATOR_VALIDATED, \
- &&OPCODE_TYPE_TEST_BUILTIN, \
- &&OPCODE_TYPE_TEST_ARRAY, \
- &&OPCODE_TYPE_TEST_NATIVE, \
- &&OPCODE_TYPE_TEST_SCRIPT, \
- &&OPCODE_SET_KEYED, \
- &&OPCODE_SET_KEYED_VALIDATED, \
- &&OPCODE_SET_INDEXED_VALIDATED, \
- &&OPCODE_GET_KEYED, \
- &&OPCODE_GET_KEYED_VALIDATED, \
- &&OPCODE_GET_INDEXED_VALIDATED, \
- &&OPCODE_SET_NAMED, \
- &&OPCODE_SET_NAMED_VALIDATED, \
- &&OPCODE_GET_NAMED, \
- &&OPCODE_GET_NAMED_VALIDATED, \
- &&OPCODE_SET_MEMBER, \
- &&OPCODE_GET_MEMBER, \
- &&OPCODE_SET_STATIC_VARIABLE, \
- &&OPCODE_GET_STATIC_VARIABLE, \
- &&OPCODE_ASSIGN, \
- &&OPCODE_ASSIGN_TRUE, \
- &&OPCODE_ASSIGN_FALSE, \
- &&OPCODE_ASSIGN_TYPED_BUILTIN, \
- &&OPCODE_ASSIGN_TYPED_ARRAY, \
- &&OPCODE_ASSIGN_TYPED_NATIVE, \
- &&OPCODE_ASSIGN_TYPED_SCRIPT, \
- &&OPCODE_CAST_TO_BUILTIN, \
- &&OPCODE_CAST_TO_NATIVE, \
- &&OPCODE_CAST_TO_SCRIPT, \
- &&OPCODE_CONSTRUCT, \
- &&OPCODE_CONSTRUCT_VALIDATED, \
- &&OPCODE_CONSTRUCT_ARRAY, \
- &&OPCODE_CONSTRUCT_TYPED_ARRAY, \
- &&OPCODE_CONSTRUCT_DICTIONARY, \
- &&OPCODE_CALL, \
- &&OPCODE_CALL_RETURN, \
- &&OPCODE_CALL_ASYNC, \
- &&OPCODE_CALL_UTILITY, \
- &&OPCODE_CALL_UTILITY_VALIDATED, \
- &&OPCODE_CALL_GDSCRIPT_UTILITY, \
- &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \
- &&OPCODE_CALL_SELF_BASE, \
- &&OPCODE_CALL_METHOD_BIND, \
- &&OPCODE_CALL_METHOD_BIND_RET, \
- &&OPCODE_CALL_BUILTIN_STATIC, \
- &&OPCODE_CALL_NATIVE_STATIC, \
- &&OPCODE_CALL_PTRCALL_NO_RETURN, \
- &&OPCODE_CALL_PTRCALL_BOOL, \
- &&OPCODE_CALL_PTRCALL_INT, \
- &&OPCODE_CALL_PTRCALL_FLOAT, \
- &&OPCODE_CALL_PTRCALL_STRING, \
- &&OPCODE_CALL_PTRCALL_VECTOR2, \
- &&OPCODE_CALL_PTRCALL_VECTOR2I, \
- &&OPCODE_CALL_PTRCALL_RECT2, \
- &&OPCODE_CALL_PTRCALL_RECT2I, \
- &&OPCODE_CALL_PTRCALL_VECTOR3, \
- &&OPCODE_CALL_PTRCALL_VECTOR3I, \
- &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \
- &&OPCODE_CALL_PTRCALL_VECTOR4, \
- &&OPCODE_CALL_PTRCALL_VECTOR4I, \
- &&OPCODE_CALL_PTRCALL_PLANE, \
- &&OPCODE_CALL_PTRCALL_QUATERNION, \
- &&OPCODE_CALL_PTRCALL_AABB, \
- &&OPCODE_CALL_PTRCALL_BASIS, \
- &&OPCODE_CALL_PTRCALL_TRANSFORM3D, \
- &&OPCODE_CALL_PTRCALL_PROJECTION, \
- &&OPCODE_CALL_PTRCALL_COLOR, \
- &&OPCODE_CALL_PTRCALL_STRING_NAME, \
- &&OPCODE_CALL_PTRCALL_NODE_PATH, \
- &&OPCODE_CALL_PTRCALL_RID, \
- &&OPCODE_CALL_PTRCALL_OBJECT, \
- &&OPCODE_CALL_PTRCALL_CALLABLE, \
- &&OPCODE_CALL_PTRCALL_SIGNAL, \
- &&OPCODE_CALL_PTRCALL_DICTIONARY, \
- &&OPCODE_CALL_PTRCALL_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \
- &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \
- &&OPCODE_AWAIT, \
- &&OPCODE_AWAIT_RESUME, \
- &&OPCODE_CREATE_LAMBDA, \
- &&OPCODE_CREATE_SELF_LAMBDA, \
- &&OPCODE_JUMP, \
- &&OPCODE_JUMP_IF, \
- &&OPCODE_JUMP_IF_NOT, \
- &&OPCODE_JUMP_TO_DEF_ARGUMENT, \
- &&OPCODE_JUMP_IF_SHARED, \
- &&OPCODE_RETURN, \
- &&OPCODE_RETURN_TYPED_BUILTIN, \
- &&OPCODE_RETURN_TYPED_ARRAY, \
- &&OPCODE_RETURN_TYPED_NATIVE, \
- &&OPCODE_RETURN_TYPED_SCRIPT, \
- &&OPCODE_ITERATE_BEGIN, \
- &&OPCODE_ITERATE_BEGIN_INT, \
- &&OPCODE_ITERATE_BEGIN_FLOAT, \
- &&OPCODE_ITERATE_BEGIN_VECTOR2, \
- &&OPCODE_ITERATE_BEGIN_VECTOR2I, \
- &&OPCODE_ITERATE_BEGIN_VECTOR3, \
- &&OPCODE_ITERATE_BEGIN_VECTOR3I, \
- &&OPCODE_ITERATE_BEGIN_STRING, \
- &&OPCODE_ITERATE_BEGIN_DICTIONARY, \
- &&OPCODE_ITERATE_BEGIN_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \
- &&OPCODE_ITERATE_BEGIN_OBJECT, \
- &&OPCODE_ITERATE, \
- &&OPCODE_ITERATE_INT, \
- &&OPCODE_ITERATE_FLOAT, \
- &&OPCODE_ITERATE_VECTOR2, \
- &&OPCODE_ITERATE_VECTOR2I, \
- &&OPCODE_ITERATE_VECTOR3, \
- &&OPCODE_ITERATE_VECTOR3I, \
- &&OPCODE_ITERATE_STRING, \
- &&OPCODE_ITERATE_DICTIONARY, \
- &&OPCODE_ITERATE_ARRAY, \
- &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \
- &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \
- &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \
- &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \
- &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \
- &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \
- &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \
- &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \
- &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
- &&OPCODE_ITERATE_OBJECT, \
- &&OPCODE_STORE_GLOBAL, \
- &&OPCODE_STORE_NAMED_GLOBAL, \
- &&OPCODE_TYPE_ADJUST_BOOL, \
- &&OPCODE_TYPE_ADJUST_INT, \
- &&OPCODE_TYPE_ADJUST_FLOAT, \
- &&OPCODE_TYPE_ADJUST_STRING, \
- &&OPCODE_TYPE_ADJUST_VECTOR2, \
- &&OPCODE_TYPE_ADJUST_VECTOR2I, \
- &&OPCODE_TYPE_ADJUST_RECT2, \
- &&OPCODE_TYPE_ADJUST_RECT2I, \
- &&OPCODE_TYPE_ADJUST_VECTOR3, \
- &&OPCODE_TYPE_ADJUST_VECTOR3I, \
- &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \
- &&OPCODE_TYPE_ADJUST_VECTOR4, \
- &&OPCODE_TYPE_ADJUST_VECTOR4I, \
- &&OPCODE_TYPE_ADJUST_PLANE, \
- &&OPCODE_TYPE_ADJUST_QUATERNION, \
- &&OPCODE_TYPE_ADJUST_AABB, \
- &&OPCODE_TYPE_ADJUST_BASIS, \
- &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \
- &&OPCODE_TYPE_ADJUST_PROJECTION, \
- &&OPCODE_TYPE_ADJUST_COLOR, \
- &&OPCODE_TYPE_ADJUST_STRING_NAME, \
- &&OPCODE_TYPE_ADJUST_NODE_PATH, \
- &&OPCODE_TYPE_ADJUST_RID, \
- &&OPCODE_TYPE_ADJUST_OBJECT, \
- &&OPCODE_TYPE_ADJUST_CALLABLE, \
- &&OPCODE_TYPE_ADJUST_SIGNAL, \
- &&OPCODE_TYPE_ADJUST_DICTIONARY, \
- &&OPCODE_TYPE_ADJUST_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \
- &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \
- &&OPCODE_ASSERT, \
- &&OPCODE_BREAKPOINT, \
- &&OPCODE_LINE, \
- &&OPCODE_END \
- }; \
+#define OPCODES_TABLE \
+ static const void *switch_table_ops[] = { \
+ &&OPCODE_OPERATOR, \
+ &&OPCODE_OPERATOR_VALIDATED, \
+ &&OPCODE_TYPE_TEST_BUILTIN, \
+ &&OPCODE_TYPE_TEST_ARRAY, \
+ &&OPCODE_TYPE_TEST_NATIVE, \
+ &&OPCODE_TYPE_TEST_SCRIPT, \
+ &&OPCODE_SET_KEYED, \
+ &&OPCODE_SET_KEYED_VALIDATED, \
+ &&OPCODE_SET_INDEXED_VALIDATED, \
+ &&OPCODE_GET_KEYED, \
+ &&OPCODE_GET_KEYED_VALIDATED, \
+ &&OPCODE_GET_INDEXED_VALIDATED, \
+ &&OPCODE_SET_NAMED, \
+ &&OPCODE_SET_NAMED_VALIDATED, \
+ &&OPCODE_GET_NAMED, \
+ &&OPCODE_GET_NAMED_VALIDATED, \
+ &&OPCODE_SET_MEMBER, \
+ &&OPCODE_GET_MEMBER, \
+ &&OPCODE_SET_STATIC_VARIABLE, \
+ &&OPCODE_GET_STATIC_VARIABLE, \
+ &&OPCODE_ASSIGN, \
+ &&OPCODE_ASSIGN_TRUE, \
+ &&OPCODE_ASSIGN_FALSE, \
+ &&OPCODE_ASSIGN_TYPED_BUILTIN, \
+ &&OPCODE_ASSIGN_TYPED_ARRAY, \
+ &&OPCODE_ASSIGN_TYPED_NATIVE, \
+ &&OPCODE_ASSIGN_TYPED_SCRIPT, \
+ &&OPCODE_CAST_TO_BUILTIN, \
+ &&OPCODE_CAST_TO_NATIVE, \
+ &&OPCODE_CAST_TO_SCRIPT, \
+ &&OPCODE_CONSTRUCT, \
+ &&OPCODE_CONSTRUCT_VALIDATED, \
+ &&OPCODE_CONSTRUCT_ARRAY, \
+ &&OPCODE_CONSTRUCT_TYPED_ARRAY, \
+ &&OPCODE_CONSTRUCT_DICTIONARY, \
+ &&OPCODE_CALL, \
+ &&OPCODE_CALL_RETURN, \
+ &&OPCODE_CALL_ASYNC, \
+ &&OPCODE_CALL_UTILITY, \
+ &&OPCODE_CALL_UTILITY_VALIDATED, \
+ &&OPCODE_CALL_GDSCRIPT_UTILITY, \
+ &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \
+ &&OPCODE_CALL_SELF_BASE, \
+ &&OPCODE_CALL_METHOD_BIND, \
+ &&OPCODE_CALL_METHOD_BIND_RET, \
+ &&OPCODE_CALL_BUILTIN_STATIC, \
+ &&OPCODE_CALL_NATIVE_STATIC, \
+ &&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \
+ &&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \
+ &&OPCODE_AWAIT, \
+ &&OPCODE_AWAIT_RESUME, \
+ &&OPCODE_CREATE_LAMBDA, \
+ &&OPCODE_CREATE_SELF_LAMBDA, \
+ &&OPCODE_JUMP, \
+ &&OPCODE_JUMP_IF, \
+ &&OPCODE_JUMP_IF_NOT, \
+ &&OPCODE_JUMP_TO_DEF_ARGUMENT, \
+ &&OPCODE_JUMP_IF_SHARED, \
+ &&OPCODE_RETURN, \
+ &&OPCODE_RETURN_TYPED_BUILTIN, \
+ &&OPCODE_RETURN_TYPED_ARRAY, \
+ &&OPCODE_RETURN_TYPED_NATIVE, \
+ &&OPCODE_RETURN_TYPED_SCRIPT, \
+ &&OPCODE_ITERATE_BEGIN, \
+ &&OPCODE_ITERATE_BEGIN_INT, \
+ &&OPCODE_ITERATE_BEGIN_FLOAT, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR2, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR2I, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR3, \
+ &&OPCODE_ITERATE_BEGIN_VECTOR3I, \
+ &&OPCODE_ITERATE_BEGIN_STRING, \
+ &&OPCODE_ITERATE_BEGIN_DICTIONARY, \
+ &&OPCODE_ITERATE_BEGIN_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \
+ &&OPCODE_ITERATE_BEGIN_OBJECT, \
+ &&OPCODE_ITERATE, \
+ &&OPCODE_ITERATE_INT, \
+ &&OPCODE_ITERATE_FLOAT, \
+ &&OPCODE_ITERATE_VECTOR2, \
+ &&OPCODE_ITERATE_VECTOR2I, \
+ &&OPCODE_ITERATE_VECTOR3, \
+ &&OPCODE_ITERATE_VECTOR3I, \
+ &&OPCODE_ITERATE_STRING, \
+ &&OPCODE_ITERATE_DICTIONARY, \
+ &&OPCODE_ITERATE_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \
+ &&OPCODE_ITERATE_OBJECT, \
+ &&OPCODE_STORE_GLOBAL, \
+ &&OPCODE_STORE_NAMED_GLOBAL, \
+ &&OPCODE_TYPE_ADJUST_BOOL, \
+ &&OPCODE_TYPE_ADJUST_INT, \
+ &&OPCODE_TYPE_ADJUST_FLOAT, \
+ &&OPCODE_TYPE_ADJUST_STRING, \
+ &&OPCODE_TYPE_ADJUST_VECTOR2, \
+ &&OPCODE_TYPE_ADJUST_VECTOR2I, \
+ &&OPCODE_TYPE_ADJUST_RECT2, \
+ &&OPCODE_TYPE_ADJUST_RECT2I, \
+ &&OPCODE_TYPE_ADJUST_VECTOR3, \
+ &&OPCODE_TYPE_ADJUST_VECTOR3I, \
+ &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \
+ &&OPCODE_TYPE_ADJUST_VECTOR4, \
+ &&OPCODE_TYPE_ADJUST_VECTOR4I, \
+ &&OPCODE_TYPE_ADJUST_PLANE, \
+ &&OPCODE_TYPE_ADJUST_QUATERNION, \
+ &&OPCODE_TYPE_ADJUST_AABB, \
+ &&OPCODE_TYPE_ADJUST_BASIS, \
+ &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \
+ &&OPCODE_TYPE_ADJUST_PROJECTION, \
+ &&OPCODE_TYPE_ADJUST_COLOR, \
+ &&OPCODE_TYPE_ADJUST_STRING_NAME, \
+ &&OPCODE_TYPE_ADJUST_NODE_PATH, \
+ &&OPCODE_TYPE_ADJUST_RID, \
+ &&OPCODE_TYPE_ADJUST_OBJECT, \
+ &&OPCODE_TYPE_ADJUST_CALLABLE, \
+ &&OPCODE_TYPE_ADJUST_SIGNAL, \
+ &&OPCODE_TYPE_ADJUST_DICTIONARY, \
+ &&OPCODE_TYPE_ADJUST_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \
+ &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \
+ &&OPCODE_ASSERT, \
+ &&OPCODE_BREAKPOINT, \
+ &&OPCODE_LINE, \
+ &&OPCODE_END \
+ }; \
static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum.");
#define OPCODE(m_op) \
@@ -489,7 +453,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Variant retvalue;
Variant *stack = nullptr;
Variant **instruction_args = nullptr;
- const void **call_args_ptr = nullptr;
int defarg = 0;
#ifdef DEBUG_ENABLED
@@ -578,12 +541,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
- if (_ptrcall_args_size) {
- call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *));
- } else {
- call_args_ptr = nullptr;
- }
-
if (p_instance) {
memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner));
script = p_instance->script.ptr();
@@ -1954,106 +1911,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
-#ifdef DEBUG_ENABLED
-#define OPCODE_CALL_PTR(m_type) \
- OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
- LOAD_INSTRUCTION_ARGS \
- CHECK_SPACE(3 + instr_arg_count); \
- ip += instr_arg_count; \
- int argc = _code_ptr[ip + 1]; \
- GD_ERR_BREAK(argc < 0); \
- GET_INSTRUCTION_ARG(base, argc); \
- GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \
- MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
- bool freed = false; \
- Object *base_obj = base->get_validated_object_with_check(freed); \
- if (freed) { \
- err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \
- OPCODE_BREAK; \
- } else if (!base_obj) { \
- err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \
- OPCODE_BREAK; \
- } \
- const void **argptrs = call_args_ptr; \
- for (int i = 0; i < argc; i++) { \
- GET_INSTRUCTION_ARG(v, i); \
- argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
- } \
- uint64_t call_time = 0; \
- if (GDScriptLanguage::get_singleton()->profiling) { \
- call_time = OS::get_singleton()->get_ticks_usec(); \
- } \
- GET_INSTRUCTION_ARG(ret, argc + 1); \
- VariantInternal::initialize(ret, Variant::m_type); \
- void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
- method->ptrcall(base_obj, argptrs, ret_opaque); \
- if (GDScriptLanguage::get_singleton()->profiling) { \
- function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \
- } \
- ip += 3; \
- } \
- DISPATCH_OPCODE
-#else
-#define OPCODE_CALL_PTR(m_type) \
- OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \
- LOAD_INSTRUCTION_ARGS \
- CHECK_SPACE(3 + instr_arg_count); \
- ip += instr_arg_count; \
- int argc = _code_ptr[ip + 1]; \
- GET_INSTRUCTION_ARG(base, argc); \
- MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \
- Object *base_obj = *VariantInternal::get_object(base); \
- const void **argptrs = call_args_ptr; \
- for (int i = 0; i < argc; i++) { \
- GET_INSTRUCTION_ARG(v, i); \
- argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \
- } \
- GET_INSTRUCTION_ARG(ret, argc + 1); \
- VariantInternal::initialize(ret, Variant::m_type); \
- void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \
- method->ptrcall(base_obj, argptrs, ret_opaque); \
- ip += 3; \
- } \
- DISPATCH_OPCODE
-#endif
-
- OPCODE_CALL_PTR(BOOL);
- OPCODE_CALL_PTR(INT);
- OPCODE_CALL_PTR(FLOAT);
- OPCODE_CALL_PTR(STRING);
- OPCODE_CALL_PTR(VECTOR2);
- OPCODE_CALL_PTR(VECTOR2I);
- OPCODE_CALL_PTR(RECT2);
- OPCODE_CALL_PTR(RECT2I);
- OPCODE_CALL_PTR(VECTOR3);
- OPCODE_CALL_PTR(VECTOR3I);
- OPCODE_CALL_PTR(TRANSFORM2D);
- OPCODE_CALL_PTR(VECTOR4);
- OPCODE_CALL_PTR(VECTOR4I);
- OPCODE_CALL_PTR(PLANE);
- OPCODE_CALL_PTR(QUATERNION);
- OPCODE_CALL_PTR(AABB);
- OPCODE_CALL_PTR(BASIS);
- OPCODE_CALL_PTR(TRANSFORM3D);
- OPCODE_CALL_PTR(PROJECTION);
- OPCODE_CALL_PTR(COLOR);
- OPCODE_CALL_PTR(STRING_NAME);
- OPCODE_CALL_PTR(NODE_PATH);
- OPCODE_CALL_PTR(RID);
- OPCODE_CALL_PTR(CALLABLE);
- OPCODE_CALL_PTR(SIGNAL);
- OPCODE_CALL_PTR(DICTIONARY);
- OPCODE_CALL_PTR(ARRAY);
- OPCODE_CALL_PTR(PACKED_BYTE_ARRAY);
- OPCODE_CALL_PTR(PACKED_INT32_ARRAY);
- OPCODE_CALL_PTR(PACKED_INT64_ARRAY);
- OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY);
- OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY);
- OPCODE_CALL_PTR(PACKED_STRING_ARRAY);
- OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY);
- OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY);
- OPCODE_CALL_PTR(PACKED_COLOR_ARRAY);
- OPCODE(OPCODE_CALL_PTRCALL_OBJECT) {
+ OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN) {
LOAD_INSTRUCTION_ARGS
CHECK_SPACE(3 + instr_arg_count);
@@ -2066,6 +1924,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
MethodBind *method = _methods_ptr[_code_ptr[ip + 2]];
GET_INSTRUCTION_ARG(base, argc);
+
#ifdef DEBUG_ENABLED
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
@@ -2080,12 +1939,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
Object *base_obj = *VariantInternal::get_object(base);
#endif
- const void **argptrs = call_args_ptr;
+ Variant **argptrs = instruction_args;
- for (int i = 0; i < argc; i++) {
- GET_INSTRUCTION_ARG(v, i);
- argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
- }
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
@@ -2095,16 +1950,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
GET_INSTRUCTION_ARG(ret, argc + 1);
- VariantInternal::initialize(ret, Variant::OBJECT);
- Object **ret_opaque = VariantInternal::get_object(ret);
- method->ptrcall(base_obj, argptrs, ret_opaque);
- if (method->is_return_type_raw_object_ptr()) {
- // The Variant has to participate in the ref count since the method returns a raw Object *.
- VariantInternal::object_assign(ret, *ret_opaque);
- } else {
- // The method, in case it returns something, returns an already encapsulated object.
- VariantInternal::update_object_id(ret);
- }
+ method->validated_call(base_obj, (const Variant **)argptrs, ret);
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
@@ -2114,7 +1960,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
ip += 3;
}
DISPATCH_OPCODE;
- OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) {
+
+ OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN) {
LOAD_INSTRUCTION_ARGS
CHECK_SPACE(3 + instr_arg_count);
@@ -2140,12 +1987,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#else
Object *base_obj = *VariantInternal::get_object(base);
#endif
- const void **argptrs = call_args_ptr;
-
- for (int i = 0; i < argc; i++) {
- GET_INSTRUCTION_ARG(v, i);
- argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v);
- }
+ Variant **argptrs = instruction_args;
#ifdef DEBUG_ENABLED
uint64_t call_time = 0;
@@ -2156,7 +1998,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(ret, argc + 1);
VariantInternal::initialize(ret, Variant::NIL);
- method->ptrcall(base_obj, argptrs, nullptr);
+ method->validated_call(base_obj, (const Variant **)argptrs, nullptr);
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
index fd1460a48f..691b611574 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
@@ -7,7 +7,7 @@ func test():
test_builtin_call_validated(Vector2.UP, false)
test_object_call(RefCounted.new(), false)
test_object_call_method_bind(Resource.new(), false)
- test_object_call_ptrcall(RefCounted.new(), false)
+ test_object_call_method_bind_validated(RefCounted.new(), false)
print("end")
@@ -40,7 +40,7 @@ func test_object_call_method_bind(v: Resource, f):
v.duplicate() # Native type method call with MethodBind.
assert(not f) # Test unary operator reading from `nil`.
-func test_object_call_ptrcall(v: RefCounted, f):
+func test_object_call_method_bind_validated(v: RefCounted, f):
@warning_ignore("return_value_discarded")
- v.get_reference_count() # Native type method call with ptrcall.
+ v.get_reference_count() # Native type method call with validated MethodBind.
assert(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 595b14f260..324eb79ea2 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -4340,7 +4340,7 @@ Error GLTFDocument::_expand_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) {
}
Error GLTFDocument::_verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) {
- // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is)
+ // This may seem duplicated from expand_skins, but this is really a safety check! (so it kinda is)
// In case additional interpolating logic is added to the skins, this will help ensure that you
// do not cause it to self implode into a fiery blaze
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 556b0b4374..953b0634eb 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -35,7 +35,10 @@
#include "lm_raster.glsl.gen.h"
#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
#include "core/math/geometry_2d.h"
+#include "editor/editor_paths.h"
+#include "editor/editor_settings.h"
#include "servers/rendering/rendering_device_binds.h"
//uncomment this if you want to see textures from all the process saved
@@ -671,6 +674,131 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}
+Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
+ Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
+ Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
+ img->convert(Image::FORMAT_RGBF);
+ Vector<uint8_t> data_float = img->get_data();
+
+ Error err = OK;
+ Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::WRITE, &err);
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save PFN at path: '%s'.", p_name));
+ file->store_line("PF");
+ file->store_line(vformat("%d %d", img->get_width(), img->get_height()));
+#ifdef BIG_ENDIAN_ENABLED
+ file->store_line("1.0");
+#else
+ file->store_line("-1.0");
+#endif
+ file->store_buffer(data_float);
+ file->close();
+
+ return OK;
+}
+
+Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
+ Error err = OK;
+ Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
+ ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
+ ERR_FAIL_COND_V(file->get_line() != "PF", Ref<Image>());
+
+ Vector<String> new_size = file->get_line().split(" ");
+ ERR_FAIL_COND_V(new_size.size() != 2, Ref<Image>());
+ int new_width = new_size[0].to_int();
+ int new_height = new_size[1].to_int();
+
+ float endian = file->get_line().to_float();
+ Vector<uint8_t> new_data = file->get_buffer(file->get_length() - file->get_position());
+ file->close();
+
+#ifdef BIG_ENDIAN_ENABLED
+ if (unlikely(endian < 0.0)) {
+ uint32_t count = new_data.size() / 4;
+ uint16_t *dst = (uint16_t *)new_data.ptrw();
+ for (uint32_t j = 0; j < count; j++) {
+ dst[j * 4] = BSWAP32(dst[j * 4]);
+ }
+ }
+#else
+ if (unlikely(endian > 0.0)) {
+ uint32_t count = new_data.size() / 4;
+ uint16_t *dst = (uint16_t *)new_data.ptrw();
+ for (uint32_t j = 0; j < count; j++) {
+ dst[j * 4] = BSWAP32(dst[j * 4]);
+ }
+ }
+#endif
+ Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
+ img->convert(Image::FORMAT_RGBAH);
+ return img;
+}
+
+LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ for (int i = 0; i < p_atlas_slices; i++) {
+ String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
+ _store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in);
+
+ for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
+ int index = i * (p_bake_sh ? 4 : 1) + j;
+ String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
+ String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
+
+ _store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in);
+
+ List<String> args;
+ args.push_back("--device");
+ args.push_back("default");
+
+ args.push_back("--filter");
+ args.push_back("RTLightmap");
+
+ args.push_back("--hdr");
+ args.push_back(fname_light_in);
+
+ args.push_back("--nrm");
+ args.push_back(fname_norm_in);
+
+ args.push_back("--output");
+ args.push_back(fname_out);
+
+ String str;
+ int exitcode = 0;
+
+ Error err = OS::get_singleton()->execute(p_exe, args, &str, &exitcode, true);
+
+ da->remove(fname_light_in);
+
+ if (err != OK || exitcode != 0) {
+ da->remove(fname_out);
+ print_verbose(str);
+ ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat(TTR("OIDN denoiser failed, return code: %d"), exitcode));
+ }
+
+ Ref<Image> img = _read_pfm(fname_out);
+ da->remove(fname_out);
+
+ ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
+
+ Vector<uint8_t> old_data = p_rd->texture_get_data(p_source_light_tex, index);
+ Vector<uint8_t> new_data = img->get_data();
+ img.unref(); // Avoid copy on write.
+
+ uint32_t count = old_data.size() / 2;
+ const uint16_t *src = (const uint16_t *)old_data.ptr();
+ uint16_t *dst = (uint16_t *)new_data.ptrw();
+ for (uint32_t k = 0; k < count; k += 4) {
+ dst[k + 3] = src[k + 3];
+ }
+
+ p_rd->texture_update(p_dest_light_tex, index, new_data);
+ }
+ da->remove(fname_norm_in);
+ }
+ return BAKE_OK;
+}
+
LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) {
RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
DenoiseParams denoise_params;
@@ -742,6 +870,23 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
}
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
+ int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
+ String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
+
+ if (p_use_denoiser && denoiser == 1) {
+ // OIDN (external).
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ if (da->dir_exists(oidn_path)) {
+ if (OS::get_singleton()->get_name() == "Windows") {
+ oidn_path = oidn_path.path_join("oidnDenoise.exe");
+ } else {
+ oidn_path = oidn_path.path_join("oidnDenoise");
+ }
+ }
+ ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings."));
+ }
+
if (p_step_function) {
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
}
@@ -1501,8 +1646,15 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
{
- SWAP(light_accum_tex, light_accum_tex2);
- BakeError error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function);
+ BakeError error;
+ if (denoiser == 1) {
+ // OIDN (external).
+ error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
+ } else {
+ // JNLM (built-in).
+ SWAP(light_accum_tex, light_accum_tex2);
+ error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function);
+ }
if (unlikely(error != BAKE_OK)) {
return error;
}
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
index 7120a21b84..9537d5eae6 100644
--- a/modules/lightmapper_rd/lightmapper_rd.h
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -246,6 +246,10 @@ class LightmapperRD : public Lightmapper {
BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function);
+ Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
+ Ref<Image> _read_pfm(const String &p_name);
+ BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe);
+
public:
virtual void add_mesh(const MeshData &p_mesh) override;
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) override;
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index ce33f2ed1d..cccf7db96e 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -205,6 +205,14 @@ uint trace_ray(vec3 p_from, vec3 p_to
return RAY_ANY; //any hit good
#endif
+ vec3 position = p_from + dir * distance;
+ vec3 hit_cell = (position - params.to_cell_offset) * params.to_cell_size;
+ if (icell != ivec3(hit_cell)) {
+ // It's possible for the ray to hit a triangle in a position outside the bounds of the cell
+ // if it's large enough to cover multiple ones. The hit must be ignored if this is the case.
+ continue;
+ }
+
#if defined(MODE_UNOCCLUDE) || defined(MODE_BOUNCE_LIGHT) || defined(MODE_LIGHT_PROBES)
if (!backface) {
// the case of meshes having both a front and back face in the same plane is more common than
diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp
index 984ce88316..a6dd76efc9 100644
--- a/modules/lightmapper_rd/register_types.cpp
+++ b/modules/lightmapper_rd/register_types.cpp
@@ -58,6 +58,8 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512);
GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048);
GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64);
+
+ GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/denoising/denoiser", PROPERTY_HINT_ENUM, "JNLM,OIDN"), 0);
#ifndef _3D_DISABLED
GDREGISTER_CLASS(LightmapperRD);
Lightmapper::create_gpu = create_lightmapper_rd;
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 9846d60c33..859d77b262 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,6 +1,6 @@
# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "android", "web", "ios"]
# Eventually support for each them should be added back.
-supported_platforms = ["windows", "macos", "linuxbsd", "android"]
+supported_platforms = ["windows", "macos", "linuxbsd", "android", "ios"]
def can_build(env, platform):
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 95bf848cbf..23d0eb8b67 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -328,6 +328,11 @@ void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("/* */"); // delimited comment
}
+void CSharpLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const {
+ p_delimiters->push_back("///"); // single-line doc comment
+ p_delimiters->push_back("/** */"); // delimited doc comment
+}
+
void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '"); // character literal
p_delimiters->push_back("\" \""); // regular string literal
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 2ab80c132d..e381f0e840 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -419,6 +419,7 @@ public:
void get_reserved_words(List<String> *p_words) const override;
bool is_control_flow_keyword(String p_keyword) const override;
void get_comment_delimiters(List<String> *p_delimiters) const override;
+ void get_doc_comment_delimiters(List<String> *p_delimiters) const override;
void get_string_delimiters(List<String> *p_delimiters) const override;
bool is_using_templates() override;
virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
index 663eb14f07..ad3a10ba49 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -29,5 +29,7 @@
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
+ <None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
+ <None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
</ItemGroup>
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
index b35cec64f3..b6c72bce9d 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -60,6 +60,18 @@
<!-- Auto-detect the target Godot platform if it was not specified. -->
<PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+ <GodotTargetPlatform Condition=" $(RuntimeIdentifier.StartsWith('ios')) ">ios</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('android')) ">android</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('browser')) ">web</GodotTargetPlatform>
+
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('linux')) ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('freebsd')) ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('osx')) ">macos</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$(GodotTargetPlatform)' == '' and $(RuntimeIdentifier.StartsWith('win')) ">windows</GodotTargetPlatform>
+ </PropertyGroup>
+
+ <!-- Auto-detect the target Godot platform if it was not specified and there's no runtime identifier information. -->
+ <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">macos</GodotTargetPlatform>
@@ -97,4 +109,6 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
+
+ <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
index 4dcc96a1f6..29ef76a5e8 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -20,4 +20,8 @@
<PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" />
<PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " />
</ItemGroup>
+
+ <!-- iOS-specific build targets -->
+ <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.targets" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
+
</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props
new file mode 100644
index 0000000000..e3c953ccac
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.props
@@ -0,0 +1,8 @@
+<Project>
+ <PropertyGroup>
+ <PublishAot>true</PublishAot>
+ <PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
+ <UseNativeAOTRuntime>true</UseNativeAOTRuntime>
+ <TrimmerSingleWarn>false</TrimmerSingleWarn>
+ </PropertyGroup>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
new file mode 100644
index 0000000000..d8129a6652
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/iOSNativeAOT.targets
@@ -0,0 +1,58 @@
+<Project>
+ <ItemGroup>
+ <TrimmerRootAssembly Include="GodotSharp" />
+ <TrimmerRootAssembly Include="$(TargetName)" />
+ <LinkerArg Include="-install_name '@rpath/$(TargetName)$(NativeBinaryExt)'" />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <LinkStandardCPlusPlusLibrary>true</LinkStandardCPlusPlusLibrary>
+ <FindXCode Condition=" '$(XCodePath)' == '' and '$([MSBuild]::IsOsPlatform(OSX))' ">true</FindXCode>
+ <XCodePath Condition=" '$(XCodePath)' == '' ">/Applications/Xcode.app/Contents/Developer</XCodePath>
+ <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+ </PropertyGroup>
+
+ <Target Name="PrepareBeforeIlcCompile"
+ BeforeTargets="IlcCompile">
+
+ <Copy SourceFiles="%(ResolvedRuntimePack.PackageDirectory)/runtimes/$(RuntimeIdentifier)/native/icudt.dat" DestinationFolder="$(PublishDir)"/>
+
+ <!-- We need to find the path to Xcode so we can set manual linker args to the correct SDKs
+ Once https://github.com/dotnet/runtime/issues/88737 is released, we can take this out
+ -->
+
+ <Exec Command="xcrun xcode-select -p" ConsoleToMSBuild="true" Condition=" '$(FindXCode)' == 'true' ">
+ <Output TaskParameter="ConsoleOutput" PropertyName="XcodeSelect" />
+ </Exec>
+
+ <PropertyGroup Condition=" '$(FindXCode)' == 'true' ">
+ <XCodePath>$(XcodeSelect)</XCodePath>
+ <XCodePath>$([MSBuild]::EnsureTrailingSlash('$(XCodePath)'))</XCodePath>
+ </PropertyGroup>
+
+ <Message Importance="normal" Text="Found XCode at $(XcodeSelect)" Condition=" '$(FindXCode)' == 'true' "/>
+
+ <ItemGroup>
+ <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk%22"
+ Condition=" $(RuntimeIdentifier.Contains('simulator')) "/>
+ <LinkerArg Include="-isysroot %22$(XCodePath)Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk%22"
+ Condition=" !$(RuntimeIdentifier.Contains('simulator')) "/>
+ </ItemGroup>
+
+ </Target>
+
+ <Target Name="FixSymbols"
+ AfterTargets="Publish">
+
+ <RemoveDir Directories="$(PublishDir)$(TargetName).framework.dSYM"/>
+
+ <!-- create-xcframework (called from the export plugin wants the symbol files in a directory
+ with a slightly different name from the one created by dotnet publish, so we copy them over
+ to the correctly-named directory -->
+ <ItemGroup>
+ <SymbolFiles Include="$(NativeBinary).dsym\**\*.*"/>
+ </ItemGroup>
+ <Copy SourceFiles="@(SymbolFiles)" DestinationFolder="$(PublishDir)$(TargetName).framework.dSYM"/>
+ </Target>
+
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index f3c8e89dff..04ea46e9ef 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -23,6 +23,13 @@ namespace GodotTools.ProjectEditor
var mainGroup = root.AddPropertyGroup();
mainGroup.AddProperty("TargetFramework", "net6.0");
+
+ var net7 = mainGroup.AddProperty("TargetFramework", "net7.0");
+ net7.Condition = " '$(GodotTargetPlatform)' == 'android' ";
+
+ var net8 = mainGroup.AddProperty("TargetFramework", "net8.0");
+ net8.Condition = " '$(GodotTargetPlatform)' == 'ios' ";
+
mainGroup.AddProperty("EnableDynamicLoading", "true");
string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 907511d140..7cf98b8f1f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
@@ -67,7 +68,7 @@ namespace GodotTools.Build
{
BuildStarted?.Invoke(buildInfo);
- // Required in order to update the build tasks list
+ // Required in order to update the build tasks list.
Internal.GodotMainIteration();
try
@@ -162,7 +163,7 @@ namespace GodotTools.Build
{
BuildStarted?.Invoke(buildInfo);
- // Required in order to update the build tasks list
+ // Required in order to update the build tasks list.
Internal.GodotMainIteration();
try
@@ -323,6 +324,45 @@ namespace GodotTools.Build
) => PublishProjectBlocking(CreatePublishBuildInfo(configuration,
platform, runtimeIdentifier, publishOutputDir, includeDebugSymbols));
+ public static bool GenerateXCFrameworkBlocking(
+ List<string> outputPaths,
+ string xcFrameworkPath)
+ {
+ using var pr = new EditorProgress("generate_xcframework", "Generating XCFramework...", 1);
+
+ pr.Step("Running xcodebuild -create-xcframework", 0);
+
+ if (!GenerateXCFramework(outputPaths, xcFrameworkPath))
+ {
+ ShowBuildErrorDialog("Failed to generate XCFramework");
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath)
+ {
+ // Required in order to update the build tasks list.
+ Internal.GodotMainIteration();
+
+ try
+ {
+ int exitCode = BuildSystem.GenerateXCFramework(outputPaths, xcFrameworkPath, StdOutputReceived, StdErrorReceived);
+
+ if (exitCode != 0)
+ PrintVerbose(
+ $"xcodebuild create-xcframework exited with code: {exitCode}.");
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+
public static bool EditorBuildCallback()
{
if (!File.Exists(GodotSharpDirs.ProjectCsProjPath))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 8a292fd73a..57b5598a78 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -9,7 +9,9 @@ using System.Text;
using System.Threading.Tasks;
using Godot;
using GodotTools.BuildLogger;
+using GodotTools.Internals;
using GodotTools.Utils;
+using Directory = GodotTools.Utils.Directory;
namespace GodotTools.Build
{
@@ -293,5 +295,81 @@ namespace GodotTools.Build
foreach (string env in platformEnvironmentVariables)
environmentVariables.Remove(env);
}
+
+ private static Process DoGenerateXCFramework(List<string> outputPaths, string xcFrameworkPath,
+ Action<string> stdOutHandler, Action<string> stdErrHandler)
+ {
+ if (Directory.Exists(xcFrameworkPath))
+ {
+ Directory.Delete(xcFrameworkPath, true);
+ }
+
+ var startInfo = new ProcessStartInfo("xcrun");
+
+ BuildXCFrameworkArguments(outputPaths, xcFrameworkPath, startInfo.ArgumentList);
+
+ string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Packaging: ")).ToString();
+ stdOutHandler?.Invoke(launchMessage);
+ if (Godot.OS.IsStdOutVerbose())
+ Console.WriteLine(launchMessage);
+
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.UseShellExecute = false;
+
+ if (OperatingSystem.IsWindows())
+ {
+ startInfo.StandardOutputEncoding = Encoding.UTF8;
+ startInfo.StandardErrorEncoding = Encoding.UTF8;
+ }
+
+ // Needed when running from Developer Command Prompt for VS.
+ RemovePlatformVariable(startInfo.EnvironmentVariables);
+
+ var process = new Process { StartInfo = startInfo };
+
+ if (stdOutHandler != null)
+ process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
+ if (stdErrHandler != null)
+ process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
+
+ process.Start();
+
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ return process;
+ }
+
+ public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string> stdOutHandler, Action<string> stdErrHandler)
+ {
+ using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler))
+ {
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
+
+ private static void BuildXCFrameworkArguments(List<string> outputPaths,
+ string xcFrameworkPath, Collection<string> arguments)
+ {
+ var baseDylib = $"{GodotSharpDirs.ProjectAssemblyName}.dylib";
+ var baseSym = $"{GodotSharpDirs.ProjectAssemblyName}.framework.dSYM";
+
+ arguments.Add("xcodebuild");
+ arguments.Add("-create-xcframework");
+
+ foreach (var outputPath in outputPaths)
+ {
+ arguments.Add("-library");
+ arguments.Add(Path.Combine(outputPath, baseDylib));
+ arguments.Add("-debug-symbols");
+ arguments.Add(Path.Combine(outputPath, baseSym));
+ }
+
+ arguments.Add("-output");
+ arguments.Add(xcFrameworkPath);
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index b98df190ca..018298cb33 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -6,9 +6,7 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using GodotTools.Build;
-using GodotTools.Core;
using GodotTools.Internals;
-using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
using OS = GodotTools.Utils.OS;
@@ -77,7 +75,7 @@ namespace GodotTools.Export
$"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}",
nameof(path));
- // TODO What if the source file is not part of the game's C# project
+ // TODO: What if the source file is not part of the game's C# project?
bool includeScriptsContent = (bool)GetOption("dotnet/include_scripts_content");
@@ -89,7 +87,7 @@ namespace GodotTools.Export
// Because of this, we add a file which contains a line break.
AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false);
- // Tell the Godot exporter that we already took care of the file
+ // Tell the Godot exporter that we already took care of the file.
Skip();
}
}
@@ -119,7 +117,7 @@ namespace GodotTools.Export
private void _ExportBeginImpl(string[] features, bool isDebug, string path, long flags)
{
- _ = flags; // Unused
+ _ = flags; // Unused.
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return;
@@ -127,115 +125,261 @@ namespace GodotTools.Export
if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported.");
- if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android }
+ if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
.Contains(platform))
{
throw new NotImplementedException("Target platform not yet implemented.");
}
- string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
-
- bool includeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols");
+ PublishConfig publishConfig = new()
+ {
+ BuildConfig = isDebug ? "ExportDebug" : "ExportRelease",
+ IncludeDebugSymbols = (bool)GetOption("dotnet/include_debug_symbols"),
+ RidOS = DetermineRuntimeIdentifierOS(platform),
+ Archs = new List<string>(),
+ UseTempDir = platform != OS.Platforms.iOS, // xcode project links directly to files in the publish dir, so use one that sticks around.
+ BundleOutputs = true,
+ };
- var archs = new List<string>();
if (features.Contains("x86_64"))
{
- archs.Add("x86_64");
+ publishConfig.Archs.Add("x86_64");
}
+
if (features.Contains("x86_32"))
{
- archs.Add("x86_32");
+ publishConfig.Archs.Add("x86_32");
}
+
if (features.Contains("arm64"))
{
- archs.Add("arm64");
+ publishConfig.Archs.Add("arm64");
}
+
if (features.Contains("arm32"))
{
- archs.Add("arm32");
+ publishConfig.Archs.Add("arm32");
}
+
if (features.Contains("universal"))
{
if (platform == OS.Platforms.MacOS)
{
- archs.Add("x86_64");
- archs.Add("arm64");
+ publishConfig.Archs.Add("x86_64");
+ publishConfig.Archs.Add("arm64");
}
}
- bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || features.Contains("android");
+ var targets = new List<PublishConfig> { publishConfig };
- foreach (var arch in archs)
+ if (platform == OS.Platforms.iOS)
{
- string ridOS = DetermineRuntimeIdentifierOS(platform);
- string ridArch = DetermineRuntimeIdentifierArch(arch);
- string runtimeIdentifier = $"{ridOS}-{ridArch}";
- string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
- if (platform == OS.Platforms.MacOS)
+ targets.Add(new PublishConfig
{
- projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
- }
+ BuildConfig = publishConfig.BuildConfig,
+ Archs = new List<string> { "arm64", "x86_64" },
+ BundleOutputs = false,
+ IncludeDebugSymbols = publishConfig.IncludeDebugSymbols,
+ RidOS = OS.DotNetOS.iOSSimulator,
+ UseTempDir = true,
+ });
+ }
- // Create temporary publish output directory
+ List<string> outputPaths = new();
- string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
- $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
+ bool embedBuildResults = (bool)GetOption("dotnet/embed_build_outputs") || platform == OS.Platforms.Android;
- _tempFolders.Add(publishOutputTempDir);
+ foreach (PublishConfig config in targets)
+ {
+ string ridOS = config.RidOS;
+ string buildConfig = config.BuildConfig;
+ bool includeDebugSymbols = config.IncludeDebugSymbols;
- if (!Directory.Exists(publishOutputTempDir))
- Directory.CreateDirectory(publishOutputTempDir);
+ foreach (string arch in config.Archs)
+ {
+ string ridArch = DetermineRuntimeIdentifierArch(arch);
+ string runtimeIdentifier = $"{ridOS}-{ridArch}";
+ string projectDataDirName = $"data_{GodotSharpDirs.CSharpProjectName}_{platform}_{arch}";
+ if (platform == OS.Platforms.MacOS)
+ {
+ projectDataDirName = Path.Combine("Contents", "Resources", projectDataDirName);
+ }
- // Execute dotnet publish
+ // Create temporary publish output directory.
+ string publishOutputDir;
- if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
- runtimeIdentifier, publishOutputTempDir, includeDebugSymbols))
- {
- throw new InvalidOperationException("Failed to build project.");
- }
+ if (config.UseTempDir)
+ {
+ publishOutputDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet",
+ $"{System.Environment.ProcessId}-{buildConfig}-{runtimeIdentifier}");
+ _tempFolders.Add(publishOutputDir);
+ }
+ else
+ {
+ publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
+ $"{buildConfig}-{runtimeIdentifier}");
- string soExt = ridOS switch
- {
- OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
- OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
- _ => "so"
- };
-
- if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll"))
- // NativeAOT shared library output
- && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}")))
- {
- throw new NotSupportedException(
- "Publish succeeded but project assembly not found in the output directory");
- }
+ }
- var manifest = new StringBuilder();
+ outputPaths.Add(publishOutputDir);
- // Add to the exported project shared object list or packed resources.
- foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories))
- {
- if (embedBuildResults)
+ if (!Directory.Exists(publishOutputDir))
+ Directory.CreateDirectory(publishOutputDir);
+
+ // Execute dotnet publish.
+ if (!BuildManager.PublishProjectBlocking(buildConfig, platform,
+ runtimeIdentifier, publishOutputDir, includeDebugSymbols))
+ {
+ throw new InvalidOperationException("Failed to build project.");
+ }
+
+ string soExt = ridOS switch
{
- var filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputTempDir, file));
- var fileData = File.ReadAllBytes(file);
- var hash = Convert.ToBase64String(SHA512.HashData(fileData));
+ OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
+ OS.DotNetOS.OSX or OS.DotNetOS.iOS or OS.DotNetOS.iOSSimulator => "dylib",
+ _ => "so"
+ };
- manifest.Append($"{filePath}\t{hash}\n");
+ string assemblyPath = Path.Combine(publishOutputDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll");
+ string nativeAotPath = Path.Combine(publishOutputDir,
+ $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}");
- AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ if (!File.Exists(assemblyPath) && !File.Exists(nativeAotPath))
+ {
+ throw new NotSupportedException(
+ $"Publish succeeded but project assembly not found at '{assemblyPath}' or '{nativeAotPath}'.");
}
- else
+
+ // For ios simulator builds, skip packaging the build outputs.
+ if (!config.BundleOutputs)
+ continue;
+
+ var manifest = new StringBuilder();
+
+ // Add to the exported project shared object list or packed resources.
+ RecursePublishContents(publishOutputDir,
+ filterDir: dir =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Exclude dsym folders.
+ return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ return true;
+ },
+ filterFile: file =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Exclude the dylib artifact, since it's included separately as an xcframework.
+ return Path.GetFileName(file) != $"{GodotSharpDirs.ProjectAssemblyName}.dylib";
+ }
+
+ return true;
+ },
+ recurseDir: dir =>
+ {
+ if (platform == OS.Platforms.iOS)
+ {
+ // Don't recurse into dsym folders.
+ return !dir.EndsWith(".dsym", StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ return true;
+ },
+ addEntry: (path, isFile) =>
+ {
+ // We get called back for both directories and files, but we only package files for now.
+ if (isFile)
+ {
+ if (embedBuildResults)
+ {
+ string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path));
+ byte[] fileData = File.ReadAllBytes(path);
+ string hash = Convert.ToBase64String(SHA512.HashData(fileData));
+
+ manifest.Append($"{filePath}\t{hash}\n");
+
+ AddFile($"res://.godot/mono/publish/{arch}/{filePath}", fileData, false);
+ }
+ else
+ {
+ if (platform == OS.Platforms.iOS && path.EndsWith(".dat"))
+ {
+ AddIosBundleFile(path);
+ }
+ else
+ {
+ AddSharedObject(path, tags: null,
+ Path.Join(projectDataDirName,
+ Path.GetRelativePath(publishOutputDir,
+ Path.GetDirectoryName(path))));
+ }
+ }
+ }
+ });
+
+ if (embedBuildResults)
{
- AddSharedObject(file, tags: null,
- Path.Join(projectDataDirName,
- Path.GetRelativePath(publishOutputTempDir, Path.GetDirectoryName(file))));
+ byte[] fileData = Encoding.Default.GetBytes(manifest.ToString());
+ AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
}
}
+ }
+
+ if (platform == OS.Platforms.iOS)
+ {
+ if (outputPaths.Count > 2)
+ {
+ // lipo the simulator binaries together
+ // TODO: Move this to the native lipo implementation we have in the macos export plugin.
+ var lipoArgs = new List<string>();
+ lipoArgs.Add("-create");
+ lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")));
+ lipoArgs.Add("-output");
+ lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"));
+
+ int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
+ if (lipoExitCode != 0)
+ throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
+
+ outputPaths.RemoveRange(2, outputPaths.Count - 2);
+ }
+
+ var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
+ $"{GodotSharpDirs.ProjectAssemblyName}.xcframework");
+ if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
+ Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
+ {
+ throw new InvalidOperationException("Failed to generate xcframework.");
+ }
+
+ AddIosEmbeddedFramework(xcFrameworkPath);
+ }
+ }
+
+ private static void RecursePublishContents(string path, Func<string, bool> filterDir,
+ Func<string, bool> filterFile, Func<string, bool> recurseDir,
+ Action<string, bool> addEntry)
+ {
+ foreach (string file in Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly))
+ {
+ if (filterFile(file))
+ {
+ addEntry(file, true);
+ }
+ }
- if (embedBuildResults)
+ foreach (string dir in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
+ {
+ if (filterDir(dir))
+ {
+ addEntry(dir, false);
+ }
+ else if (recurseDir(dir))
{
- var fileData = Encoding.Default.GetBytes(manifest.ToString());
- AddFile($"res://.godot/mono/publish/{arch}/.dotnet-publish-manifest", fileData, false);
+ RecursePublishContents(dir, filterDir, filterFile, recurseDir, addEntry);
}
}
}
@@ -304,5 +448,15 @@ namespace GodotTools.Export
platform = null;
return false;
}
+
+ private struct PublishConfig
+ {
+ public bool UseTempDir;
+ public bool BundleOutputs;
+ public string RidOS;
+ public List<string> Archs;
+ public string BuildConfig;
+ public bool IncludeDebugSymbols;
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
index 55b413453d..67891a0594 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
@@ -118,6 +118,16 @@ namespace GodotTools.Internals
}
}
+ public static string ProjectBaseOutputPath
+ {
+ get
+ {
+ if (_projectCsProjPath == null)
+ DetermineProjectLocation();
+ return Path.Combine(Path.GetDirectoryName(_projectCsProjPath)!, ".godot", "mono", "temp", "bin");
+ }
+ }
+
public static string LogsDirPathFor(string solution, string configuration)
=> Path.Combine(BuildLogsDirs, $"{solution.Md5Text()}_{configuration}");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index bff0c0df7c..c24b730c89 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -56,6 +56,7 @@ namespace GodotTools.Utils
public const string Win10 = "win10";
public const string Android = "android";
public const string iOS = "ios";
+ public const string iOSSimulator = "iossimulator";
public const string Browser = "browser";
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
index 94788ef680..9126495a27 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs
@@ -28,7 +28,7 @@ partial class AnimationNode
partial class AnimationPlayer
{
- /// <inheritdoc cref="CallbackModeMethod"/>
+ /// <inheritdoc cref="AnimationMixer.CallbackModeMethod"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationMethodCallMode MethodCallMode
{
@@ -36,7 +36,7 @@ partial class AnimationPlayer
set => CallbackModeMethod = (AnimationCallbackModeMethod)value;
}
- /// <inheritdoc cref="Active"/>
+ /// <inheritdoc cref="AnimationMixer.Active"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool PlaybackActive
{
@@ -44,7 +44,7 @@ partial class AnimationPlayer
set => Active = value;
}
- /// <inheritdoc cref="CallbackModeProcess"/>
+ /// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationProcessCallback PlaybackProcessMode
{
@@ -55,7 +55,7 @@ partial class AnimationPlayer
partial class AnimationTree
{
- /// <inheritdoc cref="CallbackModeProcess"/>
+ /// <inheritdoc cref="AnimationMixer.CallbackModeProcess"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public AnimationProcessCallback ProcessCallback
{
@@ -120,7 +120,7 @@ partial class GraphEdit
partial class GraphNode
{
- /// <inheritdoc cref="DeleteRequest"/>
+ /// <inheritdoc cref="GraphElement.DeleteRequest"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public event Action CloseRequest
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 10aeeae995..13c0cde1ef 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -36,6 +36,9 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Array"/> from the given collection's elements.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="collection"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="collection">The collection of elements to construct from.</param>
/// <returns>A new Godot Array.</returns>
public Array(IEnumerable<Variant> collection) : this()
@@ -50,6 +53,9 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Array"/> from the given objects.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="array">The objects to put in the new array.</param>
/// <returns>A new Godot Array.</returns>
public Array(Variant[] array)
@@ -68,6 +74,13 @@ namespace Godot.Collections
this[i] = array[i];
}
+ /// <summary>
+ /// Constructs a new <see cref="Array"/> from the given span's elements.
+ /// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <returns>A new Godot Array.</returns>
public Array(Span<StringName> array)
{
if (array == null)
@@ -84,6 +97,13 @@ namespace Godot.Collections
this[i] = array[i];
}
+ /// <summary>
+ /// Constructs a new <see cref="Array"/> from the given span's elements.
+ /// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <returns>A new Godot Array.</returns>
public Array(Span<NodePath> array)
{
if (array == null)
@@ -100,6 +120,13 @@ namespace Godot.Collections
this[i] = array[i];
}
+ /// <summary>
+ /// Constructs a new <see cref="Array"/> from the given span's elements.
+ /// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <returns>A new Godot Array.</returns>
public Array(Span<Rid> array)
{
if (array == null)
@@ -121,6 +148,13 @@ namespace Godot.Collections
// fine as long as the array is not mutated. However, Span does this type checking at
// instantiation, so it's not possible to use it even when not mutating anything.
// ReSharper disable once RedundantNameQualifier
+ /// <summary>
+ /// Constructs a new <see cref="Array"/> from the given ReadOnlySpan's elements.
+ /// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <returns>A new Godot Array.</returns>
public Array(ReadOnlySpan<GodotObject> array)
{
if (array == null)
@@ -861,9 +895,15 @@ namespace Godot.Collections
/// Copies the elements of this <see cref="Array"/> to the given
/// <see cref="Variant"/> C# array, starting at the given index.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
/// </exception>
+ /// <exception cref="ArgumentException">
+ /// The destination array was not long enough.
+ /// </exception>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
public void CopyTo(Variant[] array, int arrayIndex)
@@ -1031,6 +1071,7 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new empty <see cref="Array{T}"/>.
/// </summary>
+ /// <returns>A new Godot Array.</returns>
public Array()
{
_underlyingArray = new Array();
@@ -1039,6 +1080,9 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Array{T}"/> from the given collection's elements.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="collection"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="collection">The collection of elements to construct from.</param>
/// <returns>A new Godot Array.</returns>
public Array(IEnumerable<T> collection)
@@ -1055,6 +1099,9 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Array{T}"/> from the given items.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="array">The items to put in the new array.</param>
/// <returns>A new Godot Array.</returns>
public Array(T[] array)
@@ -1071,9 +1118,16 @@ namespace Godot.Collections
/// <summary>
/// Constructs a typed <see cref="Array{T}"/> from an untyped <see cref="Array"/>.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="array">The untyped array to construct from.</param>
+ /// <returns>A new Godot Array.</returns>
public Array(Array array)
{
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+
_underlyingArray = array;
}
@@ -1085,6 +1139,7 @@ namespace Godot.Collections
/// Converts this typed <see cref="Array{T}"/> to an untyped <see cref="Array"/>.
/// </summary>
/// <param name="from">The typed array to convert.</param>
+ /// <returns>A new Godot Array, or <see langword="null"/> if <see paramref="from"/> was null.</returns>
public static explicit operator Array(Array<T> from)
{
return from?._underlyingArray;
@@ -1695,9 +1750,15 @@ namespace Godot.Collections
/// Copies the elements of this <see cref="Array{T}"/> to the given
/// C# array, starting at the given index.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
/// </exception>
+ /// <exception cref="ArgumentException">
+ /// The destination array was not long enough.
+ /// </exception>
/// <param name="array">The C# array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
public void CopyTo(T[] array, int arrayIndex)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 923b2adafd..2a72ebc32b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -385,6 +385,15 @@ namespace Godot.Collections
/// Copies the elements of this <see cref="Dictionary"/> to the given untyped
/// <see cref="KeyValuePair{TKey, TValue}"/> array, starting at the given index.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// The destination array was not long enough.
+ /// </exception>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
void ICollection<KeyValuePair<Variant, Variant>>.CopyTo(KeyValuePair<Variant, Variant>[] array, int arrayIndex)
@@ -499,6 +508,7 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new empty <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
+ /// <returns>A new Godot Dictionary.</returns>
public Dictionary()
{
_underlyingDict = new Dictionary();
@@ -507,6 +517,9 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="dictionary"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="dictionary">The dictionary to construct from.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(IDictionary<TKey, TValue> dictionary)
@@ -523,10 +536,16 @@ namespace Godot.Collections
/// <summary>
/// Constructs a new <see cref="Dictionary{TKey, TValue}"/> from the given dictionary's elements.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="dictionary"/> is <see langword="null"/>.
+ /// </exception>
/// <param name="dictionary">The dictionary to construct from.</param>
/// <returns>A new Godot Dictionary.</returns>
public Dictionary(Dictionary dictionary)
{
+ if (dictionary == null)
+ throw new ArgumentNullException(nameof(dictionary));
+
_underlyingDict = dictionary;
}
@@ -539,6 +558,7 @@ namespace Godot.Collections
/// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>.
/// </summary>
/// <param name="from">The typed dictionary to convert.</param>
+ /// <returns>A new Godot Dictionary, or <see langword="null"/> if <see paramref="from"/> was null.</returns>
public static explicit operator Dictionary(Dictionary<TKey, TValue> from)
{
return from?._underlyingDict;
@@ -555,6 +575,8 @@ namespace Godot.Collections
/// elements will be shallow copied regardless of the <paramref name="deep"/>
/// setting.
/// </summary>
+ /// <param name="deep">If <see langword="true"/>, performs a deep copy.</param>
+ /// <returns>A new Godot Dictionary.</returns>
public Dictionary<TKey, TValue> Duplicate(bool deep = false)
{
return new Dictionary<TKey, TValue>(_underlyingDict.Duplicate(deep));
@@ -688,6 +710,9 @@ namespace Godot.Collections
/// <exception cref="InvalidOperationException">
/// The dictionary is read-only.
/// </exception>
+ /// <exception cref="ArgumentException">
+ /// An element with the same <paramref name="key"/> already exists.
+ /// </exception>
/// <param name="key">The key at which to add the object.</param>
/// <param name="value">The object to add.</param>
public void Add(TKey key, TValue value)
@@ -810,6 +835,15 @@ namespace Godot.Collections
/// Copies the elements of this <see cref="Dictionary{TKey, TValue}"/> to the given
/// untyped C# array, starting at the given index.
/// </summary>
+ /// <exception cref="ArgumentNullException">
+ /// The <paramref name="array"/> is <see langword="null"/>.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="arrayIndex"/> is less than 0 or greater than the array's size.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// The destination array was not long enough.
+ /// </exception>
/// <param name="array">The array to copy to.</param>
/// <param name="arrayIndex">The index to start at.</param>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index faf3ae7b04..c55c5d8111 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -89,7 +89,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
r_return_value = Variant();
- ERR_FAIL_COND(delegate_handle.value == nullptr);
+ ERR_FAIL_NULL(delegate_handle.value);
GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value);
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 247968e251..23f2f2ff13 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -322,7 +322,7 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
#if defined(WINDOWS_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll");
-#elif defined(MACOS_ENABLED)
+#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib");
#elif defined(UNIX_ENABLED)
String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so");
@@ -330,23 +330,19 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
#error "Platform not supported (yet?)"
#endif
- if (FileAccess::exists(native_aot_so_path)) {
- Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
-
- if (err != OK) {
- return nullptr;
- }
+ Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
- void *lib = r_aot_dll_handle;
+ if (err != OK) {
+ return nullptr;
+ }
- void *symbol = nullptr;
+ void *lib = r_aot_dll_handle;
- err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
- ERR_FAIL_COND_V(err != OK, nullptr);
- return (godot_plugins_initialize_fn)symbol;
- }
+ void *symbol = nullptr;
- return nullptr;
+ err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
+ ERR_FAIL_COND_V(err != OK, nullptr);
+ return (godot_plugins_initialize_fn)symbol;
}
#endif
@@ -376,11 +372,13 @@ void GDMono::initialize() {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+#if !defined(IOS_ENABLED)
// Check that the .NET assemblies directory exists before trying to use it.
if (!DirAccess::exists(GodotSharpDirs::get_api_assemblies_dir())) {
OS::get_singleton()->alert(vformat(RTR("Unable to find the .NET assemblies directory.\nMake sure the '%s' directory exists and contains the .NET assemblies."), GodotSharpDirs::get_api_assemblies_dir()), RTR(".NET assemblies not found"));
ERR_FAIL_MSG(".NET: Assemblies not found");
}
+#endif
if (!load_hostfxr(hostfxr_dll_handle)) {
#if !defined(TOOLS_ENABLED)
diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h
deleted file mode 100644
index cb397c8b46..0000000000
--- a/modules/mono/mono_gd/support/ios_support.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/**************************************************************************/
-/* ios_support.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef IOS_SUPPORT_H
-#define IOS_SUPPORT_H
-
-#if defined(IOS_ENABLED)
-
-#include "core/string/ustring.h"
-
-namespace gdmono {
-namespace ios {
-namespace support {
-
-void initialize();
-void cleanup();
-} // namespace support
-} // namespace ios
-} // namespace gdmono
-
-#endif // IOS_ENABLED
-
-#endif // IOS_SUPPORT_H
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
deleted file mode 100644
index df8b3e2626..0000000000
--- a/modules/mono/mono_gd/support/ios_support.mm
+++ /dev/null
@@ -1,150 +0,0 @@
-/**************************************************************************/
-/* ios_support.mm */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "ios_support.h"
-
-#if defined(IOS_ENABLED)
-
-#include "../gd_mono_marshal.h"
-
-#include "core/ustring.h"
-
-#import <Foundation/Foundation.h>
-#include <os/log.h>
-
-// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m
-
-// Definition generated by the Godot exporter
-extern "C" void gd_mono_setup_aot();
-
-namespace gdmono {
-namespace ios {
-namespace support {
-
-void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
- os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message);
- if (fatal) {
- os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1);
- exit(1);
- }
-}
-
-void initialize() {
- mono_dllmap_insert(nullptr, "System.Native", nullptr, "__Internal", nullptr);
- mono_dllmap_insert(nullptr, "System.IO.Compression.Native", nullptr, "__Internal", nullptr);
- mono_dllmap_insert(nullptr, "System.Security.Cryptography.Native.Apple", nullptr, "__Internal", nullptr);
-
-#ifdef IOS_DEVICE
- // This function is defined in an auto-generated source file
- gd_mono_setup_aot();
-#endif
-
- mono_set_signal_chaining(true);
- mono_set_crash_chaining(true);
-}
-
-void cleanup() {
-}
-} // namespace support
-} // namespace ios
-} // namespace gdmono
-
-// The following are P/Invoke functions required by the monotouch profile of the BCL.
-// These are P/Invoke functions and not internal calls, hence why they use
-// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
-
-#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
-
-GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() {
- NSLocale *locale = [NSLocale currentLocale];
- NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
- if (countryCode == nullptr) {
- return strdup("US");
- }
- return strdup([countryCode UTF8String]);
-}
-
-GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) {
- int length = 0;
- const uint16_t *ptr = p_unicode_message;
- while (*ptr++) {
- length += sizeof(uint16_t);
- }
- NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding];
-
- os_log_info(OS_LOG_DEFAULT, "%{public}@", msg);
-}
-
-GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) {
- NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder;
- NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject];
- NSString *path = [url path];
- return strdup([path UTF8String]);
-}
-
-GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() {
- NSTimeZone *tz = nil;
- tz = [NSTimeZone localTimeZone];
- NSString *name = [tz name];
- return (name != nil) ? strdup([name UTF8String]) : strdup("Local");
-}
-
-GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) {
- NSArray *array = [NSTimeZone knownTimeZoneNames];
- *p_count = array.count;
- char **result = (char **)malloc(sizeof(char *) * (*p_count));
- for (uint32_t i = 0; i < *p_count; i++) {
- NSString *s = [array objectAtIndex:i];
- result[i] = strdup(s.UTF8String);
- }
- return result;
-}
-
-GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before
- NSTimeZone *tz = nil;
- if (p_name) {
- NSString *n = [[NSString alloc] initWithUTF8String:p_name];
- tz = [[NSTimeZone alloc] initWithName:n];
- } else {
- tz = [NSTimeZone localTimeZone];
- }
- NSData *data = [tz data];
- *p_size = [data length];
- void *result = malloc(*p_size);
- memcpy(result, data.bytes, *p_size);
- return result;
-}
-
-GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) {
- // FIXME: What's this for? No idea how to implement.
- os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'");
-}
-
-#endif // IOS_ENABLED
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
index 9b05fa884b..a4d2aed2d6 100644
--- a/modules/multiplayer/multiplayer_debugger.cpp
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -239,8 +239,8 @@ void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_proces
MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) {
ERR_FAIL_NULL(p_sync);
synchronizer = p_sync->get_instance_id();
- if (p_sync->get_replication_config().is_valid()) {
- config = p_sync->get_replication_config()->get_instance_id();
+ if (p_sync->get_replication_config_ptr()) {
+ config = p_sync->get_replication_config_ptr()->get_instance_id();
}
if (p_sync->get_root_node()) {
root_node = p_sync->get_root_node()->get_instance_id();
diff --git a/modules/multiplayer/multiplayer_synchronizer.cpp b/modules/multiplayer/multiplayer_synchronizer.cpp
index 233f15c3a4..21f1f86dbf 100644
--- a/modules/multiplayer/multiplayer_synchronizer.cpp
+++ b/modules/multiplayer/multiplayer_synchronizer.cpp
@@ -441,6 +441,10 @@ List<NodePath> MultiplayerSynchronizer::get_delta_properties(uint64_t p_indexes)
return out;
}
+SceneReplicationConfig *MultiplayerSynchronizer::get_replication_config_ptr() const {
+ return replication_config.ptr();
+}
+
MultiplayerSynchronizer::MultiplayerSynchronizer() {
// Publicly visible by default.
peer_visibility.insert(0);
diff --git a/modules/multiplayer/multiplayer_synchronizer.h b/modules/multiplayer/multiplayer_synchronizer.h
index 7b77e691d1..99613de29b 100644
--- a/modules/multiplayer/multiplayer_synchronizer.h
+++ b/modules/multiplayer/multiplayer_synchronizer.h
@@ -118,6 +118,7 @@ public:
List<Variant> get_delta_state(uint64_t p_cur_usec, uint64_t p_last_usec, uint64_t &r_indexes);
List<NodePath> get_delta_properties(uint64_t p_indexes);
+ SceneReplicationConfig *get_replication_config_ptr() const;
MultiplayerSynchronizer();
};
diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp
index 8d102ca981..56cd0bec18 100644
--- a/modules/multiplayer/scene_cache_interface.cpp
+++ b/modules/multiplayer/scene_cache_interface.cpp
@@ -44,9 +44,8 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) {
path_get_cache.erase(p_id);
// Cleanup sent cache.
// Some refactoring is needed to make this faster and do paths GC.
- for (const KeyValue<NodePath, PathSentCache> &E : path_send_cache) {
- PathSentCache *psc = path_send_cache.getptr(E.key);
- psc->confirmed_peers.erase(p_id);
+ for (KeyValue<ObjectID, PathSentCache> &E : path_send_cache) {
+ E.value.confirmed_peers.erase(p_id);
}
}
}
@@ -67,7 +66,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
String paths;
paths.parse_utf8((const char *)(p_packet + ofs), p_packet_len - ofs);
- NodePath path = paths;
+ const NodePath path = paths;
if (!path_get_cache.has(p_from)) {
path_get_cache[p_from] = PathGetCache();
@@ -81,7 +80,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
}
PathGetCache::NodeInfo ni;
- ni.path = path;
+ ni.path = node->get_path();
path_get_cache[p_from].nodes[id] = ni;
@@ -106,19 +105,24 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac
void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(p_packet_len < 3, "Invalid packet received. Size too small.");
+ Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path());
+ ERR_FAIL_NULL(root_node);
const bool valid_rpc_checksum = p_packet[1];
String paths;
paths.parse_utf8((const char *)&p_packet[2], p_packet_len - 2);
- NodePath path = paths;
+ const NodePath path = paths;
if (valid_rpc_checksum == false) {
ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path);
}
- PathSentCache *psc = path_send_cache.getptr(path);
+ Node *node = root_node->get_node(path);
+ ERR_FAIL_NULL(node);
+
+ PathSentCache *psc = path_send_cache.getptr(node->get_instance_id());
ERR_FAIL_NULL_MSG(psc, "Invalid packet received. Tries to confirm a path which was not found in cache.");
HashMap<int, bool>::Iterator E = psc->confirmed_peers.find(p_from);
@@ -126,9 +130,9 @@ void SceneCacheInterface::process_confirm_path(int p_from, const uint8_t *p_pack
E->value = true;
}
-Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers) {
+Error SceneCacheInterface::_send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers) {
// Encode function name.
- const CharString path = String(p_path).utf8();
+ const CharString path = String(multiplayer->get_root_path().rel_path_to(p_node->get_path())).utf8();
const int path_len = encode_cstring(path.get_data(), nullptr);
// Extract MD5 from rpc methods list.
@@ -163,8 +167,9 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat
return err;
}
-bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) {
- const PathSentCache *psc = path_send_cache.getptr(p_path);
+bool SceneCacheInterface::is_cache_confirmed(Node *p_node, int p_peer) {
+ ERR_FAIL_NULL_V(p_node, false);
+ const PathSentCache *psc = path_send_cache.getptr(p_node->get_instance_id());
ERR_FAIL_NULL_V(psc, false);
HashMap<int, bool>::ConstIterator F = psc->confirmed_peers.find(p_peer);
ERR_FAIL_COND_V(!F, false); // Should never happen.
@@ -174,13 +179,13 @@ bool SceneCacheInterface::is_cache_confirmed(NodePath p_path, int p_peer) {
int SceneCacheInterface::make_object_cache(Object *p_obj) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_NULL_V(node, -1);
- NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path());
+ const ObjectID oid = node->get_instance_id();
// See if the path is cached.
- PathSentCache *psc = path_send_cache.getptr(for_path);
+ PathSentCache *psc = path_send_cache.getptr(oid);
if (!psc) {
// Path is not cached, create.
- path_send_cache[for_path] = PathSentCache();
- psc = path_send_cache.getptr(for_path);
+ path_send_cache[oid] = PathSentCache();
+ psc = path_send_cache.getptr(oid);
psc->id = last_send_cache_id++;
}
return psc->id;
@@ -189,11 +194,16 @@ int SceneCacheInterface::make_object_cache(Object *p_obj) {
bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r_id) {
Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_NULL_V(node, false);
-
- r_id = make_object_cache(p_obj);
- ERR_FAIL_COND_V(r_id < 0, false);
- NodePath for_path = multiplayer->get_root_path().rel_path_to(node->get_path());
- PathSentCache *psc = path_send_cache.getptr(for_path);
+ const ObjectID oid = node->get_instance_id();
+ // See if the path is cached.
+ PathSentCache *psc = path_send_cache.getptr(oid);
+ if (!psc) {
+ // Path is not cached, create.
+ path_send_cache[oid] = PathSentCache();
+ psc = path_send_cache.getptr(oid);
+ psc->id = last_send_cache_id++;
+ }
+ r_id = psc->id;
bool has_all_peers = true;
List<int> peers_to_add; // If one is missing, take note to add it.
@@ -225,7 +235,7 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r
}
if (peers_to_add.size()) {
- _send_confirm_path(node, for_path, psc, peers_to_add);
+ _send_confirm_path(node, psc, peers_to_add);
}
return has_all_peers;
diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h
index 7a7304fde8..e63beb5f84 100644
--- a/modules/multiplayer/scene_cache_interface.h
+++ b/modules/multiplayer/scene_cache_interface.h
@@ -58,12 +58,12 @@ private:
HashMap<int, NodeInfo> nodes;
};
- HashMap<NodePath, PathSentCache> path_send_cache;
+ HashMap<ObjectID, PathSentCache> path_send_cache;
HashMap<int, PathGetCache> path_get_cache;
int last_send_cache_id = 1;
protected:
- Error _send_confirm_path(Node *p_node, NodePath p_path, PathSentCache *psc, const List<int> &p_peers);
+ Error _send_confirm_path(Node *p_node, PathSentCache *psc, const List<int> &p_peers);
public:
void clear();
@@ -75,7 +75,7 @@ public:
bool send_object_cache(Object *p_obj, int p_target, int &p_id);
int make_object_cache(Object *p_obj);
Object *get_cached_object(int p_from, uint32_t p_cache_id);
- bool is_cache_confirmed(NodePath p_path, int p_peer);
+ bool is_cache_confirmed(Node *p_path, int p_peer);
SceneCacheInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; }
};
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index 3e3118b1cd..04de3dfb7f 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -680,12 +680,16 @@ void SceneMultiplayer::_bind_methods() {
SceneMultiplayer::SceneMultiplayer() {
relay_buffer.instantiate();
- replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this)));
- rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this)));
cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this)));
+ replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this, cache.ptr())));
+ rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this, cache.ptr(), replicator.ptr())));
set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer)));
}
SceneMultiplayer::~SceneMultiplayer() {
clear();
+ // Ensure unref in reverse order for safety (we shouldn't use those pointers in the deconstructors anyway).
+ rpc.unref();
+ replicator.unref();
+ cache.unref();
}
diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h
index a61e505689..e799abeb48 100644
--- a/modules/multiplayer/scene_multiplayer.h
+++ b/modules/multiplayer/scene_multiplayer.h
@@ -201,9 +201,6 @@ public:
void set_max_delta_packet_size(int p_size);
int get_max_delta_packet_size() const;
- Ref<SceneCacheInterface> get_path_cache() { return cache; }
- Ref<SceneReplicationInterface> get_replicator() { return replicator; }
-
SceneMultiplayer();
~SceneMultiplayer();
};
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index e350f2f68b..c95e4ff9c9 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -89,6 +89,10 @@ void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) {
}
}
+bool SceneReplicationInterface::_has_authority(const Node *p_node) {
+ return multiplayer->has_multiplayer_peer() && p_node->get_multiplayer_authority() == multiplayer->get_unique_id();
+}
+
void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) {
if (p_connected) {
peers_info[p_id] = PeerInfo();
@@ -184,7 +188,7 @@ void SceneReplicationInterface::_node_ready(const ObjectID &p_oid) {
ERR_CONTINUE(!spawner);
spawned_nodes.insert(oid);
- if (multiplayer->has_multiplayer_peer() && spawner->is_multiplayer_authority()) {
+ if (_has_authority(spawner)) {
if (tobj.net_id == 0) {
tobj.net_id = ++last_net_id;
}
@@ -248,9 +252,9 @@ Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_c
// Try to apply spawn state (before ready).
if (pending_buffer_size > 0) {
- ERR_FAIL_COND_V(!node || sync->get_replication_config().is_null(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!node || !sync->get_replication_config_ptr(), ERR_UNCONFIGURED);
int consumed = 0;
- const List<NodePath> props = sync->get_replication_config()->get_spawn_properties();
+ const List<NodePath> props = sync->get_replication_config_ptr()->get_spawn_properties();
Vector<Variant> vars;
vars.resize(props.size());
Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed);
@@ -342,7 +346,7 @@ bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer
Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) {
ERR_FAIL_NULL_V(p_sync, ERR_BUG);
- if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) {
+ if (!_has_authority(p_sync) || p_peer == multiplayer->get_unique_id()) {
return OK;
}
@@ -383,14 +387,16 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
ERR_FAIL_NULL_V(tnode, ERR_BUG);
MultiplayerSpawner *spawner = get_id_as<MultiplayerSpawner>(tnode->spawner);
Node *node = get_id_as<Node>(p_oid);
- ERR_FAIL_COND_V(!node || !spawner || !spawner->is_multiplayer_authority(), ERR_BUG);
+ ERR_FAIL_NULL_V(node, ERR_BUG);
+ ERR_FAIL_NULL_V(spawner, ERR_BUG);
+ ERR_FAIL_COND_V(!_has_authority(spawner), ERR_BUG);
ERR_FAIL_COND_V(!tracked_nodes.has(p_oid), ERR_BUG);
const HashSet<ObjectID> synchronizers = tracked_nodes[p_oid].synchronizers;
bool is_visible = true;
for (const ObjectID &sid : synchronizers) {
MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid);
ERR_CONTINUE(!sync);
- if (!sync->is_multiplayer_authority()) {
+ if (!_has_authority(sync)) {
continue;
}
// Spawn visibility is composed using OR when multiple synchronizers are present.
@@ -435,7 +441,7 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
for (int pid : to_spawn) {
ERR_CONTINUE(!peers_info.has(pid));
int path_id;
- multiplayer->get_path_cache()->send_object_cache(spawner, pid, path_id);
+ multiplayer_cache->send_object_cache(spawner, pid, path_id);
_send_raw(packet_cache.ptr(), len, pid, true);
peers_info[pid].spawn_nodes.insert(p_oid);
}
@@ -454,9 +460,9 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) {
ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
+ ERR_FAIL_COND_V(peer.is_null(), ERR_UNCONFIGURED);
peer->set_transfer_channel(0);
peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE);
return multiplayer->send_command(p_peer, p_buffer, p_size);
@@ -488,12 +494,12 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa
const HashSet<ObjectID> synchronizers = tnode->synchronizers;
for (const ObjectID &sid : synchronizers) {
MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid);
- if (!sync->is_multiplayer_authority()) {
+ if (!_has_authority(sync)) {
continue;
}
ERR_CONTINUE(!sync);
- ERR_FAIL_COND_V(sync->get_replication_config().is_null(), ERR_BUG);
- for (const NodePath &prop : sync->get_replication_config()->get_spawn_properties()) {
+ ERR_FAIL_NULL_V(sync->get_replication_config_ptr(), ERR_BUG);
+ for (const NodePath &prop : sync->get_replication_config_ptr()->get_spawn_properties()) {
state_props.push_back(prop);
}
// Ensure the synchronizer has an ID.
@@ -513,7 +519,7 @@ Error SceneReplicationInterface::_make_spawn_packet(Node *p_node, MultiplayerSpa
}
// Encode scene ID, path ID, net ID, node name.
- int path_id = multiplayer->get_path_cache()->make_object_cache(p_spawner);
+ int path_id = multiplayer_cache->make_object_cache(p_spawner);
CharString cname = p_node->get_name().operator String().utf8();
int nlen = encode_cstring(cname.get_data(), nullptr);
MAKE_ROOM(1 + 1 + 4 + 4 + 4 + 4 * sync_ids.size() + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
@@ -567,7 +573,7 @@ Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_b
ofs += 1;
uint32_t node_target = decode_uint32(&p_buffer[ofs]);
ofs += 4;
- MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_path_cache()->get_cached_object(p_from, node_target));
+ MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer_cache->get_cached_object(p_from, node_target));
ERR_FAIL_NULL_V(spawner, ERR_DOES_NOT_EXIST);
ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
@@ -678,7 +684,7 @@ bool SceneReplicationInterface::_verify_synchronizer(int p_peer, MultiplayerSync
r_net_id = p_sync->get_net_id();
if (r_net_id == 0 || (r_net_id & 0x80000000)) {
int path_id = 0;
- bool verified = multiplayer->get_path_cache()->send_object_cache(p_sync, p_peer, path_id);
+ bool verified = multiplayer_cache->send_object_cache(p_sync, p_peer, path_id);
ERR_FAIL_COND_V_MSG(path_id < 0, false, "This should never happen!");
if (r_net_id == 0) {
// First time path based ID.
@@ -693,7 +699,7 @@ bool SceneReplicationInterface::_verify_synchronizer(int p_peer, MultiplayerSync
MultiplayerSynchronizer *SceneReplicationInterface::_find_synchronizer(int p_peer, uint32_t p_net_id) {
MultiplayerSynchronizer *sync = nullptr;
if (p_net_id & 0x80000000) {
- sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_path_cache()->get_cached_object(p_peer, p_net_id & 0x7FFFFFFF));
+ sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer_cache->get_cached_object(p_peer, p_net_id & 0x7FFFFFFF));
} else if (peers_info[p_peer].recv_sync_ids.has(p_net_id)) {
const ObjectID &sid = peers_info[p_peer].recv_sync_ids[p_net_id];
sync = get_id_as<MultiplayerSynchronizer>(sid);
@@ -708,7 +714,7 @@ void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID>
int ofs = 1;
for (const ObjectID &oid : p_synchronizers) {
MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid);
- ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority());
+ ERR_CONTINUE(!sync || !sync->get_replication_config_ptr() || !_has_authority(sync));
uint32_t net_id;
if (!_verify_synchronizer(p_peer, sync, net_id)) {
continue;
@@ -803,7 +809,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p
// This is a lazy implementation, we could optimize much more here with by grouping by replication config.
for (const ObjectID &oid : p_synchronizers) {
MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(oid);
- ERR_CONTINUE(!sync || !sync->get_replication_config().is_valid() || !sync->is_multiplayer_authority());
+ ERR_CONTINUE(!sync || !sync->get_replication_config_ptr() || !_has_authority(sync));
if (!sync->update_outbound_sync_time(p_usec)) {
continue; // nothing to sync.
}
@@ -818,7 +824,7 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p
int size;
Vector<Variant> vars;
Vector<const Variant *> varp;
- const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
+ const List<NodePath> props = sync->get_replication_config_ptr()->get_sync_properties();
Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp);
ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state.");
err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size);
@@ -877,7 +883,7 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu
ofs += size;
continue;
}
- const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
+ const List<NodePath> props = sync->get_replication_config_ptr()->get_sync_properties();
Vector<Variant> vars;
vars.resize(props.size());
int consumed;
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index 267d329ca7..3b3ec6a9ef 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -37,6 +37,7 @@
#include "core/object/ref_counted.h"
class SceneMultiplayer;
+class SceneCacheInterface;
class SceneReplicationInterface : public RefCounted {
GDCLASS(SceneReplicationInterface, RefCounted);
@@ -87,6 +88,7 @@ private:
// Replicator config.
SceneMultiplayer *multiplayer = nullptr;
+ SceneCacheInterface *multiplayer_cache = nullptr;
PackedByteArray packet_cache;
int sync_mtu = 1350; // Highly dependent on underlying protocol.
int delta_mtu = 65535;
@@ -95,6 +97,7 @@ private:
void _untrack(const ObjectID &p_id);
void _node_ready(const ObjectID &p_oid);
+ bool _has_authority(const Node *p_node);
bool _verify_synchronizer(int p_peer, MultiplayerSynchronizer *p_sync, uint32_t &r_net_id);
MultiplayerSynchronizer *_find_synchronizer(int p_peer, uint32_t p_net_ida);
@@ -143,8 +146,9 @@ public:
void set_max_delta_packet_size(int p_size);
int get_max_delta_packet_size() const;
- SceneReplicationInterface(SceneMultiplayer *p_multiplayer) {
+ SceneReplicationInterface(SceneMultiplayer *p_multiplayer, SceneCacheInterface *p_cache) {
multiplayer = p_multiplayer;
+ multiplayer_cache = p_cache;
}
};
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 48e1d13f9c..1463598ddc 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -116,22 +116,6 @@ const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(con
return rpc_cache[oid];
}
-_FORCE_INLINE_ bool _can_call_mode(Node *p_node, MultiplayerAPI::RPCMode mode, int p_remote_id) {
- switch (mode) {
- case MultiplayerAPI::RPC_MODE_DISABLED: {
- return false;
- } break;
- case MultiplayerAPI::RPC_MODE_ANY_PEER: {
- return true;
- } break;
- case MultiplayerAPI::RPC_MODE_AUTHORITY: {
- return !p_node->is_multiplayer_authority() && p_remote_id == p_node->get_multiplayer_authority();
- } break;
- }
-
- return false;
-}
-
String SceneRPCInterface::get_rpc_md5(const Object *p_obj) {
const Node *node = Object::cast_to<Node>(p_obj);
ERR_FAIL_NULL_V(node, "");
@@ -167,7 +151,7 @@ Node *SceneRPCInterface::_process_get_node(int p_from, const uint8_t *p_packet,
return node;
} else {
// Use cached path.
- return Object::cast_to<Node>(multiplayer->get_path_cache()->get_cached_object(p_from, p_node_target));
+ return Object::cast_to<Node>(multiplayer_cache->get_cached_object(p_from, p_node_target));
}
}
@@ -252,7 +236,19 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i
ERR_FAIL_COND(!cache_config.configs.has(p_rpc_method_id));
const RPCConfig &config = cache_config.configs[p_rpc_method_id];
- bool can_call = _can_call_mode(p_node, config.rpc_mode, p_from);
+ bool can_call = false;
+ switch (config.rpc_mode) {
+ case MultiplayerAPI::RPC_MODE_DISABLED: {
+ can_call = false;
+ } break;
+ case MultiplayerAPI::RPC_MODE_ANY_PEER: {
+ can_call = true;
+ } break;
+ case MultiplayerAPI::RPC_MODE_AUTHORITY: {
+ can_call = p_from == p_node->get_multiplayer_authority();
+ } break;
+ }
+
ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + ".");
int argc = 0;
@@ -313,25 +309,24 @@ void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, con
// See if all peers have cached path (if so, call can be fast) while building the RPC target list.
HashSet<int> targets;
- Ref<SceneCacheInterface> cache = multiplayer->get_path_cache();
int psc_id = -1;
bool has_all_peers = true;
const ObjectID oid = p_node->get_instance_id();
if (p_to > 0) {
- ERR_FAIL_COND_MSG(!multiplayer->get_replicator()->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to));
+ ERR_FAIL_COND_MSG(!multiplayer_replicator->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to));
targets.insert(p_to);
- has_all_peers = cache->send_object_cache(p_node, p_to, psc_id);
+ has_all_peers = multiplayer_cache->send_object_cache(p_node, p_to, psc_id);
} else {
- bool restricted = !multiplayer->get_replicator()->is_rpc_visible(oid, 0);
+ bool restricted = !multiplayer_replicator->is_rpc_visible(oid, 0);
for (const int &P : multiplayer->get_connected_peers()) {
if (p_to < 0 && P == -p_to) {
continue; // Excluded peer.
}
- if (restricted && !multiplayer->get_replicator()->is_rpc_visible(oid, P)) {
+ if (restricted && !multiplayer_replicator->is_rpc_visible(oid, P)) {
continue; // Not visible to this peer.
}
targets.insert(P);
- bool has_peer = cache->send_object_cache(p_node, P, psc_id);
+ bool has_peer = multiplayer_cache->send_object_cache(p_node, P, psc_id);
has_all_peers = has_all_peers && has_peer;
}
}
@@ -443,15 +438,14 @@ void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, con
// Not all verified path, so send one by one.
// Append path at the end, since we will need it for some packets.
- NodePath from_path = multiplayer->get_root_path().rel_path_to(p_node->get_path());
- CharString pname = String(from_path).utf8();
+ CharString pname = String(multiplayer->get_root_path().rel_path_to(p_node->get_path())).utf8();
int path_len = encode_cstring(pname.get_data(), nullptr);
MAKE_ROOM(ofs + path_len);
encode_cstring(pname.get_data(), &(packet_cache.write[ofs]));
// Not all verified path, so check which needs the longer packet.
for (const int P : targets) {
- bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P);
+ bool confirmed = multiplayer_cache->is_cache_confirmed(p_node, P);
if (confirmed) {
// This one confirmed path, so use id.
encode_uint32(psc_id, &(packet_cache.write[1]));
diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h
index b40169a63b..5c9b66d5f5 100644
--- a/modules/multiplayer/scene_rpc_interface.h
+++ b/modules/multiplayer/scene_rpc_interface.h
@@ -35,6 +35,8 @@
#include "scene/main/multiplayer_api.h"
class SceneMultiplayer;
+class SceneCacheInterface;
+class SceneReplicationInterface;
class Node;
class SceneRPCInterface : public RefCounted {
@@ -77,6 +79,9 @@ private:
};
SceneMultiplayer *multiplayer = nullptr;
+ SceneCacheInterface *multiplayer_cache = nullptr;
+ SceneReplicationInterface *multiplayer_replicator = nullptr;
+
Vector<uint8_t> packet_cache;
HashMap<ObjectID, RPCConfigCache> rpc_cache;
@@ -99,7 +104,11 @@ public:
void process_rpc(int p_from, const uint8_t *p_packet, int p_packet_len);
String get_rpc_md5(const Object *p_obj);
- SceneRPCInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; }
+ SceneRPCInterface(SceneMultiplayer *p_multiplayer, SceneCacheInterface *p_cache, SceneReplicationInterface *p_replicator) {
+ multiplayer = p_multiplayer;
+ multiplayer_cache = p_cache;
+ multiplayer_replicator = p_replicator;
+ }
};
#endif // SCENE_RPC_INTERFACE_H
diff --git a/modules/openxr/util.h b/modules/openxr/util.h
index d95bc3bb8e..7488b5cf8e 100644
--- a/modules/openxr/util.h
+++ b/modules/openxr/util.h
@@ -61,13 +61,13 @@
#define GDEXTENSION_INIT_XR_FUNC(name) \
do { \
name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \
- ERR_FAIL_COND(name##_ptr == nullptr); \
+ ERR_FAIL_NULL(name##_ptr); \
} while (0)
#define GDEXTENSION_INIT_XR_FUNC_V(name) \
do { \
name##_ptr = reinterpret_cast<PFN_##name>(get_openxr_api()->get_instance_proc_addr(#name)); \
- ERR_FAIL_COND_V(name##_ptr == nullptr, false); \
+ ERR_FAIL_NULL_V(name##_ptr, false); \
} while (0)
#define EXT_PROTO_XRRESULT_FUNC1(func_name, arg1_type, arg1) \
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index b605b29f84..2e994f277d 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4084,7 +4084,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri
MutexLock lock(sd->mutex);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
+ ERR_FAIL_NULL_V(_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 0cfbf7f530..2d2fe08798 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -2939,7 +2939,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri
ERR_FAIL_COND_V(p_size <= 0, false);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
+ ERR_FAIL_NULL_V(_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
diff --git a/modules/webrtc/library_godot_webrtc.js b/modules/webrtc/library_godot_webrtc.js
index 7ece4aa872..eae4120741 100644
--- a/modules/webrtc/library_godot_webrtc.js
+++ b/modules/webrtc/library_godot_webrtc.js
@@ -90,6 +90,7 @@ const GodotRTCDataChannel = {
},
},
+ godot_js_rtc_datachannel_ready_state_get__proxy: 'sync',
godot_js_rtc_datachannel_ready_state_get__sig: 'ii',
godot_js_rtc_datachannel_ready_state_get: function (p_id) {
const ref = IDHandler.get(p_id);
@@ -110,6 +111,7 @@ const GodotRTCDataChannel = {
}
},
+ godot_js_rtc_datachannel_send__proxy: 'sync',
godot_js_rtc_datachannel_send__sig: 'iiiii',
godot_js_rtc_datachannel_send: function (p_id, p_buffer, p_length, p_raw) {
const ref = IDHandler.get(p_id);
@@ -131,16 +133,19 @@ const GodotRTCDataChannel = {
return 0;
},
+ godot_js_rtc_datachannel_is_ordered__proxy: 'sync',
godot_js_rtc_datachannel_is_ordered__sig: 'ii',
godot_js_rtc_datachannel_is_ordered: function (p_id) {
return GodotRTCDataChannel.get_prop(p_id, 'ordered', true);
},
+ godot_js_rtc_datachannel_id_get__proxy: 'sync',
godot_js_rtc_datachannel_id_get__sig: 'ii',
godot_js_rtc_datachannel_id_get: function (p_id) {
return GodotRTCDataChannel.get_prop(p_id, 'id', 65535);
},
+ godot_js_rtc_datachannel_max_packet_lifetime_get__proxy: 'sync',
godot_js_rtc_datachannel_max_packet_lifetime_get__sig: 'ii',
godot_js_rtc_datachannel_max_packet_lifetime_get: function (p_id) {
const ref = IDHandler.get(p_id);
@@ -156,21 +161,25 @@ const GodotRTCDataChannel = {
return 65535;
},
+ godot_js_rtc_datachannel_max_retransmits_get__proxy: 'sync',
godot_js_rtc_datachannel_max_retransmits_get__sig: 'ii',
godot_js_rtc_datachannel_max_retransmits_get: function (p_id) {
return GodotRTCDataChannel.get_prop(p_id, 'maxRetransmits', 65535);
},
+ godot_js_rtc_datachannel_is_negotiated__proxy: 'sync',
godot_js_rtc_datachannel_is_negotiated__sig: 'ii',
godot_js_rtc_datachannel_is_negotiated: function (p_id) {
return GodotRTCDataChannel.get_prop(p_id, 'negotiated', 65535);
},
+ godot_js_rtc_datachannel_get_buffered_amount__proxy: 'sync',
godot_js_rtc_datachannel_get_buffered_amount__sig: 'ii',
godot_js_rtc_datachannel_get_buffered_amount: function (p_id) {
return GodotRTCDataChannel.get_prop(p_id, 'bufferedAmount', 0);
},
+ godot_js_rtc_datachannel_label_get__proxy: 'sync',
godot_js_rtc_datachannel_label_get__sig: 'ii',
godot_js_rtc_datachannel_label_get: function (p_id) {
const ref = IDHandler.get(p_id);
@@ -189,12 +198,14 @@ const GodotRTCDataChannel = {
return GodotRuntime.allocString(ref.protocol);
},
+ godot_js_rtc_datachannel_destroy__proxy: 'sync',
godot_js_rtc_datachannel_destroy__sig: 'vi',
godot_js_rtc_datachannel_destroy: function (p_id) {
GodotRTCDataChannel.close(p_id);
IDHandler.remove(p_id);
},
+ godot_js_rtc_datachannel_connect__proxy: 'sync',
godot_js_rtc_datachannel_connect__sig: 'viiiiii',
godot_js_rtc_datachannel_connect: function (p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
const onopen = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
@@ -204,6 +215,7 @@ const GodotRTCDataChannel = {
GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
},
+ godot_js_rtc_datachannel_close__proxy: 'sync',
godot_js_rtc_datachannel_close__sig: 'vi',
godot_js_rtc_datachannel_close: function (p_id) {
const ref = IDHandler.get(p_id);
@@ -356,6 +368,7 @@ const GodotRTCPeerConnection = {
},
},
+ godot_js_rtc_pc_create__proxy: 'sync',
godot_js_rtc_pc_create__sig: 'iiiiiiii',
godot_js_rtc_pc_create: function (p_config, p_ref, p_on_connection_state_change, p_on_ice_gathering_state_change, p_on_signaling_state_change, p_on_ice_candidate, p_on_datachannel) {
const wrap = function (p_func) {
@@ -371,6 +384,7 @@ const GodotRTCPeerConnection = {
);
},
+ godot_js_rtc_pc_close__proxy: 'sync',
godot_js_rtc_pc_close__sig: 'vi',
godot_js_rtc_pc_close: function (p_id) {
const ref = IDHandler.get(p_id);
@@ -380,11 +394,13 @@ const GodotRTCPeerConnection = {
ref.close();
},
+ godot_js_rtc_pc_destroy__proxy: 'sync',
godot_js_rtc_pc_destroy__sig: 'vi',
godot_js_rtc_pc_destroy: function (p_id) {
GodotRTCPeerConnection.destroy(p_id);
},
+ godot_js_rtc_pc_offer_create__proxy: 'sync',
godot_js_rtc_pc_offer_create__sig: 'viiii',
godot_js_rtc_pc_offer_create: function (p_id, p_obj, p_on_session, p_on_error) {
const ref = IDHandler.get(p_id);
@@ -400,6 +416,7 @@ const GodotRTCPeerConnection = {
});
},
+ godot_js_rtc_pc_local_description_set__proxy: 'sync',
godot_js_rtc_pc_local_description_set__sig: 'viiiii',
godot_js_rtc_pc_local_description_set: function (p_id, p_type, p_sdp, p_obj, p_on_error) {
const ref = IDHandler.get(p_id);
@@ -417,6 +434,7 @@ const GodotRTCPeerConnection = {
});
},
+ godot_js_rtc_pc_remote_description_set__proxy: 'sync',
godot_js_rtc_pc_remote_description_set__sig: 'viiiiii',
godot_js_rtc_pc_remote_description_set: function (p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
const ref = IDHandler.get(p_id);
@@ -442,6 +460,7 @@ const GodotRTCPeerConnection = {
});
},
+ godot_js_rtc_pc_ice_candidate_add__proxy: 'sync',
godot_js_rtc_pc_ice_candidate_add__sig: 'viiii',
godot_js_rtc_pc_ice_candidate_add: function (p_id, p_mid_name, p_mline_idx, p_sdp) {
const ref = IDHandler.get(p_id);
@@ -458,6 +477,7 @@ const GodotRTCPeerConnection = {
},
godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
+ godot_js_rtc_pc_datachannel_create__proxy: 'sync',
godot_js_rtc_pc_datachannel_create__sig: 'iiii',
godot_js_rtc_pc_datachannel_create: function (p_id, p_label, p_config) {
try {
diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js
index ed01c69725..5e0c6fc576 100644
--- a/modules/websocket/library_godot_websocket.js
+++ b/modules/websocket/library_godot_websocket.js
@@ -144,6 +144,7 @@ const GodotWebSocket = {
},
},
+ godot_js_websocket_create__proxy: 'sync',
godot_js_websocket_create__sig: 'iiiiiiii',
godot_js_websocket_create: function (p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
const on_open = GodotRuntime.get_func(p_on_open).bind(null, p_ref);
@@ -166,6 +167,7 @@ const GodotWebSocket = {
return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close);
},
+ godot_js_websocket_send__proxy: 'sync',
godot_js_websocket_send__sig: 'iiiii',
godot_js_websocket_send: function (p_id, p_buf, p_buf_len, p_raw) {
const bytes_array = new Uint8Array(p_buf_len);
@@ -180,11 +182,13 @@ const GodotWebSocket = {
return GodotWebSocket.send(p_id, out);
},
+ godot_js_websocket_buffered_amount__proxy: 'sync',
godot_js_websocket_buffered_amount__sig: 'ii',
godot_js_websocket_buffered_amount: function (p_id) {
return GodotWebSocket.bufferedAmount(p_id);
},
+ godot_js_websocket_close__proxy: 'sync',
godot_js_websocket_close__sig: 'viii',
godot_js_websocket_close: function (p_id, p_code, p_reason) {
const code = p_code;
@@ -192,6 +196,7 @@ const GodotWebSocket = {
GodotWebSocket.close(p_id, code, reason);
},
+ godot_js_websocket_destroy__proxy: 'sync',
godot_js_websocket_destroy__sig: 'vi',
godot_js_websocket_destroy: function (p_id) {
GodotWebSocket.destroy(p_id);
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index aeaa7b9ce7..c3015ec260 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2238,6 +2238,19 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
#ifdef MODULE_MONO_ENABLED
// Android export is still a work in progress, keep a message as a warning.
err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n";
+
+ bool unsupported_arch = false;
+ Vector<ABI> enabled_abis = get_enabled_abis(p_preset);
+ for (ABI abi : enabled_abis) {
+ if (abi.arch != "arm64" && abi.arch != "x86_64") {
+ err += vformat(TTR("Android architecture %s not supported in C# projects."), abi.arch) + "\n";
+ unsupported_arch = true;
+ }
+ }
+ if (unsupported_arch) {
+ r_error = err;
+ return false;
+ }
#endif
// Look for export templates (first official, and if defined custom templates).
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index f819063a22..04d196a6e9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -84,7 +84,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
private val pluginRegistry: GodotPluginRegistry by lazy {
- GodotPluginRegistry.initializePluginRegistry(this)
+ GodotPluginRegistry.getPluginRegistry()
}
private val mSensorManager: SensorManager by lazy {
requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager
@@ -190,7 +190,7 @@ class Godot(private val context: Context) : SensorEventListener {
val activity = requireActivity()
val window = activity.window
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
- GodotPluginRegistry.initializePluginRegistry(this)
+ GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this))
if (io == null) {
io = GodotIO(activity)
}
@@ -250,11 +250,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
i++
}
- if (newArgs.isEmpty()) {
- commandLine = mutableListOf()
- } else {
- commandLine = newArgs
- }
+ commandLine = if (newArgs.isEmpty()) { mutableListOf() } else { newArgs }
if (useApkExpansion && mainPackMd5 != null && mainPackKey != null) {
// Build the full path to the app's expansion files
try {
@@ -392,6 +388,10 @@ class Godot(private val context: Context) : SensorEventListener {
// Fallback to openGl
GodotGLRenderView(host, this, xrMode, useDebugOpengl)
}
+
+ renderView?.inputHandler?.enableLongPress(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")))
+ renderView?.inputHandler?.enablePanningAndScalingGestures(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")))
+
if (host == primaryHost) {
renderView!!.startRenderer()
}
@@ -616,7 +616,7 @@ class Godot(private val context: Context) : SensorEventListener {
private fun alert(message: String, title: String, okCallback: Runnable?) {
val activity: Activity = getActivity() ?: return
- runOnUiThread(Runnable {
+ runOnUiThread {
val builder = AlertDialog.Builder(activity)
builder.setMessage(message).setTitle(title)
builder.setPositiveButton(
@@ -627,7 +627,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
val dialog = builder.create()
dialog.show()
- })
+ }
}
/**
@@ -685,9 +685,9 @@ class Godot(private val context: Context) : SensorEventListener {
return false
}
- private fun setKeepScreenOn(p_enabled: Boolean) {
+ private fun setKeepScreenOn(enabled: Boolean) {
runOnUiThread {
- if (p_enabled) {
+ if (enabled) {
getActivity()?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
getActivity()?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@@ -835,9 +835,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
}
- override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
- // Do something here if sensor accuracy changes.
- }
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
/**
* Used by the native code (java_godot_wrapper.h) to vibrate the device.
@@ -865,7 +863,7 @@ class Godot(private val context: Context) : SensorEventListener {
private fun getCommandLine(): MutableList<String> {
val original: MutableList<String> = parseCommandLine()
val hostCommandLine = primaryHost?.commandLine
- if (hostCommandLine != null && hostCommandLine.isNotEmpty()) {
+ if (!hostCommandLine.isNullOrEmpty()) {
original.addAll(hostCommandLine)
}
return original
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index 417be7cef4..a60f6e997e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -173,6 +173,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
return this
}
+ override fun getGodot(): Godot? {
+ return godotFragment?.godot
+ }
+
/**
* Used to initialize the Godot fragment instance in [onCreate].
*/
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
index 9a8b10ea3e..120e1722e5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -30,6 +30,7 @@
package org.godotengine.godot;
+import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.utils.BenchmarkUtils;
import android.app.Activity;
@@ -65,6 +66,7 @@ import com.google.android.vending.expansion.downloader.IStub;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
/**
* Base fragment for Android apps intending to use Godot for part of the app's UI.
@@ -122,6 +124,7 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
}
public ResultCallback resultCallback;
+ @Override
public Godot getGodot() {
return godot;
}
@@ -426,4 +429,13 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
}
return 0;
}
+
+ @Override
+ @CallSuper
+ public Set<GodotPlugin> getHostPlugins(Godot engine) {
+ if (parentHost != null) {
+ return parentHost.getHostPlugins(engine);
+ }
+ return Collections.emptySet();
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
index e5333085dd..1862b9fa9b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java
@@ -30,39 +30,42 @@
package org.godotengine.godot;
+import org.godotengine.godot.plugin.GodotPlugin;
+
import android.app.Activity;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
/**
* Denotate a component (e.g: Activity, Fragment) that hosts the {@link Godot} engine.
*/
public interface GodotHost {
/**
- * Provides a set of command line parameters to setup the engine.
+ * Provides a set of command line parameters to setup the {@link Godot} engine.
*/
default List<String> getCommandLine() {
return Collections.emptyList();
}
/**
- * Invoked on the render thread when the Godot setup is complete.
+ * Invoked on the render thread when setup of the {@link Godot} engine is complete.
*/
default void onGodotSetupCompleted() {}
/**
- * Invoked on the render thread when the Godot main loop has started.
+ * Invoked on the render thread when the {@link Godot} engine main loop has started.
*/
default void onGodotMainLoopStarted() {}
/**
- * Invoked on the render thread to terminate the given Godot instance.
+ * Invoked on the render thread to terminate the given {@link Godot} engine instance.
*/
default void onGodotForceQuit(Godot instance) {}
/**
- * Invoked on the render thread to terminate the Godot instance with the given id.
+ * Invoked on the render thread to terminate the {@link Godot} engine instance with the given id.
* @param godotInstanceId id of the Godot instance to terminate. See {@code onNewGodotInstanceRequested}
*
* @return true if successful, false otherwise.
@@ -90,7 +93,19 @@ public interface GodotHost {
}
/**
- * Provide access to the Activity hosting the Godot engine.
+ * Provide access to the Activity hosting the {@link Godot} engine.
*/
Activity getActivity();
+
+ /**
+ * Provide access to the hosted {@link Godot} engine.
+ */
+ Godot getGodot();
+
+ /**
+ * Returns a set of {@link GodotPlugin} to be registered with the hosted {@link Godot} engine.
+ */
+ default Set<GodotPlugin> getHostPlugins(Godot engine) {
+ return Collections.emptySet();
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt
index 68cd2c1358..795dc921c7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotService.kt
@@ -8,8 +8,10 @@ import android.util.Log
/**
* Godot service responsible for hosting the Godot engine instance.
+ *
+ * Note: Still in development, so it's made private and inaccessible until completed.
*/
-class GodotService : Service() {
+private class GodotService : Service() {
companion object {
private val TAG = GodotService::class.java.simpleName
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index c8b222254e..38c115ad7f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -95,7 +95,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
/**
* Enable multi-fingers pan & scale gestures. This is false by default.
- *
+ * <p>
* Note: This may interfere with multi-touch handling / support.
*/
public void enablePanningAndScalingGestures(boolean enable) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
index d338b72441..711bca02e7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java
@@ -42,8 +42,8 @@ import android.util.Log;
import androidx.annotation.Nullable;
import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -64,9 +64,8 @@ public final class GodotPluginRegistry {
private static GodotPluginRegistry instance;
private final ConcurrentHashMap<String, GodotPlugin> registry;
- private GodotPluginRegistry(Godot godot) {
+ private GodotPluginRegistry() {
registry = new ConcurrentHashMap<>();
- loadPlugins(godot);
}
/**
@@ -93,12 +92,14 @@ public final class GodotPluginRegistry {
* documentation.
*
* @param godot Godot instance
+ * @param runtimePlugins Set of plugins provided at runtime for registration
* @return A singleton instance of {@link GodotPluginRegistry}. This ensures that only one instance
* of each Godot Android plugins is available at runtime.
*/
- public static GodotPluginRegistry initializePluginRegistry(Godot godot) {
+ public static GodotPluginRegistry initializePluginRegistry(Godot godot, Set<GodotPlugin> runtimePlugins) {
if (instance == null) {
- instance = new GodotPluginRegistry(godot);
+ instance = new GodotPluginRegistry();
+ instance.loadPlugins(godot, runtimePlugins);
}
return instance;
@@ -108,7 +109,7 @@ public final class GodotPluginRegistry {
* Return the plugin registry if it's initialized.
* Throws a {@link IllegalStateException} exception if not.
*
- * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot)} has not been called prior to calling this method.
+ * @throws IllegalStateException if {@link GodotPluginRegistry#initializePluginRegistry(Godot, Set)} has not been called prior to calling this method.
*/
public static GodotPluginRegistry getPluginRegistry() throws IllegalStateException {
if (instance == null) {
@@ -118,7 +119,16 @@ public final class GodotPluginRegistry {
return instance;
}
- private void loadPlugins(Godot godot) {
+ private void loadPlugins(Godot godot, Set<GodotPlugin> runtimePlugins) {
+ // Register the runtime plugins
+ if (runtimePlugins != null && !runtimePlugins.isEmpty()) {
+ for (GodotPlugin plugin : runtimePlugins) {
+ Log.i(TAG, "Registering runtime plugin " + plugin.getPluginName());
+ registry.put(plugin.getPluginName(), plugin);
+ }
+ }
+
+ // Register the manifest plugins
try {
final Activity activity = godot.getActivity();
ApplicationInfo appInfo = activity
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index 8508b67f1e..2561c1c095 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -317,37 +317,37 @@ String DisplayServerIOS::get_name() const {
}
bool DisplayServerIOS::tts_is_speaking() const {
- ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return [tts isSpeaking];
}
bool DisplayServerIOS::tts_is_paused() const {
- ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_V_MSG(tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return [tts isPaused];
}
TypedArray<Dictionary> DisplayServerIOS::tts_get_voices() const {
- ERR_FAIL_COND_V_MSG(!tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_V_MSG(tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return [tts getVoices];
}
void DisplayServerIOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
- ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
[tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
}
void DisplayServerIOS::tts_pause() {
- ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
[tts pauseSpeaking];
}
void DisplayServerIOS::tts_resume() {
- ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
[tts resumeSpeaking];
}
void DisplayServerIOS::tts_stop() {
- ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
+ ERR_FAIL_NULL_MSG(tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
[tts stopSpeaking];
}
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index a8596c30a6..76546c164a 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -1927,16 +1927,19 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
}
bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
-#ifdef MODULE_MONO_ENABLED
- // Don't check for additional errors, as this particular error cannot be resolved.
- r_error += TTR("Exporting to iOS is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target iOS with C#/Mono instead.") + "\n";
- r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n";
+#if defined(MODULE_MONO_ENABLED) && !defined(MACOS_ENABLED)
+ // TODO: Remove this restriction when we don't rely on macOS tools to package up the native libraries anymore.
+ r_error += TTR("Exporting to iOS when using C#/.NET is experimental and requires macOS.") + "\n";
return false;
#else
String err;
bool valid = false;
+#if defined(MODULE_MONO_ENABLED)
+ // iOS export is still a work in progress, keep a message as a warning.
+ err += TTR("Exporting to iOS when using C#/.NET is experimental.") + "\n";
+#endif
// Look for export templates (first official, and if defined custom templates).
bool dvalid = exists_export_template("ios.zip", &err);
@@ -1963,7 +1966,7 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp
}
return valid;
-#endif // !MODULE_MONO_ENABLED
+#endif // !(MODULE_MONO_ENABLED && !MACOS_ENABLED)
}
bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 02a5208e11..81e741d7e2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -2874,7 +2874,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
*(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32();
}
- ERR_FAIL_COND(cursor_image->pixels == nullptr);
+ ERR_FAIL_NULL(cursor_image->pixels);
// Save it for a further usage
cursors[p_shape] = XcursorImageLoadCursor(x11_display, cursor_image);
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 2ca9e493b7..66c89d6cc5 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -119,6 +119,7 @@ public:
bool is_popup = false;
bool mpass = false;
bool focused = false;
+ bool is_visible = true;
Rect2i parent_safe_rect;
};
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 0989e47b19..290f0d487f 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -118,7 +118,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
WindowData wd;
wd.window_delegate = [[GodotWindowDelegate alloc] init];
- ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate");
+ ERR_FAIL_NULL_V_MSG(wd.window_delegate, INVALID_WINDOW_ID, "Can't create a window delegate");
[wd.window_delegate setWindowID:window_id_counter];
int rq_screen = get_screen_from_rect(p_rect);
@@ -144,11 +144,11 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
backing:NSBackingStoreBuffered
defer:NO];
- ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window");
+ ERR_FAIL_NULL_V_MSG(wd.window_object, INVALID_WINDOW_ID, "Can't create a window");
[wd.window_object setWindowID:window_id_counter];
wd.window_view = [[GodotContentView alloc] init];
- ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view");
+ ERR_FAIL_NULL_V_MSG(wd.window_view, INVALID_WINDOW_ID, "Can't create a window view");
[wd.window_view setWindowID:window_id_counter];
[wd.window_view setWantsLayer:TRUE];
@@ -567,7 +567,7 @@ NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref<Image> &p_image) const {
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:int(p_image->get_width()) * 4
bitsPerPixel:32];
- ERR_FAIL_COND_V(imgrep == nil, nil);
+ ERR_FAIL_NULL_V(imgrep, nil);
uint8_t *pixels = [imgrep bitmapData];
int len = p_image->get_width() * p_image->get_height();
@@ -583,7 +583,7 @@ NSImage *DisplayServerMacOS::_convert_to_nsimg(Ref<Image> &p_image) const {
}
NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(p_image->get_width(), p_image->get_height())];
- ERR_FAIL_COND_V(nsimg == nil, nil);
+ ERR_FAIL_NULL_V(nsimg, nil);
[nsimg addRepresentation:imgrep];
return nsimg;
}
@@ -1574,7 +1574,7 @@ void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_me
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- ERR_FAIL_COND(!obj);
+ ERR_FAIL_NULL(obj);
obj->hover_callback = p_callback;
}
}
@@ -3562,14 +3562,14 @@ bool DisplayServerMacOS::window_is_focused(WindowID p_window) const {
}
bool DisplayServerMacOS::window_can_draw(WindowID p_window) const {
- return (window_get_mode(p_window) != WINDOW_MODE_MINIMIZED) && [windows[p_window].window_object isOnActiveSpace];
+ return windows[p_window].is_visible;
}
bool DisplayServerMacOS::can_any_window_draw() const {
_THREAD_SAFE_METHOD_
for (const KeyValue<WindowID, WindowData> &E : windows) {
- if ((window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) && [E.value.window_object isOnActiveSpace]) {
+ if (E.value.is_visible) {
return true;
}
}
@@ -3858,7 +3858,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
bytesPerRow:int(texture_size.width) * 4
bitsPerPixel:32];
- ERR_FAIL_COND(imgrep == nil);
+ ERR_FAIL_NULL(imgrep);
uint8_t *pixels = [imgrep bitmapData];
int len = int(texture_size.width * texture_size.height);
@@ -4124,7 +4124,7 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:img->get_width() * 4
bitsPerPixel:32];
- ERR_FAIL_COND(imgrep == nil);
+ ERR_FAIL_NULL(imgrep);
uint8_t *pixels = [imgrep bitmapData];
int len = img->get_width() * img->get_height();
@@ -4140,7 +4140,7 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
}
NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
- ERR_FAIL_COND(nsimg == nil);
+ ERR_FAIL_NULL(nsimg);
[nsimg addRepresentation:imgrep];
[NSApp setApplicationIconImage:nsimg];
diff --git a/platform/macos/gl_manager_macos_legacy.mm b/platform/macos/gl_manager_macos_legacy.mm
index 3e5a96bffd..701de6df78 100644
--- a/platform/macos/gl_manager_macos_legacy.mm
+++ b/platform/macos/gl_manager_macos_legacy.mm
@@ -50,10 +50,10 @@ Error GLManagerLegacy_MacOS::create_context(GLWindow &win) {
};
NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
- ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE);
+ ERR_FAIL_NULL_V(pixel_format, ERR_CANT_CREATE);
win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context];
- ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE);
+ ERR_FAIL_NULL_V(win.context, ERR_CANT_CREATE);
if (shared_context == nullptr) {
shared_context = win.context;
}
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 1756f2d676..e359630d1d 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -356,4 +356,13 @@
}
}
+- (void)windowDidChangeOcclusionState:(NSNotification *)notification {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ if (!ds || !ds->has_window(window_id)) {
+ return;
+ }
+ DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
+ wd.is_visible = ([wd.window_object occlusionState] & NSWindowOcclusionStateVisible) && [wd.window_object isVisible];
+}
+
@end
diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm
index 1b8ca0134d..29dff683d5 100644
--- a/platform/macos/os_macos.mm
+++ b/platform/macos/os_macos.mm
@@ -819,7 +819,7 @@ OS_MacOS::OS_MacOS() {
[NSApp finishLaunching];
id delegate = [[GodotApplicationDelegate alloc] init];
- ERR_FAIL_COND(!delegate);
+ ERR_FAIL_NULL(delegate);
[NSApp setDelegate:delegate];
pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 043ddca65d..d306869889 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -41,6 +41,11 @@ def get_opts():
"dlink_enabled", "Enable WebAssembly dynamic linking (GDExtension support). Produces bigger binaries", False
),
BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False),
+ BoolVariable(
+ "proxy_to_pthread",
+ "Use Emscripten PROXY_TO_PTHREAD option to run the main application code to a separate thread",
+ True,
+ ),
]
@@ -211,6 +216,10 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-Wl,-u,scalbnf"])
if env["dlink_enabled"]:
+ if env["proxy_to_pthread"]:
+ print("GDExtension support requires proxy_to_pthread=no, disabling")
+ env["proxy_to_pthread"] = False
+
if cc_semver < (3, 1, 14):
print("GDExtension support requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver)
sys.exit(255)
@@ -221,6 +230,16 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-fvisibility=hidden"])
env.extra_suffix = ".dlink" + env.extra_suffix
+ # Run the main application in a web worker
+ if env["proxy_to_pthread"]:
+ env.Append(LINKFLAGS=["-s", "PROXY_TO_PTHREAD=1"])
+ env.Append(CPPDEFINES=["PROXY_TO_PTHREAD_ENABLED"])
+ env.Append(LINKFLAGS=["-s", "EXPORTED_RUNTIME_METHODS=['_emscripten_proxy_main']"])
+ # https://github.com/emscripten-core/emscripten/issues/18034#issuecomment-1277561925
+ env.Append(LINKFLAGS=["-s", "TEXTDECODER=0"])
+ # BigInt support to pass object pointers between contexts
+ env.Append(LINKFLAGS=["-s", "WASM_BIGINT"])
+
# Reduce code size by generating less support code (e.g. skip NodeJS support).
env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"])
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index dcc4ac4bf7..022e044185 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -35,6 +35,7 @@
#include "os_web.h"
#include "core/config/project_settings.h"
+#include "core/object/callable_method_pointer.h"
#include "scene/resources/atlas_texture.h"
#include "servers/rendering/dummy/rasterizer_dummy.h"
@@ -59,13 +60,26 @@ DisplayServerWeb *DisplayServerWeb::get_singleton() {
bool DisplayServerWeb::check_size_force_redraw() {
bool size_changed = godot_js_display_size_update() != 0;
if (size_changed && !rect_changed_callback.is_null()) {
- Variant size = Rect2i(Point2i(), window_get_size()); // TODO use window_get_position if implemented.
+ Size2i window_size = window_get_size();
+ Variant size = Rect2i(Point2i(), window_size); // TODO use window_get_position if implemented.
rect_changed_callback.call(size);
+ emscripten_set_canvas_element_size(canvas_id, window_size.x, window_size.y);
}
return size_changed;
}
void DisplayServerWeb::fullscreen_change_callback(int p_fullscreen) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_fullscreen_change_callback).bind(p_fullscreen).call_deferred();
+ return;
+ }
+#endif
+
+ _fullscreen_change_callback(p_fullscreen);
+}
+
+void DisplayServerWeb::_fullscreen_change_callback(int p_fullscreen) {
DisplayServerWeb *display = get_singleton();
if (p_fullscreen) {
display->window_mode = WINDOW_MODE_FULLSCREEN;
@@ -75,7 +89,23 @@ void DisplayServerWeb::fullscreen_change_callback(int p_fullscreen) {
}
// Drag and drop callback.
-void DisplayServerWeb::drop_files_js_callback(char **p_filev, int p_filec) {
+void DisplayServerWeb::drop_files_js_callback(const char **p_filev, int p_filec) {
+ Vector<String> files;
+ for (int i = 0; i < p_filec; i++) {
+ files.push_back(String::utf8(p_filev[i]));
+ }
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_drop_files_js_callback).bind(files).call_deferred();
+ return;
+ }
+#endif
+
+ _drop_files_js_callback(files);
+}
+
+void DisplayServerWeb::_drop_files_js_callback(const Vector<String> &p_files) {
DisplayServerWeb *ds = get_singleton();
if (!ds) {
ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active");
@@ -83,15 +113,22 @@ void DisplayServerWeb::drop_files_js_callback(char **p_filev, int p_filec) {
if (ds->drop_files_callback.is_null()) {
return;
}
- Vector<String> files;
- for (int i = 0; i < p_filec; i++) {
- files.push_back(String::utf8(p_filev[i]));
- }
- ds->drop_files_callback.call(files);
+ ds->drop_files_callback.call(p_files);
}
// Web quit request callback.
void DisplayServerWeb::request_quit_callback() {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_request_quit_callback).call_deferred();
+ return;
+ }
+#endif
+
+ _request_quit_callback();
+}
+
+void DisplayServerWeb::_request_quit_callback() {
DisplayServerWeb *ds = get_singleton();
if (ds && !ds->window_event_callback.is_null()) {
Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
@@ -119,17 +156,32 @@ void DisplayServerWeb::dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod,
void DisplayServerWeb::key_callback(int p_pressed, int p_repeat, int p_modifiers) {
DisplayServerWeb *ds = get_singleton();
JSKeyEvent &key_event = ds->key_event;
+
+ const String code = String::utf8(key_event.code);
+ const String key = String::utf8(key_event.key);
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_key_callback).bind(code, key, p_pressed, p_repeat, p_modifiers).call_deferred();
+ return;
+ }
+#endif
+
+ _key_callback(code, key, p_pressed, p_repeat, p_modifiers);
+}
+
+void DisplayServerWeb::_key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers) {
// Resume audio context after input in case autoplay was denied.
OS_Web::get_singleton()->resume_audio();
char32_t c = 0x00;
- String unicode = String::utf8(key_event.key);
+ String unicode = p_key_event_key;
if (unicode.length() == 1) {
c = unicode[0];
}
- Key keycode = dom_code2godot_scancode(key_event.code, key_event.key, false);
- Key scancode = dom_code2godot_scancode(key_event.code, key_event.key, true);
+ Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
+ Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
Ref<InputEventKey> ev;
ev.instantiate();
@@ -150,6 +202,17 @@ void DisplayServerWeb::key_callback(int p_pressed, int p_repeat, int p_modifiers
// Mouse
int DisplayServerWeb::mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_mouse_button_callback).bind(p_pressed, p_button, p_x, p_y, p_modifiers).call_deferred();
+ return true;
+ }
+#endif
+
+ return _mouse_button_callback(p_pressed, p_button, p_x, p_y, p_modifiers);
+}
+
+int DisplayServerWeb::_mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) {
DisplayServerWeb *ds = get_singleton();
Point2 pos(p_x, p_y);
@@ -226,6 +289,17 @@ int DisplayServerWeb::mouse_button_callback(int p_pressed, int p_button, double
}
void DisplayServerWeb::mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_mouse_move_callback).bind(p_x, p_y, p_rel_x, p_rel_y, p_modifiers).call_deferred();
+ return;
+ }
+#endif
+
+ _mouse_move_callback(p_x, p_y, p_rel_x, p_rel_y, p_modifiers);
+}
+
+void DisplayServerWeb::_mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) {
BitField<MouseButtonMask> input_mask = Input::get_singleton()->get_mouse_button_mask();
// For motion outside the canvas, only read mouse movement if dragging
// started inside the canvas; imitating desktop app behavior.
@@ -301,9 +375,25 @@ bool DisplayServerWeb::tts_is_paused() const {
}
void DisplayServerWeb::update_voices_callback(int p_size, const char **p_voice) {
- get_singleton()->voices.clear();
+ Vector<String> voices;
for (int i = 0; i < p_size; i++) {
- Vector<String> tokens = String::utf8(p_voice[i]).split(";", true, 2);
+ voices.append(String::utf8(p_voice[i]));
+ }
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_update_voices_callback).bind(voices).call_deferred();
+ return;
+ }
+#endif
+
+ _update_voices_callback(voices);
+}
+
+void DisplayServerWeb::_update_voices_callback(const Vector<String> &p_voices) {
+ get_singleton()->voices.clear();
+ for (int i = 0; i < p_voices.size(); i++) {
+ Vector<String> tokens = p_voices[i].split(";", true, 2);
if (tokens.size() == 2) {
Dictionary voice_d;
voice_d["name"] = tokens[1];
@@ -334,7 +424,7 @@ void DisplayServerWeb::tts_speak(const String &p_text, const String &p_voice, in
CharString string = p_text.utf8();
utterance_ids[p_utterance_id] = string;
- godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerWeb::_js_utterance_callback);
+ godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerWeb::js_utterance_callback);
}
void DisplayServerWeb::tts_pause() {
@@ -356,6 +446,17 @@ void DisplayServerWeb::tts_stop() {
godot_js_tts_stop();
}
+void DisplayServerWeb::js_utterance_callback(int p_event, int p_id, int p_pos) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_js_utterance_callback).bind(p_event, p_id, p_pos).call_deferred();
+ return;
+ }
+#endif
+
+ _js_utterance_callback(p_event, p_id, p_pos);
+}
+
void DisplayServerWeb::_js_utterance_callback(int p_event, int p_id, int p_pos) {
DisplayServerWeb *ds = (DisplayServerWeb *)DisplayServer::get_singleton();
if (ds->utterance_ids.has(p_id)) {
@@ -507,6 +608,17 @@ Point2i DisplayServerWeb::mouse_get_position() const {
// Wheel
int DisplayServerWeb::mouse_wheel_callback(double p_delta_x, double p_delta_y) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_mouse_wheel_callback).bind(p_delta_x, p_delta_y).call_deferred();
+ return true;
+ }
+#endif
+
+ return _mouse_wheel_callback(p_delta_x, p_delta_y);
+}
+
+int DisplayServerWeb::_mouse_wheel_callback(double p_delta_x, double p_delta_y) {
if (!godot_js_display_canvas_is_focused()) {
if (get_singleton()->cursor_inside_canvas) {
godot_js_display_canvas_focus();
@@ -559,6 +671,17 @@ int DisplayServerWeb::mouse_wheel_callback(double p_delta_x, double p_delta_y) {
// Touch
void DisplayServerWeb::touch_callback(int p_type, int p_count) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_touch_callback).bind(p_type, p_count).call_deferred();
+ return;
+ }
+#endif
+
+ _touch_callback(p_type, p_count);
+}
+
+void DisplayServerWeb::_touch_callback(int p_type, int p_count) {
DisplayServerWeb *ds = get_singleton();
const JSTouchEvent &touch_event = ds->touch_event;
@@ -603,13 +726,25 @@ bool DisplayServerWeb::is_touchscreen_available() const {
// Virtual Keyboard
void DisplayServerWeb::vk_input_text_callback(const char *p_text, int p_cursor) {
+ String text = p_text;
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_vk_input_text_callback).bind(text, p_cursor).call_deferred();
+ return;
+ }
+#endif
+
+ _vk_input_text_callback(text, p_cursor);
+}
+
+void DisplayServerWeb::_vk_input_text_callback(const String &p_text, int p_cursor) {
DisplayServerWeb *ds = DisplayServerWeb::get_singleton();
if (!ds || ds->input_text_callback.is_null()) {
return;
}
// Call input_text
- Variant event = String::utf8(p_text);
- ds->input_text_callback.call(event);
+ ds->input_text_callback.call(p_text);
// Insert key right to reach position.
Input *input = Input::get_singleton();
Ref<InputEventKey> k;
@@ -636,14 +771,39 @@ void DisplayServerWeb::virtual_keyboard_hide() {
}
void DisplayServerWeb::window_blur_callback() {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_window_blur_callback).call_deferred();
+ return;
+ }
+#endif
+
+ _window_blur_callback();
+}
+
+void DisplayServerWeb::_window_blur_callback() {
Input::get_singleton()->release_pressed_events();
}
// Gamepad
void DisplayServerWeb::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) {
+ String id = p_id;
+ String guid = p_guid;
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_gamepad_callback).bind(p_index, p_connected, id, guid).call_deferred();
+ return;
+ }
+#endif
+
+ _gamepad_callback(p_index, p_connected, id, guid);
+}
+
+void DisplayServerWeb::_gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid) {
Input *input = Input::get_singleton();
if (p_connected) {
- input->joy_connection_changed(p_index, true, String::utf8(p_id), String::utf8(p_guid));
+ input->joy_connection_changed(p_index, true, p_id, p_guid);
} else {
input->joy_connection_changed(p_index, false, "");
}
@@ -687,7 +847,20 @@ Vector<String> DisplayServerWeb::get_rendering_drivers_func() {
// Clipboard
void DisplayServerWeb::update_clipboard_callback(const char *p_text) {
- get_singleton()->clipboard = String::utf8(p_text);
+ String text = p_text;
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_update_clipboard_callback).bind(text).call_deferred();
+ return;
+ }
+#endif
+
+ _update_clipboard_callback(text);
+}
+
+void DisplayServerWeb::_update_clipboard_callback(const String &p_text) {
+ get_singleton()->clipboard = p_text;
}
void DisplayServerWeb::clipboard_set(const String &p_text) {
@@ -702,6 +875,17 @@ String DisplayServerWeb::clipboard_get() const {
}
void DisplayServerWeb::send_window_event_callback(int p_notification) {
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(DisplayServerWeb::_send_window_event_callback).bind(p_notification).call_deferred();
+ return;
+ }
+#endif
+
+ _send_window_event_callback(p_notification);
+}
+
+void DisplayServerWeb::_send_window_event_callback(int p_notification) {
DisplayServerWeb *ds = get_singleton();
if (!ds) {
return;
@@ -816,19 +1000,19 @@ DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode
godot_js_input_mouse_wheel_cb(&DisplayServerWeb::mouse_wheel_callback);
godot_js_input_touch_cb(&DisplayServerWeb::touch_callback, touch_event.identifier, touch_event.coords);
godot_js_input_key_cb(&DisplayServerWeb::key_callback, key_event.code, key_event.key);
- godot_js_input_paste_cb(update_clipboard_callback);
- godot_js_input_drop_files_cb(drop_files_js_callback);
+ godot_js_input_paste_cb(&DisplayServerWeb::update_clipboard_callback);
+ godot_js_input_drop_files_cb(&DisplayServerWeb::drop_files_js_callback);
godot_js_input_gamepad_cb(&DisplayServerWeb::gamepad_callback);
// JS Display interface (js/libs/library_godot_display.js)
godot_js_display_fullscreen_cb(&DisplayServerWeb::fullscreen_change_callback);
- godot_js_display_window_blur_cb(&window_blur_callback);
- godot_js_display_notification_cb(&send_window_event_callback,
+ godot_js_display_window_blur_cb(&DisplayServerWeb::window_blur_callback);
+ godot_js_display_notification_cb(&DisplayServerWeb::send_window_event_callback,
WINDOW_EVENT_MOUSE_ENTER,
WINDOW_EVENT_MOUSE_EXIT,
WINDOW_EVENT_FOCUS_IN,
WINDOW_EVENT_FOCUS_OUT);
- godot_js_display_vk_cb(&vk_input_text_callback);
+ godot_js_display_vk_cb(&DisplayServerWeb::vk_input_text_callback);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event);
}
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 1653deff80..51c6ab3c0a 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -91,28 +91,43 @@ private:
// events
WASM_EXPORT static void fullscreen_change_callback(int p_fullscreen);
+ static void _fullscreen_change_callback(int p_fullscreen);
WASM_EXPORT static int mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers);
+ static int _mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers);
WASM_EXPORT static void mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers);
+ static void _mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers);
WASM_EXPORT static int mouse_wheel_callback(double p_delta_x, double p_delta_y);
+ static int _mouse_wheel_callback(double p_delta_x, double p_delta_y);
WASM_EXPORT static void touch_callback(int p_type, int p_count);
+ static void _touch_callback(int p_type, int p_count);
WASM_EXPORT static void key_callback(int p_pressed, int p_repeat, int p_modifiers);
+ static void _key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers);
WASM_EXPORT static void vk_input_text_callback(const char *p_text, int p_cursor);
+ static void _vk_input_text_callback(const String &p_text, int p_cursor);
WASM_EXPORT static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid);
+ static void _gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid);
+ WASM_EXPORT static void js_utterance_callback(int p_event, int p_id, int p_pos);
+ static void _js_utterance_callback(int p_event, int p_id, int p_pos);
+ WASM_EXPORT static void request_quit_callback();
+ static void _request_quit_callback();
+ WASM_EXPORT static void window_blur_callback();
+ static void _window_blur_callback();
+ WASM_EXPORT static void update_voices_callback(int p_size, const char **p_voice);
+ static void _update_voices_callback(const Vector<String> &p_voices);
+ WASM_EXPORT static void update_clipboard_callback(const char *p_text);
+ static void _update_clipboard_callback(const String &p_text);
+ WASM_EXPORT static void send_window_event_callback(int p_notification);
+ static void _send_window_event_callback(int p_notification);
+ WASM_EXPORT static void drop_files_js_callback(const char **p_filev, int p_filec);
+ static void _drop_files_js_callback(const Vector<String> &p_files);
+
void process_joypads();
- WASM_EXPORT static void _js_utterance_callback(int p_event, int p_id, int p_pos);
static Vector<String> get_rendering_drivers_func();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
static void _dispatch_input_event(const Ref<InputEvent> &p_event);
- WASM_EXPORT static void request_quit_callback();
- WASM_EXPORT static void window_blur_callback();
- WASM_EXPORT static void update_voices_callback(int p_size, const char **p_voice);
- WASM_EXPORT static void update_clipboard_callback(const char *p_text);
- WASM_EXPORT static void send_window_event_callback(int p_notification);
- WASM_EXPORT static void drop_files_js_callback(char **p_filev, int p_filec);
-
protected:
int get_current_video_driver() const;
diff --git a/platform/web/godot_js.h b/platform/web/godot_js.h
index f172148bf9..031e67e486 100644
--- a/platform/web/godot_js.h
+++ b/platform/web/godot_js.h
@@ -38,6 +38,7 @@ extern "C" {
#endif
#include <stddef.h>
+#include <stdint.h>
// Config
extern void godot_js_config_locale_get(char *p_ptr, int p_ptr_max);
@@ -69,7 +70,7 @@ extern int godot_js_input_gamepad_sample();
extern int godot_js_input_gamepad_sample_count();
extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard);
extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text));
-extern void godot_js_input_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec));
+extern void godot_js_input_drop_files_cb(void (*p_callback)(const char **p_filev, int p_filec));
// TTS
extern int godot_js_tts_is_speaking();
diff --git a/platform/web/godot_webgl2.h b/platform/web/godot_webgl2.h
index d2f46e125e..3ade9e4239 100644
--- a/platform/web/godot_webgl2.h
+++ b/platform/web/godot_webgl2.h
@@ -44,6 +44,7 @@ extern "C" {
#endif
void godot_webgl2_glFramebufferTextureMultiviewOVR(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews);
+void godot_webgl2_glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data);
#define glFramebufferTextureMultiviewOVR godot_webgl2_glFramebufferTextureMultiviewOVR
diff --git a/platform/web/javascript_bridge_singleton.cpp b/platform/web/javascript_bridge_singleton.cpp
index 1bb72456e8..d72ad8331b 100644
--- a/platform/web/javascript_bridge_singleton.cpp
+++ b/platform/web/javascript_bridge_singleton.cpp
@@ -72,7 +72,8 @@ private:
WASM_EXPORT static void _free_lock(void **p_lock, int p_type);
WASM_EXPORT static Variant _js2variant(int p_type, godot_js_wrapper_ex *p_val);
WASM_EXPORT static void *_alloc_variants(int p_size);
- WASM_EXPORT static void _callback(void *p_ref, int p_arg_id, int p_argc);
+ WASM_EXPORT static void callback(void *p_ref, int p_arg_id, int p_argc);
+ static void _callback(const JavaScriptObjectImpl *obj, Variant arg);
protected:
bool _set(const StringName &p_name, const Variant &p_value) override;
@@ -163,7 +164,7 @@ void JavaScriptObjectImpl::_get_property_list(List<PropertyInfo> *p_list) const
}
void JavaScriptObjectImpl::_free_lock(void **p_lock, int p_type) {
- ERR_FAIL_COND_MSG(*p_lock == nullptr, "No lock to free!");
+ ERR_FAIL_NULL_MSG(*p_lock, "No lock to free!");
const Variant::Type type = (Variant::Type)p_type;
switch (type) {
case Variant::STRING: {
@@ -245,9 +246,10 @@ Variant JavaScriptObjectImpl::callp(const StringName &p_method, const Variant **
return _js2variant(type, &exchange);
}
-void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) {
+void JavaScriptObjectImpl::callback(void *p_ref, int p_args_id, int p_argc) {
const JavaScriptObjectImpl *obj = (JavaScriptObjectImpl *)p_ref;
ERR_FAIL_COND_MSG(obj->_callable.is_null(), "JavaScript callback failed.");
+
Vector<const Variant *> argp;
Array arg_arr;
for (int i = 0; i < p_argc; i++) {
@@ -256,7 +258,20 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) {
int type = godot_js_wrapper_object_getvar(p_args_id, Variant::INT, &exchange);
arg_arr.push_back(_js2variant(type, &exchange));
}
- obj->_callable.call(arg_arr);
+ Variant arg = arg_arr;
+
+#ifdef PROXY_TO_PTHREAD_ENABLED
+ if (!Thread::is_main_thread()) {
+ callable_mp_static(JavaScriptObjectImpl::_callback).bind(obj, arg).call_deferred();
+ return;
+ }
+#endif
+
+ _callback(obj, arg);
+}
+
+void JavaScriptObjectImpl::_callback(const JavaScriptObjectImpl *obj, Variant arg) {
+ obj->_callable.call(arg);
// Set return value
godot_js_wrapper_ex exchange;
@@ -273,7 +288,7 @@ void JavaScriptObjectImpl::_callback(void *p_ref, int p_args_id, int p_argc) {
Ref<JavaScriptObject> JavaScriptBridge::create_callback(const Callable &p_callable) {
Ref<JavaScriptObjectImpl> out = memnew(JavaScriptObjectImpl);
out->_callable = p_callable;
- out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::_callback);
+ out->_js_id = godot_js_wrapper_create_cb(out.ptr(), JavaScriptObjectImpl::callback);
return out;
}
diff --git a/platform/web/js/engine/engine.js b/platform/web/js/engine/engine.js
index fb80bd55e1..3d6720a2fc 100644
--- a/platform/web/js/engine/engine.js
+++ b/platform/web/js/engine/engine.js
@@ -164,6 +164,10 @@ const Engine = (function () {
// Preload GDExtension libraries.
const libs = [];
+ if (me.config.gdextensionLibs.length > 0 && !me.rtenv['loadDynamicLibrary']) {
+ return Promise.reject(new Error('GDExtension libraries are not supported by this engine version. '
+ + 'Enable "Extensions Support" for your export preset and/or build your custom template with "dlink_enabled=yes".'));
+ }
me.config.gdextensionLibs.forEach(function (lib) {
libs.push(me.rtenv['loadDynamicLibrary'](lib, { 'loadAsync': true }));
});
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index cc86c81096..b54c5cac85 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -159,16 +159,19 @@ const GodotAudio = {
return 1;
},
+ godot_audio_has_worklet__proxy: 'sync',
godot_audio_has_worklet__sig: 'i',
godot_audio_has_worklet: function () {
return (GodotAudio.ctx && GodotAudio.ctx.audioWorklet) ? 1 : 0;
},
+ godot_audio_has_script_processor__proxy: 'sync',
godot_audio_has_script_processor__sig: 'i',
godot_audio_has_script_processor: function () {
return (GodotAudio.ctx && GodotAudio.ctx.createScriptProcessor) ? 1 : 0;
},
+ godot_audio_init__proxy: 'sync',
godot_audio_init__sig: 'iiiii',
godot_audio_init: function (p_mix_rate, p_latency, p_state_change, p_latency_update) {
const statechange = GodotRuntime.get_func(p_state_change);
@@ -179,6 +182,7 @@ const GodotAudio = {
return channels;
},
+ godot_audio_resume__proxy: 'sync',
godot_audio_resume__sig: 'v',
godot_audio_resume: function () {
if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') {
@@ -358,6 +362,7 @@ const GodotAudioWorklet = {
},
},
+ godot_audio_worklet_create__proxy: 'sync',
godot_audio_worklet_create__sig: 'ii',
godot_audio_worklet_create: function (channels) {
try {
@@ -369,6 +374,7 @@ const GodotAudioWorklet = {
return 0;
},
+ godot_audio_worklet_start__proxy: 'sync',
godot_audio_worklet_start__sig: 'viiiii',
godot_audio_worklet_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) {
const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size);
@@ -377,6 +383,7 @@ const GodotAudioWorklet = {
GodotAudioWorklet.start(in_buffer, out_buffer, state);
},
+ godot_audio_worklet_start_no_threads__proxy: 'sync',
godot_audio_worklet_start_no_threads__sig: 'viiiiii',
godot_audio_worklet_start_no_threads: function (p_out_buf, p_out_size, p_out_callback, p_in_buf, p_in_size, p_in_callback) {
const out_callback = GodotRuntime.get_func(p_out_callback);
@@ -465,6 +472,7 @@ const GodotAudioScript = {
},
},
+ godot_audio_script_create__proxy: 'sync',
godot_audio_script_create__sig: 'iii',
godot_audio_script_create: function (buffer_length, channel_count) {
const buf_len = GodotRuntime.getHeapValue(buffer_length, 'i32');
@@ -478,6 +486,7 @@ const GodotAudioScript = {
return 0;
},
+ godot_audio_script_start__proxy: 'sync',
godot_audio_script_start__sig: 'viiiii',
godot_audio_script_start: function (p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) {
const onprocess = GodotRuntime.get_func(p_cb);
diff --git a/platform/web/js/libs/library_godot_display.js b/platform/web/js/libs/library_godot_display.js
index c60e6899f2..99fc429d8f 100644
--- a/platform/web/js/libs/library_godot_display.js
+++ b/platform/web/js/libs/library_godot_display.js
@@ -345,6 +345,7 @@ const GodotDisplay = {
},
},
+ godot_js_display_is_swap_ok_cancel__proxy: 'sync',
godot_js_display_is_swap_ok_cancel__sig: 'i',
godot_js_display_is_swap_ok_cancel: function () {
const win = (['Windows', 'Win64', 'Win32', 'WinCE']);
@@ -355,16 +356,19 @@ const GodotDisplay = {
return 0;
},
+ godot_js_tts_is_speaking__proxy: 'sync',
godot_js_tts_is_speaking__sig: 'i',
godot_js_tts_is_speaking: function () {
return window.speechSynthesis.speaking;
},
+ godot_js_tts_is_paused__proxy: 'sync',
godot_js_tts_is_paused__sig: 'i',
godot_js_tts_is_paused: function () {
return window.speechSynthesis.paused;
},
+ godot_js_tts_get_voices__proxy: 'sync',
godot_js_tts_get_voices__sig: 'vi',
godot_js_tts_get_voices: function (p_callback) {
const func = GodotRuntime.get_func(p_callback);
@@ -382,6 +386,7 @@ const GodotDisplay = {
}
},
+ godot_js_tts_speak__proxy: 'sync',
godot_js_tts_speak__sig: 'viiiffii',
godot_js_tts_speak: function (p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_callback) {
const func = GodotRuntime.get_func(p_callback);
@@ -424,53 +429,63 @@ const GodotDisplay = {
window.speechSynthesis.speak(utterance);
},
+ godot_js_tts_pause__proxy: 'sync',
godot_js_tts_pause__sig: 'v',
godot_js_tts_pause: function () {
window.speechSynthesis.pause();
},
+ godot_js_tts_resume__proxy: 'sync',
godot_js_tts_resume__sig: 'v',
godot_js_tts_resume: function () {
window.speechSynthesis.resume();
},
+ godot_js_tts_stop__proxy: 'sync',
godot_js_tts_stop__sig: 'v',
godot_js_tts_stop: function () {
window.speechSynthesis.cancel();
window.speechSynthesis.resume();
},
+ godot_js_display_alert__proxy: 'sync',
godot_js_display_alert__sig: 'vi',
godot_js_display_alert: function (p_text) {
window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert
},
+ godot_js_display_screen_dpi_get__proxy: 'sync',
godot_js_display_screen_dpi_get__sig: 'i',
godot_js_display_screen_dpi_get: function () {
return GodotDisplay.getDPI();
},
+ godot_js_display_pixel_ratio_get__proxy: 'sync',
godot_js_display_pixel_ratio_get__sig: 'f',
godot_js_display_pixel_ratio_get: function () {
return GodotDisplayScreen.getPixelRatio();
},
+ godot_js_display_fullscreen_request__proxy: 'sync',
godot_js_display_fullscreen_request__sig: 'i',
godot_js_display_fullscreen_request: function () {
return GodotDisplayScreen.requestFullscreen();
},
+ godot_js_display_fullscreen_exit__proxy: 'sync',
godot_js_display_fullscreen_exit__sig: 'i',
godot_js_display_fullscreen_exit: function () {
return GodotDisplayScreen.exitFullscreen();
},
+ godot_js_display_desired_size_set__proxy: 'sync',
godot_js_display_desired_size_set__sig: 'vii',
godot_js_display_desired_size_set: function (width, height) {
GodotDisplayScreen.desired_size = [width, height];
GodotDisplayScreen.updateSize();
},
+ godot_js_display_size_update__proxy: 'sync',
godot_js_display_size_update__sig: 'i',
godot_js_display_size_update: function () {
const updated = GodotDisplayScreen.updateSize();
@@ -480,6 +495,7 @@ const GodotDisplay = {
return updated;
},
+ godot_js_display_screen_size_get__proxy: 'sync',
godot_js_display_screen_size_get__sig: 'vii',
godot_js_display_screen_size_get: function (width, height) {
const scale = GodotDisplayScreen.getPixelRatio();
@@ -487,12 +503,14 @@ const GodotDisplay = {
GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32');
},
+ godot_js_display_window_size_get__proxy: 'sync',
godot_js_display_window_size_get__sig: 'vii',
godot_js_display_window_size_get: function (p_width, p_height) {
GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32');
GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32');
},
+ godot_js_display_has_webgl__proxy: 'sync',
godot_js_display_has_webgl__sig: 'ii',
godot_js_display_has_webgl: function (p_version) {
if (p_version !== 1 && p_version !== 2) {
@@ -507,11 +525,13 @@ const GodotDisplay = {
/*
* Canvas
*/
+ godot_js_display_canvas_focus__proxy: 'sync',
godot_js_display_canvas_focus__sig: 'v',
godot_js_display_canvas_focus: function () {
GodotConfig.canvas.focus();
},
+ godot_js_display_canvas_is_focused__proxy: 'sync',
godot_js_display_canvas_is_focused__sig: 'i',
godot_js_display_canvas_is_focused: function () {
return document.activeElement === GodotConfig.canvas;
@@ -520,6 +540,7 @@ const GodotDisplay = {
/*
* Touchscreen
*/
+ godot_js_display_touchscreen_is_available__proxy: 'sync',
godot_js_display_touchscreen_is_available__sig: 'i',
godot_js_display_touchscreen_is_available: function () {
return 'ontouchstart' in window;
@@ -528,6 +549,7 @@ const GodotDisplay = {
/*
* Clipboard
*/
+ godot_js_display_clipboard_set__proxy: 'sync',
godot_js_display_clipboard_set__sig: 'ii',
godot_js_display_clipboard_set: function (p_text) {
const text = GodotRuntime.parseString(p_text);
@@ -541,6 +563,7 @@ const GodotDisplay = {
return 0;
},
+ godot_js_display_clipboard_get__proxy: 'sync',
godot_js_display_clipboard_get__sig: 'ii',
godot_js_display_clipboard_get: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -560,11 +583,13 @@ const GodotDisplay = {
/*
* Window
*/
+ godot_js_display_window_title_set__proxy: 'sync',
godot_js_display_window_title_set__sig: 'vi',
godot_js_display_window_title_set: function (p_data) {
document.title = GodotRuntime.parseString(p_data);
},
+ godot_js_display_window_icon_set__proxy: 'sync',
godot_js_display_window_icon_set__sig: 'vii',
godot_js_display_window_icon_set: function (p_ptr, p_len) {
let link = document.getElementById('-gd-engine-icon');
@@ -593,6 +618,7 @@ const GodotDisplay = {
/*
* Cursor
*/
+ godot_js_display_cursor_set_visible__proxy: 'sync',
godot_js_display_cursor_set_visible__sig: 'vi',
godot_js_display_cursor_set_visible: function (p_visible) {
const visible = p_visible !== 0;
@@ -607,16 +633,19 @@ const GodotDisplay = {
}
},
+ godot_js_display_cursor_is_hidden__proxy: 'sync',
godot_js_display_cursor_is_hidden__sig: 'i',
godot_js_display_cursor_is_hidden: function () {
return !GodotDisplayCursor.visible;
},
+ godot_js_display_cursor_set_shape__proxy: 'sync',
godot_js_display_cursor_set_shape__sig: 'vi',
godot_js_display_cursor_set_shape: function (p_string) {
GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string));
},
+ godot_js_display_cursor_set_custom_shape__proxy: 'sync',
godot_js_display_cursor_set_custom_shape__sig: 'viiiii',
godot_js_display_cursor_set_custom_shape: function (p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) {
const shape = GodotRuntime.parseString(p_shape);
@@ -640,6 +669,7 @@ const GodotDisplay = {
}
},
+ godot_js_display_cursor_lock_set__proxy: 'sync',
godot_js_display_cursor_lock_set__sig: 'vi',
godot_js_display_cursor_lock_set: function (p_lock) {
if (p_lock) {
@@ -649,6 +679,7 @@ const GodotDisplay = {
}
},
+ godot_js_display_cursor_is_locked__proxy: 'sync',
godot_js_display_cursor_is_locked__sig: 'i',
godot_js_display_cursor_is_locked: function () {
return GodotDisplayCursor.isPointerLocked() ? 1 : 0;
@@ -657,6 +688,7 @@ const GodotDisplay = {
/*
* Listeners
*/
+ godot_js_display_fullscreen_cb__proxy: 'sync',
godot_js_display_fullscreen_cb__sig: 'vi',
godot_js_display_fullscreen_cb: function (callback) {
const canvas = GodotConfig.canvas;
@@ -671,6 +703,7 @@ const GodotDisplay = {
GodotEventListeners.add(document, 'webkitfullscreenchange', change_cb, false);
},
+ godot_js_display_window_blur_cb__proxy: 'sync',
godot_js_display_window_blur_cb__sig: 'vi',
godot_js_display_window_blur_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -679,6 +712,7 @@ const GodotDisplay = {
}, false);
},
+ godot_js_display_notification_cb__proxy: 'sync',
godot_js_display_notification_cb__sig: 'viiiii',
godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) {
const canvas = GodotConfig.canvas;
@@ -691,6 +725,7 @@ const GodotDisplay = {
});
},
+ godot_js_display_setup_canvas__proxy: 'sync',
godot_js_display_setup_canvas__sig: 'viiii',
godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen, p_hidpi) {
const canvas = GodotConfig.canvas;
@@ -725,6 +760,7 @@ const GodotDisplay = {
/*
* Virtual Keyboard
*/
+ godot_js_display_vk_show__proxy: 'sync',
godot_js_display_vk_show__sig: 'viiii',
godot_js_display_vk_show: function (p_text, p_type, p_start, p_end) {
const text = GodotRuntime.parseString(p_text);
@@ -733,21 +769,25 @@ const GodotDisplay = {
GodotDisplayVK.show(text, p_type, start, end);
},
+ godot_js_display_vk_hide__proxy: 'sync',
godot_js_display_vk_hide__sig: 'v',
godot_js_display_vk_hide: function () {
GodotDisplayVK.hide();
},
+ godot_js_display_vk_available__proxy: 'sync',
godot_js_display_vk_available__sig: 'i',
godot_js_display_vk_available: function () {
return GodotDisplayVK.available();
},
+ godot_js_display_tts_available__proxy: 'sync',
godot_js_display_tts_available__sig: 'i',
godot_js_display_tts_available: function () {
return 'speechSynthesis' in window;
},
+ godot_js_display_vk_cb__proxy: 'sync',
godot_js_display_vk_cb__sig: 'vi',
godot_js_display_vk_cb: function (p_input_cb) {
const input_cb = GodotRuntime.get_func(p_input_cb);
diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js
index 4ef24903e3..00616bc1a5 100644
--- a/platform/web/js/libs/library_godot_fetch.js
+++ b/platform/web/js/libs/library_godot_fetch.js
@@ -125,6 +125,7 @@ const GodotFetch = {
},
},
+ godot_js_fetch_create__proxy: 'sync',
godot_js_fetch_create__sig: 'iiiiiii',
godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) {
const method = GodotRuntime.parseString(p_method);
@@ -145,6 +146,7 @@ const GodotFetch = {
}), body);
},
+ godot_js_fetch_state_get__proxy: 'sync',
godot_js_fetch_state_get__sig: 'ii',
godot_js_fetch_state_get: function (p_id) {
const obj = IDHandler.get(p_id);
@@ -166,6 +168,7 @@ const GodotFetch = {
return -1;
},
+ godot_js_fetch_http_status_get__proxy: 'sync',
godot_js_fetch_http_status_get__sig: 'ii',
godot_js_fetch_http_status_get: function (p_id) {
const obj = IDHandler.get(p_id);
@@ -175,6 +178,7 @@ const GodotFetch = {
return obj.status;
},
+ godot_js_fetch_read_headers__proxy: 'sync',
godot_js_fetch_read_headers__sig: 'iiii',
godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) {
const obj = IDHandler.get(p_id);
@@ -192,6 +196,7 @@ const GodotFetch = {
return 0;
},
+ godot_js_fetch_read_chunk__proxy: 'sync',
godot_js_fetch_read_chunk__sig: 'iiii',
godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) {
const obj = IDHandler.get(p_id);
@@ -218,6 +223,7 @@ const GodotFetch = {
return p_buf_size - to_read;
},
+ godot_js_fetch_is_chunked__proxy: 'sync',
godot_js_fetch_is_chunked__sig: 'ii',
godot_js_fetch_is_chunked: function (p_id) {
const obj = IDHandler.get(p_id);
@@ -227,6 +233,7 @@ const GodotFetch = {
return obj.chunked ? 1 : 0;
},
+ godot_js_fetch_free__proxy: 'sync',
godot_js_fetch_free__sig: 'vi',
godot_js_fetch_free: function (id) {
GodotFetch.free(id);
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index 1b221e78b3..92113e85c9 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -356,6 +356,7 @@ const GodotInput = {
/*
* Mouse API
*/
+ godot_js_input_mouse_move_cb__proxy: 'sync',
godot_js_input_mouse_move_cb__sig: 'vi',
godot_js_input_mouse_move_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -374,6 +375,7 @@ const GodotInput = {
GodotEventListeners.add(window, 'mousemove', move_cb, false);
},
+ godot_js_input_mouse_wheel_cb__proxy: 'sync',
godot_js_input_mouse_wheel_cb__sig: 'vi',
godot_js_input_mouse_wheel_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -385,6 +387,7 @@ const GodotInput = {
GodotEventListeners.add(GodotConfig.canvas, 'wheel', wheel_cb, false);
},
+ godot_js_input_mouse_button_cb__proxy: 'sync',
godot_js_input_mouse_button_cb__sig: 'vi',
godot_js_input_mouse_button_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -409,6 +412,7 @@ const GodotInput = {
/*
* Touch API
*/
+ godot_js_input_touch_cb__proxy: 'sync',
godot_js_input_touch_cb__sig: 'viii',
godot_js_input_touch_cb: function (callback, ids, coords) {
const func = GodotRuntime.get_func(callback);
@@ -442,6 +446,7 @@ const GodotInput = {
/*
* Key API
*/
+ godot_js_input_key_cb__proxy: 'sync',
godot_js_input_key_cb__sig: 'viii',
godot_js_input_key_cb: function (callback, code, key) {
const func = GodotRuntime.get_func(callback);
@@ -459,23 +464,27 @@ const GodotInput = {
/*
* Gamepad API
*/
+ godot_js_input_gamepad_cb__proxy: 'sync',
godot_js_input_gamepad_cb__sig: 'vi',
godot_js_input_gamepad_cb: function (change_cb) {
const onchange = GodotRuntime.get_func(change_cb);
GodotInputGamepads.init(onchange);
},
+ godot_js_input_gamepad_sample_count__proxy: 'sync',
godot_js_input_gamepad_sample_count__sig: 'i',
godot_js_input_gamepad_sample_count: function () {
return GodotInputGamepads.get_samples().length;
},
+ godot_js_input_gamepad_sample__proxy: 'sync',
godot_js_input_gamepad_sample__sig: 'i',
godot_js_input_gamepad_sample: function () {
GodotInputGamepads.sample();
return 0;
},
+ godot_js_input_gamepad_sample_get__proxy: 'sync',
godot_js_input_gamepad_sample_get__sig: 'iiiiiii',
godot_js_input_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) {
const sample = GodotInputGamepads.get_sample(p_index);
@@ -502,6 +511,7 @@ const GodotInput = {
/*
* Drag/Drop API
*/
+ godot_js_input_drop_files_cb__proxy: 'sync',
godot_js_input_drop_files_cb__sig: 'vi',
godot_js_input_drop_files_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -524,6 +534,7 @@ const GodotInput = {
},
/* Paste API */
+ godot_js_input_paste_cb__proxy: 'sync',
godot_js_input_paste_cb__sig: 'vi',
godot_js_input_paste_cb: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -535,6 +546,7 @@ const GodotInput = {
}, false);
},
+ godot_js_input_vibrate_handheld__proxy: 'sync',
godot_js_input_vibrate_handheld__sig: 'vi',
godot_js_input_vibrate_handheld: function (p_duration_ms) {
if (typeof navigator.vibrate !== 'function') {
diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js
index 1764c9a026..b17fde1544 100644
--- a/platform/web/js/libs/library_godot_javascript_singleton.js
+++ b/platform/web/js/libs/library_godot_javascript_singleton.js
@@ -121,6 +121,7 @@ const GodotJSWrapper = {
},
},
+ godot_js_wrapper_interface_get__proxy: 'sync',
godot_js_wrapper_interface_get__sig: 'ii',
godot_js_wrapper_interface_get: function (p_name) {
const name = GodotRuntime.parseString(p_name);
@@ -130,6 +131,7 @@ const GodotJSWrapper = {
return 0;
},
+ godot_js_wrapper_object_get__proxy: 'sync',
godot_js_wrapper_object_get__sig: 'iiii',
godot_js_wrapper_object_get: function (p_id, p_exchange, p_prop) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
@@ -148,6 +150,7 @@ const GodotJSWrapper = {
return GodotJSWrapper.js2variant(obj, p_exchange);
},
+ godot_js_wrapper_object_set__proxy: 'sync',
godot_js_wrapper_object_set__sig: 'viiii',
godot_js_wrapper_object_set: function (p_id, p_name, p_type, p_exchange) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
@@ -162,6 +165,7 @@ const GodotJSWrapper = {
}
},
+ godot_js_wrapper_object_call__proxy: 'sync',
godot_js_wrapper_object_call__sig: 'iiiiiiiii',
godot_js_wrapper_object_call: function (p_id, p_method, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
@@ -189,6 +193,7 @@ const GodotJSWrapper = {
}
},
+ godot_js_wrapper_object_unref__proxy: 'sync',
godot_js_wrapper_object_unref__sig: 'vi',
godot_js_wrapper_object_unref: function (p_id) {
const proxy = IDHandler.get(p_id);
@@ -197,6 +202,7 @@ const GodotJSWrapper = {
}
},
+ godot_js_wrapper_create_cb__proxy: 'sync',
godot_js_wrapper_create_cb__sig: 'iii',
godot_js_wrapper_create_cb: function (p_ref, p_func) {
const func = GodotRuntime.get_func(p_func);
@@ -221,11 +227,13 @@ const GodotJSWrapper = {
return id;
},
+ godot_js_wrapper_object_set_cb_ret__proxy: 'sync',
godot_js_wrapper_object_set_cb_ret__sig: 'vii',
godot_js_wrapper_object_set_cb_ret: function (p_val_type, p_val_ex) {
GodotJSWrapper.cb_ret = GodotJSWrapper.variant2js(p_val_type, p_val_ex);
},
+ godot_js_wrapper_object_getvar__proxy: 'sync',
godot_js_wrapper_object_getvar__sig: 'iiii',
godot_js_wrapper_object_getvar: function (p_id, p_type, p_exchange) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
@@ -244,6 +252,7 @@ const GodotJSWrapper = {
}
},
+ godot_js_wrapper_object_setvar__proxy: 'sync',
godot_js_wrapper_object_setvar__sig: 'iiiiii',
godot_js_wrapper_object_setvar: function (p_id, p_key_type, p_key_ex, p_val_type, p_val_ex) {
const obj = GodotJSWrapper.get_proxied_value(p_id);
@@ -260,6 +269,7 @@ const GodotJSWrapper = {
}
},
+ godot_js_wrapper_create_object__proxy: 'sync',
godot_js_wrapper_create_object__sig: 'iiiiiiii',
godot_js_wrapper_create_object: function (p_object, p_args, p_argc, p_convert_callback, p_exchange, p_lock, p_free_lock_callback) {
const name = GodotRuntime.parseString(p_object);
diff --git a/platform/web/js/libs/library_godot_os.js b/platform/web/js/libs/library_godot_os.js
index 00ae399583..92635cb6ae 100644
--- a/platform/web/js/libs/library_godot_os.js
+++ b/platform/web/js/libs/library_godot_os.js
@@ -91,11 +91,13 @@ const GodotConfig = {
},
},
+ godot_js_config_canvas_id_get__proxy: 'sync',
godot_js_config_canvas_id_get__sig: 'vii',
godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) {
GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max);
},
+ godot_js_config_locale_get__proxy: 'sync',
godot_js_config_locale_get__sig: 'vii',
godot_js_config_locale_get: function (p_ptr, p_ptr_max) {
GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max);
@@ -266,22 +268,26 @@ const GodotOS = {
},
},
+ godot_js_os_finish_async__proxy: 'sync',
godot_js_os_finish_async__sig: 'vi',
godot_js_os_finish_async: function (p_callback) {
const func = GodotRuntime.get_func(p_callback);
GodotOS.finish_async(func);
},
+ godot_js_os_request_quit_cb__proxy: 'sync',
godot_js_os_request_quit_cb__sig: 'vi',
godot_js_os_request_quit_cb: function (p_callback) {
GodotOS.request_quit = GodotRuntime.get_func(p_callback);
},
+ godot_js_os_fs_is_persistent__proxy: 'sync',
godot_js_os_fs_is_persistent__sig: 'i',
godot_js_os_fs_is_persistent: function () {
return GodotFS.is_persistent();
},
+ godot_js_os_fs_sync__proxy: 'sync',
godot_js_os_fs_sync__sig: 'vi',
godot_js_os_fs_sync: function (callback) {
const func = GodotRuntime.get_func(callback);
@@ -291,6 +297,7 @@ const GodotOS = {
});
},
+ godot_js_os_has_feature__proxy: 'sync',
godot_js_os_has_feature__sig: 'ii',
godot_js_os_has_feature: function (p_ftr) {
const ftr = GodotRuntime.parseString(p_ftr);
@@ -313,6 +320,7 @@ const GodotOS = {
return 0;
},
+ godot_js_os_execute__proxy: 'sync',
godot_js_os_execute__sig: 'ii',
godot_js_os_execute: function (p_json) {
const json_args = GodotRuntime.parseString(p_json);
@@ -324,11 +332,13 @@ const GodotOS = {
return 1;
},
+ godot_js_os_shell_open__proxy: 'sync',
godot_js_os_shell_open__sig: 'vi',
godot_js_os_shell_open: function (p_uri) {
window.open(GodotRuntime.parseString(p_uri), '_blank');
},
+ godot_js_os_hw_concurrency_get__proxy: 'sync',
godot_js_os_hw_concurrency_get__sig: 'i',
godot_js_os_hw_concurrency_get: function () {
// TODO Godot core needs fixing to avoid spawning too many threads (> 24).
@@ -336,6 +346,7 @@ const GodotOS = {
return concurrency < 2 ? concurrency : 2;
},
+ godot_js_os_download_buffer__proxy: 'sync',
godot_js_os_download_buffer__sig: 'viiii',
godot_js_os_download_buffer: function (p_ptr, p_size, p_name, p_mime) {
const buf = GodotRuntime.heapSlice(HEAP8, p_ptr, p_size);
@@ -426,6 +437,7 @@ const GodotPWA = {
},
},
+ godot_js_pwa_cb__proxy: 'sync',
godot_js_pwa_cb__sig: 'vi',
godot_js_pwa_cb: function (p_update_cb) {
if ('serviceWorker' in navigator) {
@@ -434,6 +446,7 @@ const GodotPWA = {
}
},
+ godot_js_pwa_update__proxy: 'sync',
godot_js_pwa_update__sig: 'i',
godot_js_pwa_update: function () {
if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {
diff --git a/platform/web/js/libs/library_godot_webgl2.js b/platform/web/js/libs/library_godot_webgl2.js
index 3c6de4a071..dbaec9f01b 100644
--- a/platform/web/js/libs/library_godot_webgl2.js
+++ b/platform/web/js/libs/library_godot_webgl2.js
@@ -32,6 +32,19 @@ const GodotWebGL2 = {
$GodotWebGL2__deps: ['$GL', '$GodotRuntime'],
$GodotWebGL2: {},
+ // This is implemented as "glGetBufferSubData" in new emscripten versions.
+ // Since we have to support older (pre 2.0.17) emscripten versions, we add this wrapper function instead.
+ godot_webgl2_glGetBufferSubData__proxy: 'sync',
+ godot_webgl2_glGetBufferSubData__sig: 'vippp',
+ godot_webgl2_glGetBufferSubData__deps: ['$GL', 'emscripten_webgl_get_current_context'],
+ godot_webgl2_glGetBufferSubData: function (target, offset, size, data) {
+ const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
+ const gl = GL.getContext(gl_context_handle);
+ if (gl) {
+ gl.GLctx['getBufferSubData'](target, offset, HEAPU8, data, size);
+ }
+ },
+
godot_webgl2_glFramebufferTextureMultiviewOVR__deps: ['emscripten_webgl_get_current_context'],
godot_webgl2_glFramebufferTextureMultiviewOVR__proxy: 'sync',
godot_webgl2_glFramebufferTextureMultiviewOVR__sig: 'viiiiii',
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index 186e4abf92..cbdcbf565d 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -160,6 +160,12 @@ String OS_Web::get_name() const {
return "Web";
}
+void OS_Web::add_frame_delay(bool p_can_draw) {
+#ifndef PROXY_TO_PTHREAD_ENABLED
+ OS::add_frame_delay(p_can_draw);
+#endif
+}
+
void OS_Web::vibrate_handheld(int p_duration_ms) {
godot_js_input_vibrate_handheld(p_duration_ms);
}
diff --git a/platform/web/os_web.h b/platform/web/os_web.h
index f262337f00..5a48997c17 100644
--- a/platform/web/os_web.h
+++ b/platform/web/os_web.h
@@ -91,9 +91,10 @@ public:
String get_executable_path() const override;
Error shell_open(String p_uri) override;
String get_name() const override;
+
// Override default OS implementation which would block the main thread with delay_usec.
// Implemented in web_main.cpp loop callback instead.
- void add_frame_delay(bool p_can_draw) override {}
+ void add_frame_delay(bool p_can_draw) override;
void vibrate_handheld(int p_duration_ms) override;
diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp
index f199f8ffd8..ad2a801881 100644
--- a/platform/web/web_main.cpp
+++ b/platform/web/web_main.cpp
@@ -40,7 +40,10 @@
#include <stdlib.h>
static OS_Web *os = nullptr;
+#ifndef PROXY_TO_PTHREAD_ENABLED
static uint64_t target_ticks = 0;
+#endif
+
static bool main_started = false;
static bool shutdown_complete = false;
@@ -63,15 +66,20 @@ void cleanup_after_sync() {
}
void main_loop_callback() {
+#ifndef PROXY_TO_PTHREAD_ENABLED
uint64_t current_ticks = os->get_ticks_usec();
+#endif
bool force_draw = DisplayServerWeb::get_singleton()->check_size_force_redraw();
if (force_draw) {
Main::force_redraw();
+#ifndef PROXY_TO_PTHREAD_ENABLED
} else if (current_ticks < target_ticks) {
return; // Skip frame.
+#endif
}
+#ifndef PROXY_TO_PTHREAD_ENABLED
int max_fps = Engine::get_singleton()->get_max_fps();
if (max_fps > 0) {
if (current_ticks - target_ticks > 1000000) {
@@ -81,6 +89,8 @@ void main_loop_callback() {
}
target_ticks += (uint64_t)(1000000 / max_fps);
}
+#endif
+
if (os->main_loop_iterate()) {
emscripten_cancel_main_loop(); // Cancel current loop and set the cleanup one.
emscripten_set_main_loop(exit_callback, -1, false);
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 0ac6c2c8b0..60edb00dd2 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -109,6 +109,7 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
static GUID IID_XOneSWirelessGamepad = { MAKELONG(0x045E, 0x02EA), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneSBluetoothGamepad = { MAKELONG(0x045E, 0x02E0), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_XOneEliteWirelessGamepad = { MAKELONG(0x045E, 0x02E3), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
+ static GUID IID_XOneElite2WirelessGamepad = { MAKELONG(0x045E, 0x0B22), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
if (memcmp(p_guid, &IID_ValveStreamingGamepad, sizeof(*p_guid)) == 0 ||
memcmp(p_guid, &IID_X360WiredGamepad, sizeof(*p_guid)) == 0 ||
@@ -120,7 +121,8 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
memcmp(p_guid, &IID_XOneNewWirelessGamepad, sizeof(*p_guid)) == 0 ||
memcmp(p_guid, &IID_XOneSWirelessGamepad, sizeof(*p_guid)) == 0 ||
memcmp(p_guid, &IID_XOneSBluetoothGamepad, sizeof(*p_guid)) == 0 ||
- memcmp(p_guid, &IID_XOneEliteWirelessGamepad, sizeof(*p_guid)) == 0)
+ memcmp(p_guid, &IID_XOneEliteWirelessGamepad, sizeof(*p_guid)) == 0 ||
+ memcmp(p_guid, &IID_XOneElite2WirelessGamepad, sizeof(*p_guid)) == 0)
return true;
PRAWINPUTDEVICELIST dev_list = nullptr;
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 67b14692a2..ee33ff88d4 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -192,6 +192,11 @@ void GPUParticles2D::set_trail_section_subdivisions(int p_subdivisions) {
queue_redraw();
}
+void GPUParticles2D::set_interp_to_end(float p_interp) {
+ interp_to_end_factor = CLAMP(p_interp, 0.0, 1.0);
+ RS::get_singleton()->particles_set_interp_to_end(particles, interp_to_end_factor);
+}
+
#ifdef TOOLS_ENABLED
void GPUParticles2D::set_show_visibility_rect(bool p_show_visibility_rect) {
show_visibility_rect = p_show_visibility_rect;
@@ -318,6 +323,10 @@ bool GPUParticles2D::get_interpolate() const {
return interpolate;
}
+float GPUParticles2D::get_interp_to_end() const {
+ return interp_to_end_factor;
+}
+
PackedStringArray GPUParticles2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
@@ -423,6 +432,15 @@ NodePath GPUParticles2D::get_sub_emitter() const {
return sub_emitter;
}
+void GPUParticles2D::set_amount_ratio(float p_ratio) {
+ amount_ratio = p_ratio;
+ RenderingServer::get_singleton()->particles_set_amount_ratio(particles, p_ratio);
+}
+
+float GPUParticles2D::get_amount_ratio() const {
+ return amount_ratio;
+}
+
void GPUParticles2D::restart() {
RS::get_singleton()->particles_restart(particles);
RS::get_singleton()->particles_set_emitting(particles, true);
@@ -670,6 +688,8 @@ void GPUParticles2D::_notification(int p_what) {
} else {
RS::get_singleton()->particles_set_speed_scale(particles, 0);
}
+ set_process_internal(true);
+ previous_position = get_global_position();
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -692,6 +712,12 @@ void GPUParticles2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
+ RS::get_singleton()->particles_set_emitter_velocity(particles,
+ Vector3((get_global_position() - previous_position).x,
+ (get_global_position() - previous_position).y,
+ 0.0) /
+ get_process_delta_time());
+ previous_position = get_global_position();
if (one_shot) {
time += get_process_delta_time();
if (time > emission_time) {
@@ -730,6 +756,7 @@ void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles2D::set_process_material);
ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles2D::set_speed_scale);
ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles2D::set_collision_base_size);
+ ClassDB::bind_method(D_METHOD("set_interp_to_end", "interp"), &GPUParticles2D::set_interp_to_end);
ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles2D::is_emitting);
ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles2D::get_amount);
@@ -746,6 +773,7 @@ void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles2D::get_process_material);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles2D::get_speed_scale);
ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles2D::get_collision_base_size);
+ ClassDB::bind_method(D_METHOD("get_interp_to_end"), &GPUParticles2D::get_interp_to_end);
ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles2D::set_draw_order);
ClassDB::bind_method(D_METHOD("get_draw_order"), &GPUParticles2D::get_draw_order);
@@ -776,11 +804,15 @@ void GPUParticles2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles2D::convert_from_particles);
+ ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles2D::set_amount_ratio);
+ ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles2D::get_amount_ratio);
+
ADD_SIGNAL(MethodInfo("finished"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles2D"), "set_sub_emitter", "get_sub_emitter");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "process_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,ParticleProcessMaterial"), "set_process_material", "get_process_material");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
@@ -794,6 +826,7 @@ void GPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1,suffix:FPS"), "set_fixed_fps", "get_fixed_fps");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.001"), "set_interp_to_end", "get_interp_to_end");
ADD_GROUP("Collision", "collision_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
ADD_GROUP("Drawing", "");
@@ -829,6 +862,7 @@ GPUParticles2D::GPUParticles2D() {
set_emitting(true);
set_one_shot(false);
set_amount(8);
+ set_amount_ratio(1.0);
set_lifetime(1);
set_fixed_fps(0);
set_fractional_delta(true);
diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h
index 3a342e2c22..40831cd30e 100644
--- a/scene/2d/gpu_particles_2d.h
+++ b/scene/2d/gpu_particles_2d.h
@@ -52,6 +52,7 @@ private:
bool signal_canceled = false;
bool one_shot = false;
int amount = 0;
+ float amount_ratio = 1.0;
double lifetime = 0.0;
double pre_process_time = 0.0;
real_t explosiveness_ratio = 0.0;
@@ -62,6 +63,8 @@ private:
int fixed_fps = 0;
bool fractional_delta = false;
bool interpolate = true;
+ float interp_to_end_factor = 0;
+ Vector2 previous_position;
#ifdef TOOLS_ENABLED
bool show_visibility_rect = false;
#endif
@@ -114,6 +117,7 @@ public:
void set_trail_lifetime(double p_seconds);
void set_trail_sections(int p_sections);
void set_trail_section_subdivisions(int p_subdivisions);
+ void set_interp_to_end(float p_interp);
#ifdef TOOLS_ENABLED
void set_show_visibility_rect(bool p_show_visibility_rect);
@@ -136,6 +140,7 @@ public:
double get_trail_lifetime() const;
int get_trail_sections() const;
int get_trail_section_subdivisions() const;
+ float get_interp_to_end() const;
void set_fixed_fps(int p_count);
int get_fixed_fps() const;
@@ -152,6 +157,9 @@ public:
void set_texture(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_texture() const;
+ void set_amount_ratio(float p_ratio);
+ float get_amount_ratio() const;
+
PackedStringArray get_configuration_warnings() const override;
void set_sub_emitter(const NodePath &p_path);
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 7b0bd7e26c..b1c5677726 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -197,10 +197,10 @@ void TileMapLayer::_rendering_update() {
if (!canvas_item.is_valid()) {
RID ci = rs->canvas_item_create();
rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item());
- rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000);
canvas_item = ci;
}
RID &ci = canvas_item;
+ rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000);
rs->canvas_item_set_sort_children_by_y(ci, y_sort_enabled);
rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid());
rs->canvas_item_set_z_index(ci, z_index);
@@ -4518,14 +4518,26 @@ PackedStringArray TileMap::get_configuration_warnings() const {
}
}
- // Check if Y-sort is enabled on a layer but not on the node.
if (!is_y_sort_enabled()) {
+ // Check if Y-sort is enabled on a layer but not on the node.
for (const Ref<TileMapLayer> &layer : layers) {
if (layer->is_y_sort_enabled()) {
warnings.push_back(RTR("A TileMap layer is set as Y-sorted, but Y-sort is not enabled on the TileMap node itself."));
break;
}
}
+ } else {
+ // Check if Y-sort is enabled on the node, but not on any of the layers.
+ bool need_warning = true;
+ for (const Ref<TileMapLayer> &layer : layers) {
+ if (layer->is_y_sort_enabled()) {
+ need_warning = false;
+ break;
+ }
+ }
+ if (need_warning) {
+ warnings.push_back(RTR("The TileMap node is set as Y-sorted, but Y-sort is not enabled on any of the TileMap's layers.\nThis may lead to unwanted behaviors, as a layer that is not Y-sorted will be Y-sorted as a whole."));
+ }
}
// Check if we are in isometric mode without Y-sort enabled.
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 12f13bee2f..fc84b3308e 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -61,6 +61,8 @@ void GPUParticles3D::set_emitting(bool p_emitting) {
} else {
set_process_internal(false);
}
+ } else {
+ set_process_internal(true);
}
emitting = p_emitting;
@@ -79,20 +81,20 @@ void GPUParticles3D::set_lifetime(double p_lifetime) {
RS::get_singleton()->particles_set_lifetime(particles, lifetime);
}
+void GPUParticles3D::set_interp_to_end(float p_interp) {
+ interp_to_end_factor = CLAMP(p_interp, 0.0, 1.0);
+ RS::get_singleton()->particles_set_interp_to_end(particles, interp_to_end_factor);
+}
+
void GPUParticles3D::set_one_shot(bool p_one_shot) {
one_shot = p_one_shot;
RS::get_singleton()->particles_set_one_shot(particles, one_shot);
if (is_emitting()) {
- set_process_internal(true);
if (!one_shot) {
RenderingServer::get_singleton()->particles_restart(particles);
}
}
-
- if (!one_shot) {
- set_process_internal(false);
- }
}
void GPUParticles3D::set_pre_process_time(double p_time) {
@@ -154,6 +156,10 @@ double GPUParticles3D::get_lifetime() const {
return lifetime;
}
+float GPUParticles3D::get_interp_to_end() const {
+ return interp_to_end_factor;
+}
+
bool GPUParticles3D::get_one_shot() const {
return one_shot;
}
@@ -401,9 +407,7 @@ void GPUParticles3D::restart() {
time = 0;
emission_time = lifetime * (1 - explosiveness_ratio);
active_time = lifetime * (2 - explosiveness_ratio);
- if (one_shot) {
- set_process_internal(true);
- }
+ set_process_internal(true);
}
AABB GPUParticles3D::capture_aabb() const {
@@ -456,6 +460,9 @@ void GPUParticles3D::_notification(int p_what) {
// Use internal process when emitting and one_shot is on so that when
// the shot ends the editor can properly update.
case NOTIFICATION_INTERNAL_PROCESS: {
+ RS::get_singleton()->particles_set_emitter_velocity(particles, (get_global_position() - previous_position) / get_process_delta_time());
+ previous_position = get_global_position();
+
if (one_shot) {
time += get_process_delta_time();
if (time > emission_time) {
@@ -477,6 +484,7 @@ void GPUParticles3D::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_TREE: {
+ set_process_internal(false);
if (sub_emitter != NodePath()) {
_attach_sub_emitter();
}
@@ -485,6 +493,8 @@ void GPUParticles3D::_notification(int p_what) {
} else {
RS::get_singleton()->particles_set_speed_scale(particles, 0);
}
+ previous_position = get_global_transform().origin;
+ set_process_internal(true);
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -641,6 +651,15 @@ void GPUParticles3D::convert_from_particles(Node *p_particles) {
#undef CONVERT_PARAM
}
+void GPUParticles3D::set_amount_ratio(float p_ratio) {
+ amount_ratio = p_ratio;
+ RS::get_singleton()->particles_set_amount_ratio(particles, p_ratio);
+}
+
+float GPUParticles3D::get_amount_ratio() const {
+ return amount_ratio;
+}
+
void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emitting", "emitting"), &GPUParticles3D::set_emitting);
ClassDB::bind_method(D_METHOD("set_amount", "amount"), &GPUParticles3D::set_amount);
@@ -657,6 +676,7 @@ void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_process_material", "material"), &GPUParticles3D::set_process_material);
ClassDB::bind_method(D_METHOD("set_speed_scale", "scale"), &GPUParticles3D::set_speed_scale);
ClassDB::bind_method(D_METHOD("set_collision_base_size", "size"), &GPUParticles3D::set_collision_base_size);
+ ClassDB::bind_method(D_METHOD("set_interp_to_end", "interp"), &GPUParticles3D::set_interp_to_end);
ClassDB::bind_method(D_METHOD("is_emitting"), &GPUParticles3D::is_emitting);
ClassDB::bind_method(D_METHOD("get_amount"), &GPUParticles3D::get_amount);
@@ -673,6 +693,7 @@ void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_process_material"), &GPUParticles3D::get_process_material);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &GPUParticles3D::get_speed_scale);
ClassDB::bind_method(D_METHOD("get_collision_base_size"), &GPUParticles3D::get_collision_base_size);
+ ClassDB::bind_method(D_METHOD("get_interp_to_end"), &GPUParticles3D::get_interp_to_end);
ClassDB::bind_method(D_METHOD("set_draw_order", "order"), &GPUParticles3D::set_draw_order);
@@ -706,14 +727,19 @@ void GPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("convert_from_particles", "particles"), &GPUParticles3D::convert_from_particles);
+ ClassDB::bind_method(D_METHOD("set_amount_ratio", "ratio"), &GPUParticles3D::set_amount_ratio);
+ ClassDB::bind_method(D_METHOD("get_amount_ratio"), &GPUParticles3D::get_amount_ratio);
+
ADD_SIGNAL(MethodInfo("finished"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emitting"), "set_emitting", "is_emitting");
ADD_PROPERTY_DEFAULT("emitting", true); // Workaround for doctool in headless mode, as dummy rasterizer always returns false.
ADD_PROPERTY(PropertyInfo(Variant::INT, "amount", PROPERTY_HINT_RANGE, "1,1000000,1,exp"), "set_amount", "get_amount");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "amount_ratio", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_amount_ratio", "get_amount_ratio");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "sub_emitter", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GPUParticles3D"), "set_sub_emitter", "get_sub_emitter");
ADD_GROUP("Time", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime", PROPERTY_HINT_RANGE, "0.01,600.0,0.01,or_greater,exp,suffix:s"), "set_lifetime", "get_lifetime");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interp_to_end", PROPERTY_HINT_RANGE, "0.00,1.0,0.01"), "set_interp_to_end", "get_interp_to_end");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "one_shot"), "set_one_shot", "get_one_shot");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "preprocess", PROPERTY_HINT_RANGE, "0.00,600.0,0.01,exp,suffix:s"), "set_pre_process_time", "get_pre_process_time");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_speed_scale", "get_speed_scale");
@@ -767,6 +793,7 @@ GPUParticles3D::GPUParticles3D() {
one_shot = false; // Needed so that set_emitting doesn't access uninitialized values
set_emitting(true);
set_one_shot(false);
+ set_amount_ratio(1.0);
set_amount(8);
set_lifetime(1);
set_fixed_fps(30);
diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h
index f3df4f1929..ae9349817c 100644
--- a/scene/3d/gpu_particles_3d.h
+++ b/scene/3d/gpu_particles_3d.h
@@ -65,6 +65,7 @@ private:
bool signal_canceled = false;
bool one_shot = false;
int amount = 0;
+ float amount_ratio = 1.0;
double lifetime = 0.0;
double pre_process_time = 0.0;
real_t explosiveness_ratio = 0.0;
@@ -93,6 +94,8 @@ private:
double time = 0.0;
double emission_time = 0.0;
double active_time = 0.0;
+ float interp_to_end_factor = 0;
+ Vector3 previous_position;
void _attach_sub_emitter();
@@ -120,9 +123,11 @@ public:
void set_collision_base_size(real_t p_ratio);
void set_trail_enabled(bool p_enabled);
void set_trail_lifetime(double p_seconds);
+ void set_interp_to_end(float p_interp);
bool is_emitting() const;
int get_amount() const;
+
double get_lifetime() const;
bool get_one_shot() const;
double get_pre_process_time() const;
@@ -135,6 +140,10 @@ public:
real_t get_collision_base_size() const;
bool is_trail_enabled() const;
double get_trail_lifetime() const;
+ float get_interp_to_end() const;
+
+ void set_amount_ratio(float p_ratio);
+ float get_amount_ratio() const;
void set_fixed_fps(int p_count);
int get_fixed_fps() const;
diff --git a/scene/3d/voxelizer.cpp b/scene/3d/voxelizer.cpp
index 7c752545db..fec4bc2b05 100644
--- a/scene/3d/voxelizer.cpp
+++ b/scene/3d/voxelizer.cpp
@@ -884,6 +884,8 @@ Vector<uint8_t> Voxelizer::get_sdf_3d_image() const {
}
}
+ memdelete_arr(work_memory);
+
return image3d;
}
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index d99f5aa3e4..cca1c6efbb 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -868,6 +868,27 @@ bool AnimationMixer::_update_caches() {
track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
+ // TODO: Currently, misc type cannot be blended. In the future,
+ // it should have a separate blend weight, just as bool is converted to 0 and 1.
+ // Then, it should provide the correct precedence value.
+ switch (track_value->init_value.get_type()) {
+ case Variant::NIL:
+ case Variant::STRING_NAME:
+ case Variant::NODE_PATH:
+ case Variant::RID:
+ case Variant::OBJECT:
+ case Variant::CALLABLE:
+ case Variant::SIGNAL:
+ case Variant::DICTIONARY:
+ case Variant::ARRAY: {
+ WARN_PRINT_ONCE_ED("AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' uses a non-numeric type as key value with UpdateMode.UPDATE_CONTINUOUS. This will not be blended correctly, so it is forced to UpdateMode.UPDATE_DISCRETE.");
+ track_value->is_continuous = false;
+ break;
+ }
+ default: {
+ }
+ }
+
if (was_continuous != track_value->is_continuous) {
WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
}
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 2d4f2cbceb..b3285d4cfc 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -544,12 +544,12 @@ bool AnimationPlayer::is_valid() const {
}
double AnimationPlayer::get_current_animation_position() const {
- ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation");
+ ERR_FAIL_NULL_V_MSG(playback.current.from, 0, "AnimationPlayer has no current animation.");
return playback.current.pos;
}
double AnimationPlayer::get_current_animation_length() const {
- ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation");
+ ERR_FAIL_NULL_V_MSG(playback.current.from, 0, "AnimationPlayer has no current animation.");
return playback.current.from->animation->get_length();
}
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 1b8c410101..c778129eb6 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -675,7 +675,7 @@ bool CallbackTweener::step(double &r_delta) {
return false;
}
- if (!callback.get_object()) {
+ if (!callback.is_valid()) {
return false;
}
@@ -740,7 +740,7 @@ bool MethodTweener::step(double &r_delta) {
return false;
}
- if (!callback.get_object()) {
+ if (!callback.is_valid()) {
return false;
}
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 20fcf9cba7..40c276e4e0 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -479,19 +479,8 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
accept_event();
return;
}
- if (k->is_action("ui_home", true)) {
- code_completion_current_selected = 0;
- code_completion_force_item_center = -1;
- queue_redraw();
- accept_event();
- return;
- }
- if (k->is_action("ui_end", true)) {
- code_completion_current_selected = code_completion_options.size() - 1;
- code_completion_force_item_center = -1;
- queue_redraw();
- accept_event();
- return;
+ if (k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_line_end", true)) {
+ cancel_code_completion();
}
if (k->is_action("ui_text_completion_replace", true) || k->is_action("ui_text_completion_accept", true)) {
confirm_code_completion(k->is_action("ui_text_completion_replace", true));
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index be05273a09..2d2e964449 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -676,7 +676,7 @@ void FileDialog::update_file_list() {
files.pop_front();
}
- if (mode != FILE_MODE_SAVE_FILE) {
+ if (mode != FILE_MODE_SAVE_FILE && mode != FILE_MODE_OPEN_DIR) {
// Select the first file from list if nothing is selected.
if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) {
tree->get_root()->get_first_child()->select(0);
diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp
index 5129fdd135..7fa5b0ceec 100644
--- a/scene/gui/graph_element.cpp
+++ b/scene/gui/graph_element.cpp
@@ -150,7 +150,7 @@ void GraphElement::gui_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
- ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphElement must be the child of a GraphEdit node.");
+ ERR_FAIL_NULL_MSG(get_parent_control(), "GraphElement must be the child of a GraphEdit node.");
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Vector2 mpos = mb->get_position();
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index ec6d5f01f1..9de86d1877 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -486,6 +486,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k->is_action("ui_cancel")) {
callable_mp((Control *)this, &Control::release_focus).call_deferred();
+ accept_event();
return;
}
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index e3b0a18325..2e4a35e1d3 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -2170,14 +2170,16 @@ int PopupMenu::get_item_count() const {
void PopupMenu::scroll_to_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
- // Scroll item into view (upwards).
- if (items[p_idx]._ofs_cache - scroll_container->get_v_scroll() < -control->get_position().y) {
- scroll_container->set_v_scroll(items[p_idx]._ofs_cache + control->get_position().y);
- }
-
- // Scroll item into view (downwards).
- if (items[p_idx]._ofs_cache + items[p_idx]._height_cache - scroll_container->get_v_scroll() > -control->get_position().y + scroll_container->get_size().height) {
- scroll_container->set_v_scroll(items[p_idx]._ofs_cache + items[p_idx]._height_cache + control->get_position().y);
+ // Calculate the position of the item relative to the visible area.
+ int item_y = items[p_idx]._ofs_cache;
+ int visible_height = scroll_container->get_size().height;
+ int relative_y = item_y - scroll_container->get_v_scroll();
+
+ // If item is not fully visible, adjust scroll.
+ if (relative_y < 0) {
+ scroll_container->set_v_scroll(item_y);
+ } else if (relative_y + items[p_idx]._height_cache > visible_height) {
+ scroll_container->set_v_scroll(item_y + items[p_idx]._height_cache - visible_height);
}
}
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index b4cd201e6a..7768c2d84e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1143,8 +1143,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
fx_offset = fx_offset.round();
}
-
- Vector2i char_off = char_xform.get_origin();
+ Vector2 char_off = char_xform.get_origin();
// Draw glyph outlines.
const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a);
@@ -1389,8 +1388,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
fx_offset = fx_offset.round();
}
+ Vector2 char_off = char_xform.get_origin();
- Vector2i char_off = char_xform.get_origin();
Transform2D char_reverse_xform;
char_reverse_xform.set_origin(-char_off);
char_xform = char_xform * char_reverse_xform;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 2f5c05859f..073f164bdd 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -212,9 +212,6 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features());
}
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(r, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
- }
}
// Apply tab align.
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e78789edbd..2d3166270b 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2770,21 +2770,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
x -= theme_cache.h_separation;
}
- if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
- if (enable_recursive_folding && p_mod->is_shift_pressed()) {
- p_item->set_collapsed_recursive(!p_item->is_collapsed());
- } else {
- p_item->set_collapsed(!p_item->is_collapsed());
- }
- return -1; //collapse/uncollapse because nothing can be done with item
- }
-
const TreeItem::Cell &c = p_item->cells[col];
- bool already_selected = c.selected;
- bool already_cursor = (p_item == selected_item) && col == selected_col;
-
- if (!cache.rtl && p_item->cells[col].buttons.size()) {
+ if (!cache.rtl && !p_item->cells[col].buttons.is_empty()) {
int button_w = 0;
for (int j = p_item->cells[col].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = p_item->cells[col].buttons[j].texture;
@@ -2827,6 +2815,18 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width -= w + theme_cache.button_margin;
}
+ if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
+ if (enable_recursive_folding && p_mod->is_shift_pressed()) {
+ p_item->set_collapsed_recursive(!p_item->is_collapsed());
+ } else {
+ p_item->set_collapsed(!p_item->is_collapsed());
+ }
+ return -1; // Collapse/uncollapse, because nothing can be done with the item.
+ }
+
+ bool already_selected = c.selected;
+ bool already_cursor = (p_item == selected_item) && col == selected_col;
+
if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) {
/* process selection */
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 584e08d32e..f44e3de203 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1855,7 +1855,7 @@ void Node::_set_owner_nocheck(Node *p_owner) {
}
void Node::_release_unique_name_in_owner() {
- ERR_FAIL_NULL(data.owner); // Sanity check.
+ ERR_FAIL_NULL(data.owner); // Safety check.
StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String());
Node **which = data.owner->data.owned_unique_nodes.getptr(key);
if (which == nullptr || *which != this) {
@@ -1865,7 +1865,7 @@ void Node::_release_unique_name_in_owner() {
}
void Node::_acquire_unique_name_in_owner() {
- ERR_FAIL_NULL(data.owner); // Sanity check.
+ ERR_FAIL_NULL(data.owner); // Safety check.
StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String());
Node **which = data.owner->data.owned_unique_nodes.getptr(key);
if (which != nullptr && *which != this) {
@@ -1938,7 +1938,7 @@ Node *Node::get_owner() const {
}
void Node::_clean_up_owner() {
- ERR_FAIL_NULL(data.owner); // Sanity check.
+ ERR_FAIL_NULL(data.owner); // Safety check.
if (data.unique_name_in_owner) {
_release_unique_name_in_owner();
diff --git a/scene/main/node.h b/scene/main/node.h
index 2a45b70139..94c6893170 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -411,7 +411,7 @@ public:
Window *get_last_exclusive_window() const;
_FORCE_INLINE_ SceneTree *get_tree() const {
- ERR_FAIL_COND_V(!data.tree, nullptr);
+ ERR_FAIL_NULL_V(data.tree, nullptr);
return data.tree;
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 368fd8fae0..fd5aa65428 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2734,7 +2734,7 @@ Viewport::SubWindowResize Viewport::_sub_window_get_resize_margin(Window *p_subw
bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) {
- ERR_FAIL_COND_V(gui.currently_dragged_subwindow == nullptr, false);
+ ERR_FAIL_NULL_V(gui.currently_dragged_subwindow, false);
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
@@ -4019,6 +4019,17 @@ void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near,
}
}
+void Viewport::set_disable_2d(bool p_disable) {
+ ERR_MAIN_THREAD_GUARD;
+ disable_2d = p_disable;
+ RenderingServer::get_singleton()->viewport_set_disable_2d(viewport, disable_2d);
+}
+
+bool Viewport::is_2d_disabled() const {
+ ERR_READ_THREAD_GUARD_V(false);
+ return disable_2d;
+}
+
void Viewport::set_disable_3d(bool p_disable) {
ERR_MAIN_THREAD_GUARD;
disable_3d = p_disable;
@@ -4461,6 +4472,9 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_as_audio_listener_3d", "enable"), &Viewport::set_as_audio_listener_3d);
ClassDB::bind_method(D_METHOD("is_audio_listener_3d"), &Viewport::is_audio_listener_3d);
+ ClassDB::bind_method(D_METHOD("set_disable_2d", "disable"), &Viewport::set_disable_2d);
+ ClassDB::bind_method(D_METHOD("is_2d_disabled"), &Viewport::is_2d_disabled);
+
ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d);
ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled);
@@ -4485,6 +4499,7 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vrs_texture", "texture"), &Viewport::set_vrs_texture);
ClassDB::bind_method(D_METHOD("get_vrs_texture"), &Viewport::get_vrs_texture);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_2d"), "set_disable_2d", "is_2d_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d");
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 65777c973f..247c9e0928 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -283,6 +283,7 @@ private:
void _update_audio_listener_2d();
+ bool disable_2d = false;
bool disable_3d = false;
void _propagate_viewport_notification(Node *p_node, int p_what);
@@ -738,6 +739,9 @@ public:
void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
+ void set_disable_2d(bool p_disable);
+ bool is_2d_disabled() const;
+
void set_disable_3d(bool p_disable);
bool is_3d_disabled() const;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 71ec9b29c5..2c28dc31d6 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -779,7 +779,7 @@ void Window::set_visible(bool p_visible) {
return;
}
- ERR_FAIL_COND_MSG(get_parent() == nullptr, "Can't change visibility of main window.");
+ ERR_FAIL_NULL_MSG(get_parent(), "Can't change visibility of main window.");
visible = p_visible;
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index c49da1ded1..af1f9da2b5 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -4398,7 +4398,7 @@ struct AnimationCompressionDataState {
PacketData packet;
packet.frame = p_frame;
for (int i = 0; i < 3; i++) {
- ERR_FAIL_COND_V(p_key[i] > 65535, false); // Sanity check
+ ERR_FAIL_COND_V(p_key[i] > 65535, false); // Safety checks.
packet.data[i] = p_key[i];
}
@@ -4544,7 +4544,7 @@ struct AnimationCompressionDataState {
}
int32_t delta = _compute_delta16_signed(temp_packets[i - 1].data[j], temp_packets[i].data[j]);
- ERR_FAIL_COND(delta < -32768 || delta > 32767); //sanity check
+ ERR_FAIL_COND(delta < -32768 || delta > 32767); // Safety check.
uint16_t deltau;
if (delta < 0) {
@@ -4556,7 +4556,7 @@ struct AnimationCompressionDataState {
}
}
if (bits_used != 0) {
- ERR_FAIL_COND(bit_buffer > 0xFF); // Sanity check
+ ERR_FAIL_COND(bit_buffer > 0xFF); // Safety check.
data.push_back(bit_buffer);
}
@@ -4645,7 +4645,7 @@ Vector3i Animation::_compress_key(uint32_t p_track, const AABB &p_bounds, int32_
values[0] = CLAMP(int32_t(blend * 65535.0), 0, 65535);
} break;
default: {
- ERR_FAIL_V(Vector3i()); //sanity check
+ ERR_FAIL_V(Vector3i()); // Safety check.
} break;
}
@@ -4819,7 +4819,7 @@ void Animation::compress(uint32_t p_page_size, uint32_t p_fps, float p_split_tol
break;
}
- ERR_FAIL_COND(key_frame < base_page_frame); // Sanity check, should never happen
+ ERR_FAIL_COND(key_frame < base_page_frame); // Safety check, should never happen.
if (key_frame - base_page_frame >= max_frames_per_page) {
// Invalid because beyond the max frames allowed per page
@@ -5260,7 +5260,7 @@ bool Animation::_fetch_compressed(uint32_t p_compressed_track, double p_time, Ve
// So, the last frame found still has a time that is less than the required frame,
// will have to interpolate with the first frame of the next timekey.
- if ((uint32_t)packet_idx < time_key_count - 1) { // Sanity check but should not matter much, otherwise current next packet is last packet
+ if ((uint32_t)packet_idx < time_key_count - 1) { // Safety check but should not matter much, otherwise current next packet is last packet.
uint16_t time_key_data_next = time_keys[(packet_idx + 1) * 2 + 1];
uint32_t data_offset_next = (time_key_data_next & 0xFFF) * 4; // Lower 12 bits
diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp
index 258e3e90c3..d2aabf7d5c 100644
--- a/scene/resources/curve.cpp
+++ b/scene/resources/curve.cpp
@@ -1808,7 +1808,7 @@ real_t Curve3D::_sample_baked_tilt(Interval p_interval) const {
}
// Internal method for getting posture at a baked point. Assuming caller
-// make all sanity checks.
+// make all safety checks.
Basis Curve3D::_compose_posture(int p_index) const {
Vector3 forward = baked_forward_vector_cache[p_index];
diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp
index dbc74d7c87..0112ffd452 100644
--- a/scene/resources/immediate_mesh.cpp
+++ b/scene/resources/immediate_mesh.cpp
@@ -175,7 +175,7 @@ void ImmediateMesh::surface_end() {
AABB aabb;
{
- surface_vertex_create_cache.resize(vertex_stride * vertices.size());
+ surface_vertex_create_cache.resize((vertex_stride + normal_tangent_stride) * vertices.size());
uint8_t *surface_vertex_ptr = surface_vertex_create_cache.ptrw();
for (uint32_t i = 0; i < vertices.size(); i++) {
{
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 39b3acfe3f..461dbbec2f 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -3079,6 +3079,8 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) :
flags[FLAG_ALBEDO_TEXTURE_MSDF] = false;
flags[FLAG_USE_TEXTURE_REPEAT] = true;
+ current_key.invalid_key = 1;
+
_mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change));
}
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 8cd583c709..a631f77598 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -325,6 +325,7 @@ private:
uint64_t emission_op : get_num_bits(EMISSION_OP_MAX - 1);
uint64_t distance_fade : get_num_bits(DISTANCE_FADE_MAX - 1);
// booleans
+ uint64_t invalid_key : 1;
uint64_t deep_parallax : 1;
uint64_t grow : 1;
uint64_t proximity_fade : 1;
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index dbb286c6e7..3e710e1f27 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -50,6 +50,7 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->initial_angle_min = "initial_angle_min";
shader_names->angular_velocity_min = "angular_velocity_min";
shader_names->orbit_velocity_min = "orbit_velocity_min";
+ shader_names->radial_velocity_min = "radial_velocity_min";
shader_names->linear_accel_min = "linear_accel_min";
shader_names->radial_accel_min = "radial_accel_min";
shader_names->tangent_accel_min = "tangent_accel_min";
@@ -58,11 +59,14 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->hue_variation_min = "hue_variation_min";
shader_names->anim_speed_min = "anim_speed_min";
shader_names->anim_offset_min = "anim_offset_min";
+ shader_names->directional_velocity_min = "directional_velocity_min";
+ shader_names->scale_over_velocity_min = "scale_over_velocity_min";
shader_names->initial_linear_velocity_max = "initial_linear_velocity_max";
shader_names->initial_angle_max = "initial_angle_max";
shader_names->angular_velocity_max = "angular_velocity_max";
shader_names->orbit_velocity_max = "orbit_velocity_max";
+ shader_names->radial_velocity_max = "radial_velocity_max";
shader_names->linear_accel_max = "linear_accel_max";
shader_names->radial_accel_max = "radial_accel_max";
shader_names->tangent_accel_max = "tangent_accel_max";
@@ -71,22 +75,32 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->hue_variation_max = "hue_variation_max";
shader_names->anim_speed_max = "anim_speed_max";
shader_names->anim_offset_max = "anim_offset_max";
+ shader_names->directional_velocity_max = "directional_velocity_max";
+ shader_names->scale_over_velocity_max = "scale_over_velocity_max";
shader_names->angle_texture = "angle_texture";
shader_names->angular_velocity_texture = "angular_velocity_texture";
- shader_names->orbit_velocity_texture = "orbit_velocity_texture";
+ shader_names->orbit_velocity_texture = "orbit_velocity_curve";
+ shader_names->radial_velocity_texture = "radial_velocity_curve";
shader_names->linear_accel_texture = "linear_accel_texture";
shader_names->radial_accel_texture = "radial_accel_texture";
shader_names->tangent_accel_texture = "tangent_accel_texture";
shader_names->damping_texture = "damping_texture";
- shader_names->scale_texture = "scale_texture";
+ shader_names->scale_texture = "scale_curve";
shader_names->hue_variation_texture = "hue_variation_texture";
shader_names->anim_speed_texture = "anim_speed_texture";
shader_names->anim_offset_texture = "anim_offset_texture";
+ shader_names->directional_velocity_texture = "directional_velocity_curve";
+ shader_names->scale_over_velocity_texture = "scale_over_velocity_curve";
shader_names->color = "color_value";
shader_names->color_ramp = "color_ramp";
+ shader_names->alpha_ramp = "alpha_curve";
+ shader_names->emission_ramp = "emission_curve";
shader_names->color_initial_ramp = "color_initial_ramp";
+ shader_names->velocity_limit_curve = "velocity_limit_curve";
+ shader_names->inherit_emitter_velocity_ratio = "inherit_emitter_velocity_ratio";
+ shader_names->velocity_pivot = "velocity_pivot";
shader_names->emission_sphere_radius = "emission_sphere_radius";
shader_names->emission_box_extents = "emission_box_extents";
@@ -98,6 +112,8 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->emission_ring_height = "emission_ring_height";
shader_names->emission_ring_radius = "emission_ring_radius";
shader_names->emission_ring_inner_radius = "emission_ring_inner_radius";
+ shader_names->emission_shape_offset = "emission_shape_offset";
+ shader_names->emission_shape_scale = "emission_shape_scale";
shader_names->turbulence_enabled = "turbulence_enabled";
shader_names->turbulence_noise_strength = "turbulence_noise_strength";
@@ -134,7 +150,7 @@ void ParticleProcessMaterial::_update_shader() {
dirty_materials->remove(&element);
MaterialKey mk = _compute_key();
- if (mk.key == current_key.key) {
+ if (mk == current_key) {
return; //no update required in the end
}
@@ -160,6 +176,7 @@ void ParticleProcessMaterial::_update_shader() {
String code = "// NOTE: Shader automatically converted from " VERSION_NAME " " VERSION_FULL_CONFIG "'s ParticleProcessMaterial.\n\n";
code += "shader_type particles;\n";
+ code += "render_mode disable_velocity;\n";
if (collision_scale) {
code += "render_mode collision_use_scale;\n";
@@ -168,32 +185,61 @@ void ParticleProcessMaterial::_update_shader() {
code += "uniform vec3 direction;\n";
code += "uniform float spread;\n";
code += "uniform float flatness;\n";
+
+ code += "uniform float inherit_emitter_velocity_ratio = 0;\n";
+
code += "uniform float initial_linear_velocity_min;\n";
- code += "uniform float initial_angle_min;\n";
+ code += "uniform float initial_linear_velocity_max;\n";
+
+ code += "uniform float directional_velocity_min;\n";
+ code += "uniform float directional_velocity_max;\n";
+
code += "uniform float angular_velocity_min;\n";
+ code += "uniform float angular_velocity_max;\n";
+
code += "uniform float orbit_velocity_min;\n";
+ code += "uniform float orbit_velocity_max;\n";
+
+ code += "uniform float radial_velocity_min;\n";
+ code += "uniform float radial_velocity_max;\n";
+
code += "uniform float linear_accel_min;\n";
+ code += "uniform float linear_accel_max;\n";
+
code += "uniform float radial_accel_min;\n";
+ code += "uniform float radial_accel_max;\n";
+
code += "uniform float tangent_accel_min;\n";
+ code += "uniform float tangent_accel_max;\n";
+
code += "uniform float damping_min;\n";
- code += "uniform float scale_min;\n";
- code += "uniform float hue_variation_min;\n";
- code += "uniform float anim_speed_min;\n";
- code += "uniform float anim_offset_min;\n";
+ code += "uniform float damping_max;\n";
- code += "uniform float initial_linear_velocity_max;\n";
+ code += "uniform float initial_angle_min;\n";
code += "uniform float initial_angle_max;\n";
- code += "uniform float angular_velocity_max;\n";
- code += "uniform float orbit_velocity_max;\n";
- code += "uniform float linear_accel_max;\n";
- code += "uniform float radial_accel_max;\n";
- code += "uniform float tangent_accel_max;\n";
- code += "uniform float damping_max;\n";
+
+ code += "uniform float scale_min;\n";
code += "uniform float scale_max;\n";
+
+ code += "uniform float hue_variation_min;\n";
code += "uniform float hue_variation_max;\n";
+
+ code += "uniform float anim_speed_min;\n";
code += "uniform float anim_speed_max;\n";
+
+ code += "uniform float anim_offset_min;\n";
code += "uniform float anim_offset_max;\n";
+
code += "uniform float lifetime_randomness;\n";
+ code += "uniform vec3 emission_shape_offset = vec3(0.);\n";
+ code += "uniform vec3 emission_shape_scale = vec3(1.);\n";
+
+ code += "uniform vec3 velocity_pivot = vec3(0.);\n";
+
+ if (tex_parameters[PARAM_SCALE_OVER_VELOCITY].is_valid()) {
+ code += "uniform float scale_over_velocity_min = 0.0;\n";
+ code += "uniform float scale_over_velocity_max = 5.0;\n";
+ }
switch (emission_shape) {
case EMISSION_SHAPE_POINT: {
@@ -254,12 +300,18 @@ void ParticleProcessMaterial::_update_shader() {
if (color_initial_ramp.is_valid()) {
code += "uniform sampler2D color_initial_ramp : repeat_disable;\n";
}
+ if (alpha_curve.is_valid()) {
+ code += "uniform sampler2D alpha_curve : repeat_disable;\n";
+ }
+ if (emission_curve.is_valid()) {
+ code += "uniform sampler2D emission_curve : repeat_disable;\n";
+ }
if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
code += "uniform sampler2D linear_velocity_texture : repeat_disable;\n";
}
if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
- code += "uniform sampler2D orbit_velocity_texture : repeat_disable;\n";
+ code += "uniform sampler2D orbit_velocity_curve : repeat_disable;\n";
}
if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
code += "uniform sampler2D angular_velocity_texture : repeat_disable;\n";
@@ -280,16 +332,28 @@ void ParticleProcessMaterial::_update_shader() {
code += "uniform sampler2D angle_texture : repeat_disable;\n";
}
if (tex_parameters[PARAM_SCALE].is_valid()) {
- code += "uniform sampler2D scale_texture : repeat_disable;\n";
+ code += "uniform sampler2D scale_curve : repeat_disable;\n";
}
if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) {
- code += "uniform sampler2D hue_variation_texture : repeat_disable;\n";
+ code += "uniform sampler2D hue_rot_curve : repeat_disable;\n";
}
if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) {
- code += "uniform sampler2D anim_speed_texture : repeat_disable;\n";
+ code += "uniform sampler2D animation_speed_curve : repeat_disable;\n";
}
if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) {
- code += "uniform sampler2D anim_offset_texture : repeat_disable;\n";
+ code += "uniform sampler2D animation_offset_curve : repeat_disable;\n";
+ }
+ if (tex_parameters[PARAM_RADIAL_VELOCITY].is_valid()) {
+ code += "uniform sampler2D radial_velocity_curve : repeat_disable;\n";
+ }
+ if (tex_parameters[PARAM_SCALE_OVER_VELOCITY].is_valid()) {
+ code += "uniform sampler2D scale_over_velocity_curve : repeat_disable;\n";
+ }
+ if (tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
+ code += "uniform sampler2D directional_velocity_curve: repeat_disable;\n";
+ }
+ if (velocity_limit_curve.is_valid()) {
+ code += "uniform sampler2D velocity_limit_curve: repeat_disable;\n";
}
if (collision_mode == COLLISION_RIGID) {
@@ -390,6 +454,23 @@ void ParticleProcessMaterial::_update_shader() {
code += " return noise_direction;\n";
code += "}\n";
}
+ code += "vec4 rotate_hue(vec4 current_color, float hue_rot_angle){\n";
+ code += " float hue_rot_c = cos(hue_rot_angle);\n";
+ code += " float hue_rot_s = sin(hue_rot_angle);\n";
+ code += " mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0),\n";
+ code += " vec4(0.299, 0.587, 0.114, 0.0),\n";
+ code += " vec4(0.299, 0.587, 0.114, 0.0),\n";
+ code += " vec4(0.000, 0.000, 0.000, 1.0)) +\n";
+ code += " mat4(vec4(0.701, -0.587, -0.114, 0.0),\n";
+ code += " vec4(-0.299, 0.413, -0.114, 0.0),\n";
+ code += " vec4(-0.300, -0.588, 0.886, 0.0),\n";
+ code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_c +\n";
+ code += " mat4(vec4(0.168, 0.330, -0.497, 0.0),\n";
+ code += " vec4(-0.328, 0.035, 0.292, 0.0),\n";
+ code += " vec4(1.250, -1.050, -0.203, 0.0),\n";
+ code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s;\n";
+ code += " return hue_rot_mat * current_color;\n";
+ code += "}\n";
//need a random function
code += "\n\n";
@@ -421,398 +502,528 @@ void ParticleProcessMaterial::_update_shader() {
code += "}\n";
code += "\n";
- code += "void start() {\n";
- code += " uint base_number = NUMBER;\n";
- code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
- code += " float angle_rand = rand_from_seed(alt_seed);\n";
- code += " float scale_rand = rand_from_seed(alt_seed);\n";
- code += " float hue_rot_rand = rand_from_seed(alt_seed);\n";
- code += " float anim_offset_rand = rand_from_seed(alt_seed);\n";
- if (color_initial_ramp.is_valid()) {
- code += " float color_initial_rand = rand_from_seed(alt_seed);\n";
+ code += "struct DisplayParameters{\n";
+ code += " vec3 scale;\n";
+ code += " float hue_rotation;\n";
+ code += " float animation_speed;\n";
+ code += " float animation_offset;\n";
+ code += " float lifetime;\n";
+ code += " vec4 color;\n";
+ code += "};\n";
+ code += "\n";
+ code += "struct DynamicsParameters{\n";
+ code += " float angle;\n";
+ code += " float angular_velocity;\n";
+ code += " float initial_velocity_multiplier;\n";
+ code += " float directional_velocity;\n";
+ code += " float radial_velocity;\n";
+ code += " float orbit_velocity;\n";
+ if (turbulence_enabled) {
+ code += " float turb_influence;\n";
+ }
+ code += "};\n";
+ code += "struct PhysicalParameters{\n";
+ code += " float linear_accel;\n";
+ code += " float radial_accel;\n";
+ code += " float tangent_accel;\n";
+ code += " float damping;\n";
+ code += "};\n";
+
+ code += "\n";
+ code += "void calculate_initial_physical_params(inout PhysicalParameters params, inout uint alt_seed){\n";
+ code += " params.linear_accel = mix(linear_accel_min, linear_accel_min ,rand_from_seed(alt_seed));\n";
+ code += " params.radial_accel = mix(radial_accel_min, radial_accel_min,rand_from_seed(alt_seed));\n";
+ code += " params.tangent_accel = mix(tangent_accel_min, tangent_accel_max,rand_from_seed(alt_seed));\n";
+ code += " params.damping = mix(damping_min, damping_max,rand_from_seed(alt_seed));\n";
+ code += "}\n";
+ code += "\n";
+ code += "void calculate_initial_dynamics_params(inout DynamicsParameters params,inout uint alt_seed){\n";
+ code += " // -------------------- DO NOT REORDER OPERATIONS, IT BREAKS VISUAL COMPATIBILITY\n";
+ code += " // -------------------- ADD NEW OPERATIONS AT THE BOTTOM\n";
+ code += " params.angle = mix(initial_angle_min, initial_angle_max, rand_from_seed(alt_seed));\n";
+ code += " params.angular_velocity = mix(angular_velocity_min, angular_velocity_max, rand_from_seed(alt_seed));\n";
+ code += " params.initial_velocity_multiplier = mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));\n";
+ code += " params.directional_velocity = mix(directional_velocity_min, directional_velocity_max,rand_from_seed(alt_seed));\n";
+ code += " params.radial_velocity = mix(radial_velocity_min, radial_velocity_max,rand_from_seed(alt_seed));\n";
+ code += " params.orbit_velocity = mix(orbit_velocity_min, orbit_velocity_max,rand_from_seed(alt_seed));\n";
+ if (turbulence_enabled) {
+ code += " params.turb_influence = mix(turbulence_influence_min,turbulence_influence_max,rand_from_seed(alt_seed));\n";
}
+ code += "}\n";
+ code += "void calculate_initial_display_params(inout DisplayParameters params,inout uint alt_seed){\n";
+ code += " // -------------------- DO NOT REORDER OPERATIONS, IT BREAKS VISUAL COMPATIBILITY\n";
+ code += " // -------------------- ADD NEW OPERATIONS AT THE BOTTOM\n";
code += " float pi = 3.14159;\n";
code += " float degree_to_rad = pi / 180.0;\n";
- code += "\n";
- if (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
+ code += " params.scale = vec3(mix(scale_min, scale_max, rand_from_seed(alt_seed)));\n";
+ code += " params.scale = sign(params.scale) * max(abs(params.scale), 0.001);\n";
+ code += " params.hue_rotation = pi * 2.0 * mix(hue_variation_min, hue_variation_max, rand_from_seed(alt_seed));\n";
+ code += " params.animation_speed = mix(anim_speed_min, anim_speed_max, rand_from_seed(alt_seed));\n";
+ code += " params.animation_offset = mix(anim_offset_min, anim_offset_max, rand_from_seed(alt_seed));\n";
+ code += " params.lifetime = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n";
+ code += " params.color = color_value;\n";
+ if (color_initial_ramp.is_valid()) {
+ code += " params.color = texture(color_initial_ramp, vec2(rand_from_seed(alt_seed)));\n";
+ }
+ if (emission_color_texture.is_valid() && (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS)) {
code += " int point = min(emission_texture_point_count - 1, int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n";
code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n";
code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n";
+ code += " parameters.color *= texelFetch(emission_texture_color, emission_tex_ofs, 0);\n";
}
- if (tex_parameters[PARAM_ANGLE].is_valid()) {
- code += " float tex_angle = textureLod(angle_texture, vec2(0.0, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_angle = 1.0;\n";
- }
+ code += "}\n";
- if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) {
- code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(0.0, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_anim_offset = 1.0;\n";
+ // process display parameters that are bound solely by lifetime
+ code += "void process_display_param(inout DisplayParameters parameters, float lifetime){\n";
+ code += " // compile-time add textures\n";
+ if (tex_parameters[PARAM_SCALE].is_valid()) {
+ code += " parameters.scale *= texture(scale_curve, vec2(lifetime)).rgb;\n";
}
-
- code += " float spread_rad = spread * degree_to_rad;\n";
-
- code += " if (RESTART_VELOCITY) {\n";
-
- if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
- code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(0.0, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_linear_velocity = 1.0;\n";
+ if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) {
+ code += " parameters.hue_rotation *= texture(hue_rot_curve, vec2(lifetime)).r;\n";
}
-
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " {\n";
- code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
- code += " angle1_rad += direction.x != 0.0 ? atan(direction.y, direction.x) : sign(direction.y) * (pi / 2.0);\n";
- code += " vec3 rot = vec3(cos(angle1_rad), sin(angle1_rad), 0.0);\n";
- code += " VELOCITY = rot * mix(initial_linear_velocity_min,initial_linear_velocity_max, rand_from_seed(alt_seed));\n";
- code += " }\n";
-
- } else {
- //initiate velocity spread in 3D
- code += " {\n";
- code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
- code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
- code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
- code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
- code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
- code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
- code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n";
- code += " // rotate spread to direction\n";
- code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n";
- code += " if (length(binormal) < 0.0001) {\n";
- code += " // direction is parallel to Y. Choose Z as the binormal.\n";
- code += " binormal = vec3(0.0, 0.0, 1.0);\n";
- code += " }\n";
- code += " binormal = normalize(binormal);\n";
- code += " vec3 normal = cross(binormal, direction_nrm);\n";
- code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n";
- code += " VELOCITY = spread_direction * mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));\n";
- code += " }\n";
+ if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) {
+ code += " parameters.animation_offset += texture(animation_offset_curve, vec2(lifetime)).r;\n";
}
- code += " }\n";
-
- code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, angle_rand);\n";
- code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle
- code += " CUSTOM.y = 0.0;\n"; // phase
- code += " CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n";
- code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n\n"; // animation offset (0-1)
-
- code += " if (RESTART_ROT_SCALE) {\n";
- code += " TRANSFORM[0].xyz = vec3(1.0, 0.0, 0.0);\n";
- code += " TRANSFORM[1].xyz = vec3(0.0, 1.0, 0.0);\n";
- code += " TRANSFORM[2].xyz = vec3(0.0, 0.0, 1.0);\n";
- code += " }\n\n";
-
- code += " if (RESTART_POSITION) {\n";
-
- switch (emission_shape) {
- case EMISSION_SHAPE_POINT: {
- //do none, identity (will later be multiplied by emission transform)
- code += " TRANSFORM[3].xyz = vec3(0.0, 0.0, 0.0);\n";
- } break;
- case EMISSION_SHAPE_SPHERE: {
- code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
- code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float p = rand_from_seed(alt_seed);\n";
- code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
- code += " TRANSFORM[3].xyz = mix(vec3(0.0, 0.0, 0.0), vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s), p);\n";
- } break;
- case EMISSION_SHAPE_SPHERE_SURFACE: {
- code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
- code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
- code += " TRANSFORM[3].xyz = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n";
- } break;
- case EMISSION_SHAPE_BOX: {
- code += " TRANSFORM[3].xyz = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n";
- } break;
- case EMISSION_SHAPE_POINTS:
- case EMISSION_SHAPE_DIRECTED_POINTS: {
- code += " TRANSFORM[3].xyz = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
-
- if (emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " {\n";
- code += " mat2 rotm;";
- code += " rotm[0] = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xy;\n";
- code += " rotm[1] = rotm[0].yx * vec2(1.0, -1.0);\n";
- code += " if (RESTART_VELOCITY) VELOCITY.xy = rotm * VELOCITY.xy;\n";
- code += " }\n";
- } else {
- code += " {\n";
- code += " vec3 normal = texelFetch(emission_texture_normal, emission_tex_ofs, 0).xyz;\n";
- code += " vec3 v0 = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(0.0, 1.0, 0.0);\n";
- code += " vec3 tangent = normalize(cross(v0, normal));\n";
- code += " vec3 bitangent = normalize(cross(tangent, normal));\n";
- code += " if (RESTART_VELOCITY) VELOCITY = mat3(tangent, bitangent, normal) * VELOCITY;\n";
- code += " }\n";
- }
- }
- } break;
- case EMISSION_SHAPE_RING: {
- code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n";
- code += " vec3 axis = normalize(emission_ring_axis);\n";
- code += " vec3 ortho_axis = vec3(0.0);\n";
- code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n";
- code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n";
- code += " } else {\n";
- code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n";
- code += " }\n";
- code += " ortho_axis = normalize(ortho_axis);\n";
- code += " float s = sin(ring_spawn_angle);\n";
- code += " float c = cos(ring_spawn_angle);\n";
- code += " float oc = 1.0 - c;\n";
- code += " ortho_axis = mat3(\n";
- code += " vec3(c + axis.x * axis.x * oc, axis.x * axis.y * oc - axis.z * s, axis.x * axis.z *oc + axis.y * s),\n";
- code += " vec3(axis.x * axis.y * oc + s * axis.z, c + axis.y * axis.y * oc, axis.y * axis.z * oc - axis.x * s),\n";
- code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n";
- code += " ) * ortho_axis;\n";
- code += " ortho_axis = normalize(ortho_axis);\n";
- code += " TRANSFORM[3].xyz = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
- } break;
- case EMISSION_SHAPE_MAX: { // Max value for validity check.
- break;
- }
+ if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) {
+ code += " parameters.animation_speed *= texture(animation_speed_curve, vec2(lifetime)).r;\n";
}
- code += " if (RESTART_VELOCITY) VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
- // Apply noise/turbulence: initial displacement.
- if (turbulence_enabled) {
- code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz);\n";
- code += " float turb_init_displacement = mix(turbulence_initial_displacement_min, turbulence_initial_displacement_max, rand_from_seed(alt_seed));";
- code += " TRANSFORM[3].xyz += noise_direction * turb_init_displacement;\n";
+ if (color_ramp.is_valid()) {
+ code += " parameters.color *= texture(color_ramp, vec2(lifetime));\n";
}
- code += " TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " VELOCITY.z = 0.0;\n";
- code += " TRANSFORM[3].z = 0.0;\n";
+ if (alpha_curve.is_valid()) {
+ code += " parameters.color.a *= texture(alpha_curve, vec2(lifetime)).r;\n";
}
- code += " }\n";
- code += "}\n\n";
-
- code += "void process() {\n";
- code += " uint base_number = NUMBER;\n";
- code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
- code += " float angle_rand = rand_from_seed(alt_seed);\n";
- code += " float scale_rand = rand_from_seed(alt_seed);\n";
- code += " float hue_rot_rand = rand_from_seed(alt_seed);\n";
- code += " float anim_offset_rand = rand_from_seed(alt_seed);\n";
- if (color_initial_ramp.is_valid()) {
- code += " float color_initial_rand = rand_from_seed(alt_seed);\n";
+ code += " parameters.color = rotate_hue(parameters.color, parameters.hue_rotation);\n";
+ if (emission_curve.is_valid()) {
+ code += " parameters.color.rgb *= 1.0 + texture(emission_curve, vec2(lifetime)).r;\n";
}
+ code += "}\n";
+
+ code += "vec3 calculate_initial_position(inout uint alt_seed) {\n";
code += " float pi = 3.14159;\n";
code += " float degree_to_rad = pi / 180.0;\n";
- code += "\n";
-
- if (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
- code += " int point = min(emission_texture_point_count - 1, int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n";
- code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n";
- code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n";
+ code += " vec3 pos = vec3(0.);\n";
+ if (emission_shape == EMISSION_SHAPE_POINT) {
+ code += " pos = vec3(0.);\n";
}
-
- code += " CUSTOM.y += DELTA / LIFETIME;\n";
- code += " float tv = CUSTOM.y / CUSTOM.w;\n";
- if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
- code += " float tex_linear_velocity = textureLod(linear_velocity_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_linear_velocity = 1.0;\n";
+ if (emission_shape == EMISSION_SHAPE_SPHERE) {
+ code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
+ code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
+ code += " float p = rand_from_seed(alt_seed);\n";
+ code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
+ code += " pos = mix(vec3(0.0, 0.0, 0.0), vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s), p);\n";
}
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
- code += " float tex_orbit_velocity = textureLod(orbit_velocity_texture, vec2(tv, 0.0), 0.0).r;\n";
+ if (emission_shape == EMISSION_SHAPE_SPHERE_SURFACE) {
+ code += " float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";
+ code += " float t = rand_from_seed(alt_seed) * 2.0 * pi;\n";
+ code += " float radius = emission_sphere_radius * sqrt(1.0 - s * s);\n";
+ code += " pos = vec3(radius * cos(t), radius * sin(t), emission_sphere_radius * s);\n";
+ }
+ if (emission_shape == EMISSION_SHAPE_BOX) {
+ code += " pos = vec3(rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0, rand_from_seed(alt_seed) * 2.0 - 1.0) * emission_box_extents;\n";
+ }
+ if (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS) {
+ code += " int point = min(emission_texture_point_count - 1, int(rand_from_seed(alt_seed) * float(emission_texture_point_count)));\n";
+ code += " ivec2 emission_tex_size = textureSize(emission_texture_points, 0);\n";
+ code += " ivec2 emission_tex_ofs = ivec2(point % emission_tex_size.x, point / emission_tex_size.x);\n";
+ code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
+ }
+ if (emission_shape == EMISSION_SHAPE_RING) {
+ code += " \n";
+ code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
+ code += " float ring_random_radius = rand_from_seed(alt_seed) * (emission_ring_radius - emission_ring_inner_radius) + emission_ring_inner_radius;\n";
+ code += " vec3 axis = normalize(emission_ring_axis);\n";
+ code += " vec3 ortho_axis = vec3(0.0);\n";
+ code += " if (axis == vec3(1.0, 0.0, 0.0)) {\n";
+ code += " ortho_axis = cross(axis, vec3(0.0, 1.0, 0.0));\n";
+ code += " } else {\n";
+ code += " ortho_axis = cross(axis, vec3(1.0, 0.0, 0.0));\n";
+ code += " }\n";
+ code += " ortho_axis = normalize(ortho_axis);\n";
+ code += " float s = sin(ring_spawn_angle);\n";
+ code += " float c = cos(ring_spawn_angle);\n";
+ code += " float oc = 1.0 - c;\n";
+ code += " ortho_axis = mat3(\n";
+ code += " vec3(c + axis.x * axis.x * oc, axis.x * axis.y * oc - axis.z * s, axis.x * axis.z *oc + axis.y * s),\n";
+ code += " vec3(axis.x * axis.y * oc + s * axis.z, c + axis.y * axis.y * oc, axis.y * axis.z * oc - axis.x * s),\n";
+ code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n";
+ code += " ) * ortho_axis;\n";
+ code += " ortho_axis = normalize(ortho_axis);\n";
+ code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
+ }
+
+ code += " return pos * emission_shape_scale + emission_shape_offset;\n";
+ code += "}\n";
+ code += "\n";
+ if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid() || particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += "vec3 process_orbit_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform,float delta, float total_lifetime){\n";
+ // No reason to run all these expensive calculation below if we have no orbit velocity
+ // HOWEVER
+ // May be a bad idea for fps consistency?
+ code += "if(abs(param.orbit_velocity) < 0.01){ return vec3(0.0);}\n";
+ code += "\n";
+ code += " vec3 displacement = vec3(0.);\n";
+ code += " float pi = 3.14159;\n";
+ code += " float degree_to_rad = pi / 180.0;\n";
+ if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += " float orbit_amount = param.orbit_velocity;\n";
+
+ if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid()) {
+ code += " orbit_amount *= texture(orbit_velocity_curve, vec2(lifetime)).r;\n";
+ }
+ code += " if (orbit_amount != 0.0) {\n";
+ code += " vec3 pos = transform[3].xyz;\n";
+ code += " vec3 org = emission_transform[3].xyz;\n";
+ code += " vec3 diff = pos - org;\n";
+ code += " float ang = orbit_amount * delta * pi * 2.0;\n";
+ code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n";
+ code += " displacement.xy -= diff.xy;\n";
+ code += " displacement.xy += rot * diff.xy;\n";
+ code += " }\n";
} else {
- code += " float tex_orbit_velocity = 1.0;\n";
+ code += " vec3 orbit_velocities = vec3(param.orbit_velocity);\n";
+ code += " orbit_velocities *= texture(orbit_velocity_curve, vec2(lifetime)).rgb;\n";
+
+ code += " orbit_velocities *= degree_to_rad;\n";
+ code += " orbit_velocities *= delta/total_lifetime; // we wanna process those by the delta angle\n";
+ code += " //vec3 local_velocity_pivot = ((emission_transform) * vec4(velocity_pivot,1.0)).xyz;\n";
+ code += " // X axis\n";
+ code += " vec3 local_pos = (inverse(emission_transform) * transform[3]).xyz;\n";
+ code += " local_pos -= velocity_pivot;\n";
+ code += " local_pos.x = 0.;\n";
+ code += " mat3 x_rotation_mat = mat3(\n";
+ code += " vec3(1.0,0.0,0.0),\n";
+ code += " vec3(0.0, cos(orbit_velocities.x), sin(orbit_velocities.x)),\n";
+ code += " vec3(0.0, -sin(orbit_velocities.x), cos(orbit_velocities.x))\n";
+ code += " );\n";
+ code += " vec3 new_pos = x_rotation_mat * local_pos;\n";
+ code += " displacement = new_pos - local_pos;\n";
+ code += "\n";
+ code += " // Y axis\n";
+ code += " local_pos = (inverse(emission_transform) * transform[3]).xyz;\n";
+ code += " local_pos -= velocity_pivot;\n";
+ code += " local_pos.y = 0.;\n";
+ code += " mat3 y_rotation_mat = mat3(\n";
+ code += " vec3(cos(orbit_velocities.y), 0.0, -sin(orbit_velocities.y)),\n";
+ code += " vec3(0.0, 1.0,0.0),\n";
+ code += " vec3(sin(orbit_velocities.y), 0.0, cos(orbit_velocities.y))\n";
+ code += " );\n";
+ code += " new_pos = y_rotation_mat * local_pos;\n";
+ code += " displacement += new_pos - local_pos;\n";
+ code += " // z axis\n";
+ code += "\n";
+ code += " local_pos = (inverse(emission_transform) * transform[3]).xyz;\n";
+ code += " local_pos -= velocity_pivot;\n";
+ code += " local_pos.z = 0.;\n";
+ code += " mat3 z_rotation_mat = mat3(\n";
+ code += " vec3(cos(orbit_velocities.z),-sin(orbit_velocities.z),0.0),\n";
+ code += " vec3(-sin(orbit_velocities.z),cos(orbit_velocities.z), 0.0),\n";
+ code += " vec3(0.0,0.0,1.0)\n";
+ code += " );\n";
+ code += " new_pos = z_rotation_mat * local_pos;\n";
+ code += " displacement += new_pos - local_pos;\n";
+ code += "\n";
}
+ code += " return (emission_transform * vec4(displacement, 0.0)).xyz;\n";
+ code += "}\n";
+ code += "\n";
+ code += "\n";
}
- if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
- code += " float tex_angular_velocity = textureLod(angular_velocity_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_angular_velocity = 1.0;\n";
+ code += "vec3 get_random_direction_from_spread(inout uint alt_seed, float spread_angle){\n";
+ code += " float pi = 3.14159;\n";
+ code += " float degree_to_rad = pi / 180.0;\n";
+ code += " vec3 velocity = vec3(0.);\n";
+ code += " float spread_rad = spread_angle * degree_to_rad;\n";
+ code += " float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;\n";
+ code += " float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0 - flatness);\n";
+ code += " vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));\n";
+ code += " vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));\n";
+ code += " direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution\n";
+ code += " vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);\n";
+ code += " vec3 direction_nrm = length(direction) > 0.0 ? normalize(direction) : vec3(0.0, 0.0, 1.0);\n";
+ code += " // rotate spread to direction\n";
+ code += " vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);\n";
+ code += " if (length(binormal) < 0.0001) {\n";
+ code += " // direction is parallel to Y. Choose Z as the binormal.\n";
+ code += " binormal = vec3(0.0, 0.0, 1.0);\n";
+ code += " }\n";
+ code += " binormal = normalize(binormal);\n";
+ code += " vec3 normal = cross(binormal, direction_nrm);\n";
+ code += " spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;\n";
+ code += " return spread_direction;\n";
+
+ code += "}\n";
+
+ code += "vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){\n";
+ code += " vec3 radial_displacement = vec3(0.0);\n";
+ code += " float radial_displacement_multiplier = 1.0;\n";
+ if (tex_parameters[PARAM_RADIAL_VELOCITY].is_valid()) {
+ code += " radial_displacement_multiplier = texture(radial_velocity_curve, vec2(lifetime)).r;\n";
+ }
+ code += " vec3 global_pivot = (emission_transform * vec4(velocity_pivot, 1.0)).xyz;\n";
+ code += " if(length(transform[3].xyz - global_pivot) > 0.01){\n";
+ code += " radial_displacement = normalize(transform[3].xyz - global_pivot) * radial_displacement_multiplier * param.radial_velocity;\n";
+ code += " }else{radial_displacement = get_random_direction_from_spread(alt_seed, 360.0)* param.radial_velocity;} \n";
+ code += " if (radial_displacement_multiplier * param.radial_velocity < 0.0){\n // Prevent inwards velocity to flicker once the point is reached.";
+ code += " if (length(radial_displacement) > 0.01){\n";
+ code += " radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz - global_pivot));\n";
+ code += " }\n";
+ code += " \n";
+ code += " return radial_displacement * delta;\n";
+ code += "}\n";
+ if (tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
+ code += "vec3 process_directional_displacement(DynamicsParameters param, float lifetime_percent,mat4 transform, mat4 emission_transform, float delta){\n";
+ code += " vec3 displacement = vec3(0.);\n";
+ if (directional_velocity_global) {
+ code += " displacement = texture(directional_velocity_curve, vec2(lifetime_percent)).xyz * param.directional_velocity;\n";
+ code += " displacement = (emission_transform * vec4(displacement, 0.0)).xyz;\n";
+ } else {
+ code += " displacement = texture(directional_velocity_curve, vec2(lifetime_percent)).xyz * param.directional_velocity;\n";
+ }
+ code += " return displacement * delta;\n";
+ code += "}\n";
}
+ code += "\n";
+ code += "void process_physical_parameters(inout PhysicalParameters params, float lifetime_percent){\n";
if (tex_parameters[PARAM_LINEAR_ACCEL].is_valid()) {
- code += " float tex_linear_accel = textureLod(linear_accel_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_linear_accel = 1.0;\n";
+ code += " params.linear_accel *= texture(linear_accel_texture, vec2(lifetime_percent)).r;\n";
}
-
if (tex_parameters[PARAM_RADIAL_ACCEL].is_valid()) {
- code += " float tex_radial_accel = textureLod(radial_accel_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_radial_accel = 1.0;\n";
+ code += " params.radial_accel *= texture(radial_accel_texture, vec2(lifetime_percent)).r;\n";
}
-
if (tex_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) {
- code += " float tex_tangent_accel = textureLod(tangent_accel_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_tangent_accel = 1.0;\n";
+ code += " params.tangent_accel *= texture(tangent_accel_texture, vec2(lifetime_percent)).r;\n";
}
-
if (tex_parameters[PARAM_DAMPING].is_valid()) {
- code += " float tex_damping = textureLod(damping_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_damping = 1.0;\n";
+ code += " params.damping *= texture(damping_texture, vec2(lifetime_percent)).r;\n";
}
+ code += " \n";
+ code += "}\n";
+ code += "\n";
- if (tex_parameters[PARAM_ANGLE].is_valid()) {
- code += " float tex_angle = textureLod(angle_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_angle = 1.0;\n";
+ code += "void start() {\n";
+ code += " uint base_number = NUMBER;\n";
+ code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
+ code += " if (rand_from_seed(alt_seed) > AMOUNT_RATIO) {\n";
+ code += " ACTIVE = false;\n";
+ code += " }\n";
+ code += " DisplayParameters params;\n";
+ code += " calculate_initial_display_params(params, alt_seed);\n";
+ code += " // reset alt seed?\n";
+ code += " // alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
+ code += " DynamicsParameters dynamic_params;\n";
+ code += " calculate_initial_dynamics_params(dynamic_params, alt_seed);\n";
+ code += " PhysicalParameters physics_params;\n";
+ code += " calculate_initial_physical_params(physics_params, alt_seed);\n";
+ code += " process_display_param(params, 0.0);\n";
+ code += " \n";
+ code += " float pi = 3.14159;\n";
+ code += " float degree_to_rad = pi / 180.0;\n";
+ code += " \n";
+ code += " if (RESTART_CUSTOM){\n";
+ code += " CUSTOM = vec4(0.);\n";
+ code += " CUSTOM.w = params.lifetime;\n";
+ code += " CUSTOM.x = dynamic_params.angle;\n";
+ code += " }\n";
+ code += " if (RESTART_COLOR){\n";
+ code += " COLOR = params.color;\n";
+ code += " }\n";
+ code += " if (RESTART_ROT_SCALE) {\n";
+ code += " TRANSFORM[0].xyz = vec3(1.0, 0.0, 0.0);\n";
+ code += " TRANSFORM[1].xyz = vec3(0.0, 1.0, 0.0);\n";
+ code += " TRANSFORM[2].xyz = vec3(0.0, 0.0, 1.0);\n";
+ code += " }\n";
+ code += "\n";
+ code += " if (RESTART_POSITION) {\n";
+ code += " TRANSFORM[3].xyz = calculate_initial_position(alt_seed);\n";
+ if (turbulence_enabled) {
+ code += " float initial_turbulence_displacement = mix(turbulence_initial_displacement_min, turbulence_initial_displacement_max, rand_from_seed(alt_seed));\n";
+ code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz);\n";
+ code += " TRANSFORM[3].xyz += noise_direction * initial_turbulence_displacement;\n";
}
-
- if (tex_parameters[PARAM_ANIM_SPEED].is_valid()) {
- code += " float tex_anim_speed = textureLod(anim_speed_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_anim_speed = 1.0;\n";
+ code += " TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;\n";
+ code += " }\n";
+ code += " if (RESTART_VELOCITY) {\n";
+ code += " VELOCITY = get_random_direction_from_spread(alt_seed, spread) * dynamic_params.initial_velocity_multiplier;\n";
+ code += " }\n";
+ code += " process_display_param(params, 0.);\n";
+ code += "// process_dynamic_parameters(dynamic_params, 0., alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
+ code += " VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;\n";
+ code += " VELOCITY += EMITTER_VELOCITY * inherit_emitter_velocity_ratio;\n";
+ if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += " VELOCITY.z = 0.;\n";
+ code += " TRANSFORM[3].z = 0.;\n";
}
+ code += "}\n";
+ code += "\n";
- if (tex_parameters[PARAM_ANIM_OFFSET].is_valid()) {
- code += " float tex_anim_offset = textureLod(anim_offset_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_anim_offset = 1.0;\n";
+ code += "void process() {\n";
+ code += " uint base_number = NUMBER;\n";
+ // TODO add optional determinism here
+ code += "// if (repeatable){\n";
+ code += "// base_number = INDEX;\n";
+ code += "// }\n";
+ code += " uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);\n";
+ code += " DisplayParameters params;\n";
+ code += " calculate_initial_display_params(params, alt_seed);\n";
+ code += " DynamicsParameters dynamic_params;\n";
+ code += " calculate_initial_dynamics_params(dynamic_params, alt_seed);\n";
+ code += " PhysicalParameters physics_params;\n";
+ code += " calculate_initial_physical_params(physics_params, alt_seed);\n";
+ code += " float pi = 3.14159;\n";
+ code += " float degree_to_rad = pi / 180.0;\n";
+ code += "\n";
+ code += " CUSTOM.y += DELTA / LIFETIME;\n";
+ code += " CUSTOM.y = mix(CUSTOM.y, 1.0, INTERPOLATE_TO_END);\n";
+ code += " float lifetime_percent = CUSTOM.y/ params.lifetime;\n";
+ code += " if (CUSTOM.y > CUSTOM.w) {\n";
+ code += " ACTIVE = false;\n";
+ code += " }\n";
+ code += " \n";
+ code += " \n";
+ code += " \n";
+ code += " // will use this later to calculate final displacement and orient the particle.\n";
+ code += " vec3 starting_position = TRANSFORM[3].xyz;\n";
+ code += " vec3 controlled_displacement = vec3(0.0);\n";
+ code += " \n";
+ code += "// VELOCITY += process_physics_parameters(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
+ code += " \n";
+ if (tex_parameters[PARAM_ORBIT_VELOCITY].is_valid() || particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += " controlled_displacement += process_orbit_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA, params.lifetime * LIFETIME);\n";
}
-
- code += " vec3 force = gravity;\n";
- code += " vec3 pos = TRANSFORM[3].xyz;\n";
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " pos.z = 0.0;\n";
- }
- code += " // apply linear acceleration\n";
- code += " float linear_accel_rand = rand_from_seed(alt_seed);\n";
- code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, linear_accel_rand) : vec3(0.0);\n";
- code += " // apply radial acceleration\n";
- code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n";
- code += " vec3 diff = pos - org;\n";
- code += " float radial_accel_rand = rand_from_seed(alt_seed);\n";
- code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, radial_accel_rand) : vec3(0.0);\n";
- code += " // apply tangential acceleration;\n";
- code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));\n";
+ code += " // calculate all velocity\n";
+ code += " \n";
+ code += " controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
+ code += " \n";
+ if (tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
+ code += " controlled_displacement += process_directional_displacement(dynamic_params, lifetime_percent, TRANSFORM, EMISSION_TRANSFORM, DELTA);\n";
+ }
+ code += " \n";
+ code += " process_physical_parameters(physics_params, lifetime_percent);\n";
+ code += " vec3 force;\n";
+ code += " {\n";
+ code += " // copied from previous version\n";
+ code += " vec3 pos = TRANSFORM[3].xyz;\n";
+ code += " force = gravity;\n";
+ code += " // apply linear acceleration\n";
+ code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * physics_params.linear_accel : vec3(0.0);\n";
+ code += " // apply radial acceleration\n";
+ code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n";
+ code += " vec3 diff = pos - org;\n";
+ code += " force += length(diff) > 0.0 ? normalize(diff) * physics_params.radial_accel : vec3(0.0);\n";
+ code += " // apply tangential acceleration;\n";
+ code += " float tangent_accel_val = physics_params.tangent_accel;\n";
if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n";
-
+ code += " force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);\n";
} else {
- code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n";
- code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * tangent_accel_val : vec3(0.0);\n";
+ code += " vec3 crossDiff = cross(normalize(diff), normalize(gravity));\n";
+ code += " force += length(crossDiff) > 0.0 ? normalize(crossDiff) * tangent_accel_val : vec3(0.0);\n";
}
if (attractor_interaction_enabled) {
- code += " force += ATTRACTOR_FORCE;\n\n";
+ code += " force += ATTRACTOR_FORCE;\n";
}
-
- code += " // apply attractor forces\n";
- code += " VELOCITY += force * DELTA;\n";
-
- if (tex_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) {
- code += " VELOCITY = normalize(VELOCITY) * tex_linear_velocity;\n";
+ code += "\n";
+ code += " // apply attractor forces\n";
+ if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += " force.z = 0.;\n";
}
-
- // Apply noise/turbulence.
+ code += " VELOCITY += force * DELTA;\n";
+ code += " }\n";
+ code += " {\n";
+ code += " // copied from previous version\n";
+ code += " if (physics_params.damping > 0.0) {\n";
+ if (!particle_flags[PARTICLE_FLAG_DAMPING_AS_FRICTION]) {
+ code += " float v = length(VELOCITY);\n";
+ code += " v -= physics_params.damping * DELTA;\n";
+ code += " if (v < 0.0) {\n";
+ code += " VELOCITY = vec3(0.0);\n";
+ code += " } else {\n";
+ code += " VELOCITY = normalize(VELOCITY) * v;\n";
+ code += " }\n";
+ } else {
+ code += " if (length(VELOCITY) > 0.01){\n";
+ code += " VELOCITY -= normalize(VELOCITY) * length(VELOCITY) * (physics_params.damping) * DELTA;\n";
+ code += " }\n";
+ }
+ code += " }\n";
+ code += " \n";
+ code += " }\n";
+ code += " \n";
+ if (collision_mode == COLLISION_RIGID) {
+ code += " if (COLLIDED) {\n";
+ code += " if (length(VELOCITY) > 3.0) {\n";
+ code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
+ code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
+ code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n";
+ code += " } else {\n";
+ code += " VELOCITY = vec3(0.0);\n";
+ // If turbulence is enabled, set the noise direction to up so the turbulence color is "neutral"
+ if (turbulence_enabled) {
+ code += " noise_direction = vec3(1.0, 0.0, 0.0);\n";
+ }
+ code += " }\n";
+ code += " }\n";
+ } else if (collision_mode == COLLISION_HIDE_ON_CONTACT) {
+ code += " if (COLLIDED) {\n";
+ code += " ACTIVE = false;\n";
+ code += " }\n";
+ }
+ code += " vec3 final_velocity = controlled_displacement/DELTA + VELOCITY;\n";
+ code += " \n";
+ code += " // turbulence before limiting\n";
if (turbulence_enabled) {
- code += " // apply turbulence\n";
if (tex_parameters[PARAM_TURB_INFLUENCE_OVER_LIFE].is_valid()) {
- code += " float turbulence_influence = textureLod(turbulence_influence_over_life, vec2(tv, 0.0), 0.0).r;\n";
+ code += " float turbulence_influence = textureLod(turbulence_influence_over_life, vec2(lifetime_percent, 0.0), 0.0).r;\n";
} else {
- code += " const float turbulence_influence = 1.0;\n";
- }
- code += " \n";
- code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz);\n";
- // If collision happened, turbulence is no longer applied.
- // We don't need this check when the collision mode is "hide on contact",
- // as the particle will be hidden anyway.
- String extra_tab = "";
- if (collision_mode != COLLISION_RIGID) {
- code += " if (!COLLIDED) {\n";
- extra_tab = " ";
- }
- code += extra_tab + " \n";
- code += extra_tab + " float vel_mag = length(VELOCITY);\n";
- code += extra_tab + " float vel_infl = clamp(mix(turbulence_influence_min, turbulence_influence_max, rand_from_seed(alt_seed)) * turbulence_influence, 0.0, 1.0);\n";
- code += extra_tab + " VELOCITY = mix(VELOCITY, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n";
- if (collision_mode != COLLISION_RIGID) {
- code += " }";
+ code += " float turbulence_influence = 1.0;\n";
}
+ code += " \n";
+ code += " vec3 noise_direction = get_noise_direction(TRANSFORM[3].xyz);\n";
+ code += " if (!COLLIDED) {\n";
+ code += " \n";
+ code += " float vel_mag = length(final_velocity);\n";
+ code += " float vel_infl = clamp(dynamic_params.turb_influence * turbulence_influence, 0.0,1.0);\n";
+ code += " final_velocity = mix(final_velocity, normalize(noise_direction) * vel_mag * (1.0 + (1.0 - vel_infl) * 0.2), vel_infl);\n";
+ code += " }\n";
}
code += " \n";
- code += " // orbit velocity\n";
- if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- code += " float orbit_amount = tex_orbit_velocity * mix(orbit_velocity_min, orbit_velocity_max, rand_from_seed(alt_seed));\n";
- code += " if (orbit_amount != 0.0) {\n";
- code += " float ang = orbit_amount * DELTA * pi * 2.0;\n";
- code += " mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));\n";
- code += " TRANSFORM[3].xy -= diff.xy;\n";
- code += " TRANSFORM[3].xy += rot * diff.xy;\n";
+ code += " // limit velocity\n";
+ if (velocity_limit_curve.is_valid()) {
+ code += " if (length(final_velocity) > 0.001){\n";
+ code += " final_velocity = normalize(final_velocity) * min(abs(length(final_velocity)), abs(texture(velocity_limit_curve, vec2(lifetime_percent)).r));\n";
code += " }\n";
}
-
- code += " float dmp = mix(damping_min, damping_max, rand_from_seed(alt_seed));\n";
- code += " if (dmp * tex_damping > 0.0) {\n";
- code += " float v = length(VELOCITY);\n";
- code += " float damp = tex_damping * dmp;\n";
- code += " v -= damp * DELTA;\n";
- code += " if (v < 0.0) {\n";
- code += " VELOCITY = vec3(0.0);\n";
- code += " } else {\n";
- code += " VELOCITY = normalize(VELOCITY) * v;\n";
- code += " }\n";
- code += " }\n";
- code += " float base_angle = (tex_angle) * mix(initial_angle_min, initial_angle_max, angle_rand);\n";
- code += " base_angle += CUSTOM.y * LIFETIME * (tex_angular_velocity) * mix(angular_velocity_min,angular_velocity_max, rand_from_seed(alt_seed));\n";
- code += " CUSTOM.x = base_angle * degree_to_rad;\n"; // angle
- code += " CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand) + tv * tex_anim_speed * mix(anim_speed_min, anim_speed_max, rand_from_seed(alt_seed));\n"; // angle
-
- // apply color
- // apply hue rotation
- if (tex_parameters[PARAM_SCALE].is_valid()) {
- code += " vec3 tex_scale = textureLod(scale_texture, vec2(tv, 0.0), 0.0).rgb;\n";
- } else {
- code += " vec3 tex_scale = vec3(1.0);\n";
+ code += " \n";
+ if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
+ code += " final_velocity.z = 0.;\n";
}
-
- if (tex_parameters[PARAM_HUE_VARIATION].is_valid()) {
- code += " float tex_hue_variation = textureLod(hue_variation_texture, vec2(tv, 0.0), 0.0).r;\n";
- } else {
- code += " float tex_hue_variation = 1.0;\n";
+ code += " TRANSFORM[3].xyz += final_velocity * DELTA;\n";
+ code += " \n";
+ code += " \n";
+ code += " process_display_param(params, lifetime_percent);\n";
+ code += " \n";
+ code += " float base_angle = dynamic_params.angle;\n";
+ if (tex_parameters[PARAM_ANGLE].is_valid()) {
+ code += " base_angle *= texture(angle_texture, vec2(lifetime_percent)).r;\n";
}
+ if (tex_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) {
+ code += " base_angle += CUSTOM.y * LIFETIME * dynamic_params.angular_velocity * texture(angular_velocity_texture, vec2(lifetime_percent)).r;\n";
- code += " float hue_rot_angle = (tex_hue_variation) * pi * 2.0 * mix(hue_variation_min, hue_variation_max, rand_from_seed(alt_seed));\n";
- code += " float hue_rot_c = cos(hue_rot_angle);\n";
- code += " float hue_rot_s = sin(hue_rot_angle);\n";
- code += " mat4 hue_rot_mat = mat4(vec4(0.299, 0.587, 0.114, 0.0),\n";
- code += " vec4(0.299, 0.587, 0.114, 0.0),\n";
- code += " vec4(0.299, 0.587, 0.114, 0.0),\n";
- code += " vec4(0.000, 0.000, 0.000, 1.0)) +\n";
- code += " mat4(vec4(0.701, -0.587, -0.114, 0.0),\n";
- code += " vec4(-0.299, 0.413, -0.114, 0.0),\n";
- code += " vec4(-0.300, -0.588, 0.886, 0.0),\n";
- code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_c +\n";
- code += " mat4(vec4(0.168, 0.330, -0.497, 0.0),\n";
- code += " vec4(-0.328, 0.035, 0.292, 0.0),\n";
- code += " vec4(1.250, -1.050, -0.203, 0.0),\n";
- code += " vec4(0.000, 0.000, 0.000, 0.0)) * hue_rot_s;\n";
- if (color_ramp.is_valid()) {
- code += " COLOR = hue_rot_mat * textureLod(color_ramp, vec2(tv, 0.0), 0.0) * color_value;\n";
} else {
- code += " COLOR = hue_rot_mat * color_value;\n";
- }
-
- if (color_initial_ramp.is_valid()) {
- code += " vec4 start_color = textureLod(color_initial_ramp, vec2(color_initial_rand, 0.0), 0.0);\n";
- code += " COLOR *= start_color;\n";
- }
-
- if (emission_color_texture.is_valid() && (emission_shape == EMISSION_SHAPE_POINTS || emission_shape == EMISSION_SHAPE_DIRECTED_POINTS)) {
- code += " COLOR *= texelFetch(emission_texture_color, emission_tex_ofs, 0);\n";
+ code += " base_angle += CUSTOM.y * LIFETIME * dynamic_params.angular_velocity;\n";
}
- code += "\n";
+ code += " CUSTOM.x = base_angle * degree_to_rad;\n";
+ code += " COLOR = params.color;\n";
if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) {
- code += " if (length(VELOCITY) > 0.0) {\n";
- code += " TRANSFORM[1].xyz = normalize(VELOCITY);\n";
+ code += " if (length(final_velocity) > 0.0) {\n";
+ code += " TRANSFORM[1].xyz = normalize(final_velocity);\n";
code += " } else {\n";
code += " TRANSFORM[1].xyz = normalize(TRANSFORM[1].xyz);\n";
code += " }\n";
@@ -827,8 +1038,8 @@ void ParticleProcessMaterial::_update_shader() {
} else {
// orient particle Y towards velocity
if (particle_flags[PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY]) {
- code += " if (length(VELOCITY) > 0.0) {\n";
- code += " TRANSFORM[1].xyz = normalize(VELOCITY);\n";
+ code += " if (length(final_velocity) > 0.0) {\n";
+ code += " TRANSFORM[1].xyz = normalize(final_velocity);\n";
code += " } else {\n";
code += " TRANSFORM[1].xyz = normalize(TRANSFORM[1].xyz);\n";
code += " }\n";
@@ -855,33 +1066,20 @@ void ParticleProcessMaterial::_update_shader() {
if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
code += " TRANSFORM[3].z = 0.0;\n";
}
-
- // scale by scale
- code += " float base_scale = mix(scale_min, scale_max, scale_rand);\n";
- code += " base_scale = sign(base_scale) * max(abs(base_scale), 0.001);\n";
- code += " TRANSFORM[0].xyz *= base_scale * sign(tex_scale.r) * max(abs(tex_scale.r), 0.001);\n";
- code += " TRANSFORM[1].xyz *= base_scale * sign(tex_scale.g) * max(abs(tex_scale.g), 0.001);\n";
- code += " TRANSFORM[2].xyz *= base_scale * sign(tex_scale.b) * max(abs(tex_scale.b), 0.001);\n";
-
- if (collision_mode == COLLISION_RIGID) {
- code += " if (COLLIDED) {\n";
- code += " if (length(VELOCITY) > 3.0) {\n";
- code += " TRANSFORM[3].xyz += COLLISION_NORMAL * COLLISION_DEPTH;\n";
- code += " VELOCITY -= COLLISION_NORMAL * dot(COLLISION_NORMAL, VELOCITY) * (1.0 + collision_bounce);\n";
- code += " VELOCITY = mix(VELOCITY,vec3(0.0),clamp(collision_friction, 0.0, 1.0));\n";
- code += " } else {\n";
- code += " VELOCITY = vec3(0.0);\n";
- // If turbulence is enabled, set the noise direction to up so the turbulence color is "neutral"
- if (turbulence_enabled) {
- code += " noise_direction = vec3(1.0, 0.0, 0.0);\n";
- }
- code += " }\n";
- code += " }\n";
- } else if (collision_mode == COLLISION_HIDE_ON_CONTACT) {
- code += " if (COLLIDED) {\n";
- code += " ACTIVE = false;\n";
- code += " }\n";
+ if (tex_parameters[PARAM_SCALE_OVER_VELOCITY].is_valid()) {
+ code += " if(length(final_velocity) > 0.001){\n";
+ code += " params.scale *= texture(scale_over_velocity_curve, vec2(clamp(length(final_velocity)/(scale_over_velocity_max - scale_over_velocity_min), 0.0,1.0), 0.0)).rgb;\n";
+ code += " } else {params.scale *= texture(scale_over_velocity_curve, vec2(0.0)).rgb;}\n \n";
}
+ code += "// params.scale *= length(final_velocity)/100.0;\n";
+ code += "\n";
+ code += " TRANSFORM[0].xyz *= sign(params.scale.x) * max(abs(params.scale.x), 0.001);\n";
+ code += " TRANSFORM[1].xyz *= sign(params.scale.y) * max(abs(params.scale.y), 0.001);\n";
+ code += " TRANSFORM[2].xyz *= sign(params.scale.z) * max(abs(params.scale.z), 0.001);\n";
+ code += " \n";
+ code += " // \n";
+ code += " CUSTOM.z = params.animation_offset + lifetime_percent * params.animation_speed;\n";
+ code += " \n";
if (sub_emitter_mode != SUB_EMITTER_DISABLED && !RenderingServer::get_singleton()->is_low_end()) {
code += " int emit_count = 0;\n";
@@ -975,6 +1173,15 @@ float ParticleProcessMaterial::get_flatness() const {
return flatness;
}
+void ParticleProcessMaterial::set_velocity_pivot(const Vector3 &p_pivot) {
+ velocity_pivot = p_pivot;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->velocity_pivot, p_pivot);
+}
+
+Vector3 ParticleProcessMaterial::get_velocity_pivot() {
+ return velocity_pivot;
+}
+
void ParticleProcessMaterial::set_param_min(Parameter p_param, float p_value) {
ERR_FAIL_INDEX(p_param, PARAM_MAX);
@@ -1029,6 +1236,15 @@ void ParticleProcessMaterial::set_param_min(Parameter p_param, float p_value) {
case PARAM_TURB_INFLUENCE_OVER_LIFE: {
// Can't happen, but silences warning
} break;
+ case PARAM_RADIAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_velocity_min, p_value);
+ } break;
+ case PARAM_SCALE_OVER_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_over_velocity_min, p_value);
+ } break;
+ case PARAM_DIRECTIONAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->directional_velocity_min, p_value);
+ } break;
case PARAM_MAX:
break; // Can't happen, but silences warning
}
@@ -1094,6 +1310,15 @@ void ParticleProcessMaterial::set_param_max(Parameter p_param, float p_value) {
case PARAM_TURB_INFLUENCE_OVER_LIFE: {
// Can't happen, but silences warning
} break;
+ case PARAM_RADIAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_velocity_max, p_value);
+ } break;
+ case PARAM_SCALE_OVER_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_over_velocity_max, p_value);
+ } break;
+ case PARAM_DIRECTIONAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->directional_velocity_max, p_value);
+ } break;
case PARAM_MAX:
break; // Can't happen, but silences warning
}
@@ -1107,11 +1332,15 @@ float ParticleProcessMaterial::get_param_max(Parameter p_param) const {
static void _adjust_curve_range(const Ref<Texture2D> &p_texture, float p_min, float p_max) {
Ref<CurveTexture> curve_tex = p_texture;
- if (!curve_tex.is_valid()) {
+ if (curve_tex.is_valid()) {
+ curve_tex->ensure_default_setup(p_min, p_max);
+ return;
+ }
+ Ref<CurveXYZTexture> curve_xyz_tex = p_texture;
+ if (curve_xyz_tex.is_valid()) {
+ curve_xyz_tex->ensure_default_setup(p_min, p_max);
return;
}
-
- curve_tex->ensure_default_setup(p_min, p_max);
}
void ParticleProcessMaterial::set_param_texture(Parameter p_param, const Ref<Texture2D> &p_texture) {
@@ -1132,6 +1361,7 @@ void ParticleProcessMaterial::set_param_texture(Parameter p_param, const Ref<Tex
case PARAM_ORBIT_VELOCITY: {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->orbit_velocity_texture, tex_rid);
_adjust_curve_range(p_texture, -500, 500);
+ notify_property_list_changed();
} break;
case PARAM_LINEAR_ACCEL: {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->linear_accel_texture, tex_rid);
@@ -1178,6 +1408,18 @@ void ParticleProcessMaterial::set_param_texture(Parameter p_param, const Ref<Tex
case PARAM_TURB_INIT_DISPLACEMENT: {
// Can't happen, but silences warning
} break;
+ case PARAM_RADIAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->radial_velocity_texture, tex_rid);
+ } break;
+ case PARAM_SCALE_OVER_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->scale_over_velocity_texture, tex_rid);
+ _adjust_curve_range(p_texture, 0, 3);
+ notify_property_list_changed();
+ } break;
+ case PARAM_DIRECTIONAL_VELOCITY: {
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->directional_velocity_texture, tex_rid);
+ notify_property_list_changed();
+ } break;
case PARAM_MAX:
break; // Can't happen, but silences warning
}
@@ -1233,6 +1475,42 @@ void ParticleProcessMaterial::set_particle_flag(ParticleFlags p_particle_flag, b
}
}
+void ParticleProcessMaterial::set_alpha_curve(const Ref<Texture2D> &p_texture) {
+ alpha_curve = p_texture;
+ RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->alpha_ramp, tex_rid);
+ _queue_shader_change();
+ notify_property_list_changed();
+}
+
+Ref<Texture2D> ParticleProcessMaterial::get_alpha_curve() const {
+ return alpha_curve;
+}
+
+void ParticleProcessMaterial::set_emission_curve(const Ref<Texture2D> &p_texture) {
+ emission_curve = p_texture;
+ RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ramp, tex_rid);
+ _queue_shader_change();
+ notify_property_list_changed();
+}
+
+Ref<Texture2D> ParticleProcessMaterial::get_emission_curve() const {
+ return emission_curve;
+}
+
+void ParticleProcessMaterial::set_velocity_limit_curve(const Ref<Texture2D> &p_texture) {
+ velocity_limit_curve = p_texture;
+ RID tex_rid = p_texture.is_valid() ? p_texture->get_rid() : RID();
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->velocity_limit_curve, tex_rid);
+ _queue_shader_change();
+ notify_property_list_changed();
+}
+
+Ref<Texture2D> ParticleProcessMaterial::get_velocity_limit_curve() const {
+ return velocity_limit_curve;
+}
+
bool ParticleProcessMaterial::get_particle_flag(ParticleFlags p_particle_flag) const {
ERR_FAIL_INDEX_V(p_particle_flag, PARTICLE_FLAG_MAX, false);
return particle_flags[p_particle_flag];
@@ -1299,6 +1577,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius);
}
+void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) {
+ inherit_emitter_velocity_ratio = p_ratio;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio);
+}
+
ParticleProcessMaterial::EmissionShape ParticleProcessMaterial::get_emission_shape() const {
return emission_shape;
}
@@ -1343,6 +1626,28 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) {
+ emission_shape_offset = p_emission_shape_offset;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset);
+}
+
+Vector3 ParticleProcessMaterial::get_emission_shape_offset() const {
+ return emission_shape_offset;
+}
+
+void ParticleProcessMaterial::set_emission_shape_scale(const Vector3 &p_emission_shape_scale) {
+ emission_shape_scale = p_emission_shape_scale;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_scale, p_emission_shape_scale);
+}
+
+Vector3 ParticleProcessMaterial::get_emission_shape_scale() const {
+ return emission_shape_scale;
+}
+
+double ParticleProcessMaterial::get_inherit_velocity_ratio() {
+ return inherit_emitter_velocity_ratio;
+}
+
void ParticleProcessMaterial::set_turbulence_enabled(const bool p_turbulence_enabled) {
turbulence_enabled = p_turbulence_enabled;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->turbulence_enabled, turbulence_enabled);
@@ -1459,10 +1764,6 @@ void ParticleProcessMaterial::_validate_property(PropertyInfo &p_property) const
p_property.usage = PROPERTY_USAGE_NONE;
}
- if (p_property.name.begins_with("orbit_") && !particle_flags[PARTICLE_FLAG_DISABLE_Z]) {
- p_property.usage = PROPERTY_USAGE_NONE;
- }
-
if (!turbulence_enabled) {
if (p_property.name == "turbulence_noise_strength" ||
p_property.name == "turbulence_noise_scale" ||
@@ -1484,6 +1785,15 @@ void ParticleProcessMaterial::_validate_property(PropertyInfo &p_property) const
if (p_property.name == "collision_bounce" && collision_mode != COLLISION_RIGID) {
p_property.usage = PROPERTY_USAGE_NONE;
}
+ if ((p_property.name == "directional_velocity_min" || p_property.name == "directional_velocity_max") && !tex_parameters[PARAM_DIRECTIONAL_VELOCITY].is_valid()) {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+ if ((p_property.name == "scale_over_velocity_min" || p_property.name == "scale_over_velocity_max") && !tex_parameters[PARAM_SCALE_OVER_VELOCITY].is_valid()) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ if ((p_property.name == "orbit_velocity_min" || p_property.name == "orbit_velocity_max") && (!tex_parameters[PARAM_ORBIT_VELOCITY].is_valid() && !particle_flags[PARTICLE_FLAG_DISABLE_Z])) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
}
void ParticleProcessMaterial::set_sub_emitter_mode(SubEmitterMode p_sub_emitter_mode) {
@@ -1588,6 +1898,9 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_direction", "degrees"), &ParticleProcessMaterial::set_direction);
ClassDB::bind_method(D_METHOD("get_direction"), &ParticleProcessMaterial::get_direction);
+ ClassDB::bind_method(D_METHOD("set_inherit_velocity_ratio", "ratio"), &ParticleProcessMaterial::set_inherit_velocity_ratio);
+ ClassDB::bind_method(D_METHOD("get_inherit_velocity_ratio"), &ParticleProcessMaterial::get_inherit_velocity_ratio);
+
ClassDB::bind_method(D_METHOD("set_spread", "degrees"), &ParticleProcessMaterial::set_spread);
ClassDB::bind_method(D_METHOD("get_spread"), &ParticleProcessMaterial::get_spread);
@@ -1608,13 +1921,24 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_color_ramp", "ramp"), &ParticleProcessMaterial::set_color_ramp);
ClassDB::bind_method(D_METHOD("get_color_ramp"), &ParticleProcessMaterial::get_color_ramp);
+ ClassDB::bind_method(D_METHOD("set_alpha_curve", "curve"), &ParticleProcessMaterial::set_alpha_curve);
+ ClassDB::bind_method(D_METHOD("get_alpha_curve"), &ParticleProcessMaterial::get_alpha_curve);
+
+ ClassDB::bind_method(D_METHOD("set_emission_curve", "curve"), &ParticleProcessMaterial::set_emission_curve);
+ ClassDB::bind_method(D_METHOD("get_emission_curve"), &ParticleProcessMaterial::get_emission_curve);
ClassDB::bind_method(D_METHOD("set_color_initial_ramp", "ramp"), &ParticleProcessMaterial::set_color_initial_ramp);
ClassDB::bind_method(D_METHOD("get_color_initial_ramp"), &ParticleProcessMaterial::get_color_initial_ramp);
+ ClassDB::bind_method(D_METHOD("set_velocity_limit_curve", "curve"), &ParticleProcessMaterial::set_velocity_limit_curve);
+ ClassDB::bind_method(D_METHOD("get_velocity_limit_curve"), &ParticleProcessMaterial::get_velocity_limit_curve);
+
ClassDB::bind_method(D_METHOD("set_particle_flag", "particle_flag", "enable"), &ParticleProcessMaterial::set_particle_flag);
ClassDB::bind_method(D_METHOD("get_particle_flag", "particle_flag"), &ParticleProcessMaterial::get_particle_flag);
+ ClassDB::bind_method(D_METHOD("set_velocity_pivot", "pivot"), &ParticleProcessMaterial::set_velocity_pivot);
+ ClassDB::bind_method(D_METHOD("get_velocity_pivot"), &ParticleProcessMaterial::get_velocity_pivot);
+
ClassDB::bind_method(D_METHOD("set_emission_shape", "shape"), &ParticleProcessMaterial::set_emission_shape);
ClassDB::bind_method(D_METHOD("get_emission_shape"), &ParticleProcessMaterial::get_emission_shape);
@@ -1648,6 +1972,12 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset);
+ ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset);
+
+ ClassDB::bind_method(D_METHOD("set_emission_shape_scale", "emission_shape_scale"), &ParticleProcessMaterial::set_emission_shape_scale);
+ ClassDB::bind_method(D_METHOD("get_emission_shape_scale"), &ParticleProcessMaterial::get_emission_shape_scale);
+
ClassDB::bind_method(D_METHOD("get_turbulence_enabled"), &ParticleProcessMaterial::get_turbulence_enabled);
ClassDB::bind_method(D_METHOD("set_turbulence_enabled", "turbulence_enabled"), &ParticleProcessMaterial::set_turbulence_enabled);
@@ -1699,10 +2029,16 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_bounce", "bounce"), &ParticleProcessMaterial::set_collision_bounce);
ClassDB::bind_method(D_METHOD("get_collision_bounce"), &ParticleProcessMaterial::get_collision_bounce);
- ADD_GROUP("Time", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lifetime_randomness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_lifetime_randomness", "get_lifetime_randomness");
-
- ADD_GROUP("Emission Shape", "emission_");
+ ADD_GROUP("Particle Flags", "particle_flag_");
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_disable_z"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_DISABLE_Z);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_damping_as_friction"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_DAMPING_AS_FRICTION);
+ ADD_GROUP("Spawn", "");
+ ADD_SUBGROUP("Position", "");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_shape_offset"), "set_emission_shape_offset", "get_emission_shape_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_shape_scale"), "set_emission_shape_scale", "get_emission_shape_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_shape", PROPERTY_HINT_ENUM, "Point,Sphere,Sphere Surface,Box,Points,Directed Points,Ring"), "set_emission_shape", "get_emission_shape");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_sphere_radius", PROPERTY_HINT_RANGE, "0.01,128,0.01,or_greater"), "set_emission_sphere_radius", "get_emission_sphere_radius");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_box_extents"), "set_emission_box_extents", "get_emission_box_extents");
@@ -1714,60 +2050,87 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
- ADD_GROUP("Particle Flags", "particle_flag_");
- ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
- ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
- ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_disable_z"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_DISABLE_Z);
- ADD_GROUP("Direction", "");
+ ADD_SUBGROUP("Angle", "");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_less,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_less,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE);
+ ADD_SUBGROUP("Velocity", "");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inherit_velocity_ratio", PROPERTY_HINT_RANGE, "0.0,1.0,0.001,or_less,or_greater"), "set_inherit_velocity_ratio", "get_inherit_velocity_ratio");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity_pivot"), "set_velocity_pivot", "get_velocity_pivot");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "direction"), "set_direction", "get_direction");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spread", PROPERTY_HINT_RANGE, "0,180,0.01"), "set_spread", "get_spread");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "flatness", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_flatness", "get_flatness");
- ADD_GROUP("Gravity", "");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity");
- ADD_GROUP("Initial Velocity", "initial_");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "spread", PROPERTY_HINT_RANGE, "0,180,0.001"), "set_spread", "get_spread");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "flatness", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_flatness", "get_flatness");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_INITIAL_LINEAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "initial_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_INITIAL_LINEAR_VELOCITY);
- ADD_GROUP("Angular Velocity", "angular_");
+ ADD_GROUP("Animated Velocity", "");
+ ADD_SUBGROUP("Velocity Limit", "");
+ ADD_SUBGROUP("Angular Velocity", "angular_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angular_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ANGULAR_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angular_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGULAR_VELOCITY);
- ADD_GROUP("Orbit Velocity", "orbit_");
+ ADD_SUBGROUP("Directional Velocity", "directional_");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_velocity_min", PROPERTY_HINT_RANGE, "-720,720,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_DIRECTIONAL_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "directional_velocity_max", PROPERTY_HINT_RANGE, "-720,720,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_DIRECTIONAL_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "directional_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_DIRECTIONAL_VELOCITY);
+ ADD_SUBGROUP("Orbit Velocity", "orbit_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ORBIT_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "orbit_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ORBIT_VELOCITY);
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY);
- ADD_GROUP("Linear Accel", "linear_");
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "orbit_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_ORBIT_VELOCITY);
+ ADD_SUBGROUP("Radial Velocity", "radial_");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_velocity_min", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_velocity_max", PROPERTY_HINT_RANGE, "-1000,1000,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_VELOCITY);
+ ADD_SUBGROUP("Velocity Limit", "");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "velocity_limit_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_velocity_limit_curve", "get_velocity_limit_curve");
+ ADD_GROUP("Accelerations", "");
+ ADD_SUBGROUP("Gravity", "");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity");
+ ADD_SUBGROUP("Linear Accel", "linear_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_LINEAR_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "linear_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_LINEAR_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "linear_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_LINEAR_ACCEL);
- ADD_GROUP("Radial Accel", "radial_");
+ ADD_SUBGROUP("Radial Accel", "radial_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_RADIAL_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "radial_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_RADIAL_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "radial_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_RADIAL_ACCEL);
- ADD_GROUP("Tangential Accel", "tangential_");
+ ADD_SUBGROUP("Tangential Accel", "tangential_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_min", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_TANGENTIAL_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "tangential_accel_max", PROPERTY_HINT_RANGE, "-100,100,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_TANGENTIAL_ACCEL);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "tangential_accel_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TANGENTIAL_ACCEL);
- ADD_GROUP("Damping", "");
+ ADD_SUBGROUP("Damping", "");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_min", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_param_min", "get_param_min", PARAM_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "damping_max", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), "set_param_max", "get_param_max", PARAM_DAMPING);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_DAMPING);
- ADD_GROUP("Angle", "");
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_min", PROPERTY_HINT_RANGE, "-720,720,0.1,or_less,or_greater,degrees"), "set_param_min", "get_param_min", PARAM_ANGLE);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "angle_max", PROPERTY_HINT_RANGE, "-720,720,0.1,or_less,or_greater,degrees"), "set_param_max", "get_param_max", PARAM_ANGLE);
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE);
- ADD_GROUP("Scale", "");
+ ADD_SUBGROUP("Attractor Interaction", "attractor_interaction_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "attractor_interaction_enabled"), "set_attractor_interaction_enabled", "is_attractor_interaction_enabled");
+
+ ADD_GROUP("Display", "");
+ ADD_SUBGROUP("Scale", "");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE);
- ADD_GROUP("Color", "");
+ ADD_SUBGROUP("Scale Over Velocity", "");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_over_velocity_min", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_min", "get_param_min", PARAM_SCALE_OVER_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "scale_over_velocity_max", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_param_max", "get_param_max", PARAM_SCALE_OVER_VELOCITY);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "scale_over_velocity_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture,CurveXYZTexture"), "set_param_texture", "get_param_texture", PARAM_SCALE_OVER_VELOCITY);
+
+ ADD_SUBGROUP("Color Curves", "");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture1D"), "set_color_ramp", "get_color_ramp");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_initial_ramp", PROPERTY_HINT_RESOURCE_TYPE, "GradientTexture1D"), "set_color_initial_ramp", "get_color_initial_ramp");
-
- ADD_GROUP("Hue Variation", "hue_");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "alpha_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_alpha_curve", "get_alpha_curve");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_emission_curve", "get_emission_curve");
+ ADD_SUBGROUP("Hue Variation", "hue_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_min", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_min", "get_param_min", PARAM_HUE_VARIATION);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "hue_variation_max", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_param_max", "get_param_max", PARAM_HUE_VARIATION);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "hue_variation_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_HUE_VARIATION);
+ ADD_SUBGROUP("Animation", "anim_");
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET);
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET);
ADD_GROUP("Turbulence", "turbulence_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "turbulence_enabled"), "set_turbulence_enabled", "get_turbulence_enabled");
@@ -1781,14 +2144,11 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "turbulence_initial_displacement_max", PROPERTY_HINT_RANGE, "-100,100,0.1"), "set_param_max", "get_param_max", PARAM_TURB_INIT_DISPLACEMENT);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "turbulence_influence_over_life", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_TURB_INFLUENCE_OVER_LIFE);
- ADD_GROUP("Animation", "anim_");
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_min", PROPERTY_HINT_RANGE, "0,16,0.01,or_less,or_greater"), "set_param_min", "get_param_min", PARAM_ANIM_SPEED);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_speed_max", PROPERTY_HINT_RANGE, "0,16,0.01,or_less,or_greater"), "set_param_max", "get_param_max", PARAM_ANIM_SPEED);
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_speed_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_SPEED);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_min", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_param_min", "get_param_min", PARAM_ANIM_OFFSET);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anim_offset_max", PROPERTY_HINT_RANGE, "0,1,0.0001"), "set_param_max", "get_param_max", PARAM_ANIM_OFFSET);
- ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "anim_offset_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANIM_OFFSET);
-
+ ADD_GROUP("Collision", "collision_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Rigid,Hide On Contact"), "set_collision_mode", "get_collision_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale");
ADD_GROUP("Sub Emitter", "sub_emitter_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_mode", PROPERTY_HINT_ENUM, "Disabled,Constant,At End,At Collision"), "set_sub_emitter_mode", "get_sub_emitter_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sub_emitter_frequency", PROPERTY_HINT_RANGE, "0.01,100,0.01,suffix:Hz"), "set_sub_emitter_frequency", "get_sub_emitter_frequency");
@@ -1796,14 +2156,6 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "sub_emitter_amount_at_collision", PROPERTY_HINT_RANGE, "1,32,1"), "set_sub_emitter_amount_at_collision", "get_sub_emitter_amount_at_collision");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sub_emitter_keep_velocity"), "set_sub_emitter_keep_velocity", "get_sub_emitter_keep_velocity");
- ADD_GROUP("Attractor Interaction", "attractor_interaction_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "attractor_interaction_enabled"), "set_attractor_interaction_enabled", "is_attractor_interaction_enabled");
- ADD_GROUP("Collision", "collision_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Rigid,Hide On Contact"), "set_collision_mode", "get_collision_mode");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_friction", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_friction", "get_collision_friction");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_bounce", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_collision_bounce", "get_collision_bounce");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_use_scale"), "set_collision_use_scale", "is_collision_using_scale");
-
BIND_ENUM_CONSTANT(PARAM_INITIAL_LINEAR_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_ANGULAR_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_ORBIT_VELOCITY);
@@ -1816,11 +2168,15 @@ void ParticleProcessMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(PARAM_HUE_VARIATION);
BIND_ENUM_CONSTANT(PARAM_ANIM_SPEED);
BIND_ENUM_CONSTANT(PARAM_ANIM_OFFSET);
+ BIND_ENUM_CONSTANT(PARAM_RADIAL_VELOCITY);
+ BIND_ENUM_CONSTANT(PARAM_DIRECTIONAL_VELOCITY);
+ BIND_ENUM_CONSTANT(PARAM_SCALE_OVER_VELOCITY);
BIND_ENUM_CONSTANT(PARAM_MAX);
BIND_ENUM_CONSTANT(PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
BIND_ENUM_CONSTANT(PARTICLE_FLAG_ROTATE_Y);
BIND_ENUM_CONSTANT(PARTICLE_FLAG_DISABLE_Z);
+ BIND_ENUM_CONSTANT(PARTICLE_FLAG_DAMPING_AS_FRICTION);
BIND_ENUM_CONSTANT(PARTICLE_FLAG_MAX);
BIND_ENUM_CONSTANT(EMISSION_SHAPE_POINT);
@@ -1877,6 +2233,8 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_param_max(PARAM_HUE_VARIATION, 0);
set_param_max(PARAM_ANIM_SPEED, 0);
set_param_max(PARAM_ANIM_OFFSET, 0);
+ set_param_min(PARAM_DIRECTIONAL_VELOCITY, 1.0);
+ set_param_max(PARAM_DIRECTIONAL_VELOCITY, 1.0);
set_emission_shape(EMISSION_SHAPE_POINT);
set_emission_sphere_radius(1);
set_emission_box_extents(Vector3(1, 1, 1));
@@ -1884,6 +2242,8 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_shape_offset(Vector3(0.0, 0.0, 0.0));
+ set_emission_shape_scale(Vector3(1.0, 1.0, 1.0));
set_turbulence_enabled(false);
set_turbulence_noise_speed(Vector3(0.0, 0.0, 0.0));
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 533bd9636f..83228104b2 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -32,6 +32,7 @@
#define PARTICLE_PROCESS_MATERIAL_H
#include "core/templates/rid.h"
+#include "core/templates/self_list.h"
#include "scene/resources/material.h"
/*
@@ -61,6 +62,9 @@ public:
PARAM_TURB_INFLUENCE_OVER_LIFE,
PARAM_TURB_VEL_INFLUENCE,
PARAM_TURB_INIT_DISPLACEMENT,
+ PARAM_RADIAL_VELOCITY,
+ PARAM_DIRECTIONAL_VELOCITY,
+ PARAM_SCALE_OVER_VELOCITY,
PARAM_MAX
};
@@ -69,6 +73,7 @@ public:
PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY,
PARTICLE_FLAG_ROTATE_Y,
PARTICLE_FLAG_DISABLE_Z,
+ PARTICLE_FLAG_DAMPING_AS_FRICTION,
PARTICLE_FLAG_MAX
};
@@ -102,35 +107,37 @@ public:
};
private:
- union MaterialKey {
- // The bit size of the struct must be kept below or equal to 32 bits.
+ struct MaterialKey {
+ // The bit size of the struct must be kept below or equal to 64 bits.
// Consider this when extending ParticleFlags, EmissionShape, or SubEmitterMode.
- struct {
- uint32_t texture_mask : 16;
- uint32_t texture_color : 1;
- uint32_t particle_flags : 4;
- uint32_t emission_shape : 3;
- uint32_t invalid_key : 1;
- uint32_t has_emission_color : 1;
- uint32_t sub_emitter : 2;
- uint32_t attractor_enabled : 1;
- uint32_t collision_mode : 2;
- uint32_t collision_scale : 1;
- uint32_t turbulence_enabled : 1;
- };
-
- uint64_t key = 0;
+ uint64_t texture_mask : PARAM_MAX;
+ uint64_t texture_color : 1;
+ uint64_t particle_flags : PARTICLE_FLAG_MAX - 1;
+ uint64_t emission_shape : 3;
+ uint64_t invalid_key : 1;
+ uint64_t has_emission_color : 1;
+ uint64_t sub_emitter : 2;
+ uint64_t attractor_enabled : 1;
+ uint64_t collision_mode : 2;
+ uint64_t collision_scale : 1;
+ uint64_t turbulence_enabled : 1;
+ uint64_t limiter_curve : 1;
+ uint64_t alpha_curve : 1;
+ uint64_t emission_curve : 1;
+ uint64_t has_initial_ramp : 1;
+
+ MaterialKey() {
+ memset(this, 0, sizeof(MaterialKey));
+ }
static uint32_t hash(const MaterialKey &p_key) {
- return hash_murmur3_one_32(p_key.key);
+ return hash_djb2_buffer((const uint8_t *)&p_key, sizeof(MaterialKey));
}
-
bool operator==(const MaterialKey &p_key) const {
- return key == p_key.key;
+ return memcmp(this, &p_key, sizeof(MaterialKey)) == 0;
}
-
bool operator<(const MaterialKey &p_key) const {
- return key < p_key.key;
+ return memcmp(this, &p_key, sizeof(MaterialKey)) < 0;
}
};
@@ -145,17 +152,6 @@ private:
_FORCE_INLINE_ MaterialKey _compute_key() const {
MaterialKey mk;
- mk.key = 0;
- for (int i = 0; i < PARAM_MAX; i++) {
- if (tex_parameters[i].is_valid()) {
- mk.texture_mask |= (1 << i);
- }
- }
- for (int i = 0; i < PARTICLE_FLAG_MAX; i++) {
- if (particle_flags[i]) {
- mk.particle_flags |= (1 << i);
- }
- }
mk.texture_color = color_ramp.is_valid() ? 1 : 0;
mk.emission_shape = emission_shape;
@@ -165,6 +161,21 @@ private:
mk.attractor_enabled = attractor_interaction_enabled;
mk.collision_scale = collision_scale;
mk.turbulence_enabled = turbulence_enabled;
+ mk.limiter_curve = velocity_limit_curve.is_valid() ? 1 : 0;
+ mk.alpha_curve = alpha_curve.is_valid() ? 1 : 0;
+ mk.emission_curve = emission_curve.is_valid() ? 1 : 0;
+ mk.has_initial_ramp = color_initial_ramp.is_valid() ? 1 : 0;
+
+ for (int i = 0; i < PARAM_MAX; i++) {
+ if (tex_parameters[i].is_valid()) {
+ mk.texture_mask |= ((uint64_t)1 << i);
+ }
+ }
+ for (int i = 0; i < PARTICLE_FLAG_MAX; i++) {
+ if (particle_flags[i]) {
+ mk.particle_flags |= ((uint64_t)1 << i);
+ }
+ }
return mk;
}
@@ -180,44 +191,59 @@ private:
StringName initial_angle_min;
StringName angular_velocity_min;
StringName orbit_velocity_min;
+ StringName radial_velocity_min;
StringName linear_accel_min;
StringName radial_accel_min;
StringName tangent_accel_min;
StringName damping_min;
StringName scale_min;
+ StringName scale_over_velocity_min;
StringName hue_variation_min;
StringName anim_speed_min;
StringName anim_offset_min;
+ StringName directional_velocity_min;
StringName initial_linear_velocity_max;
StringName initial_angle_max;
StringName angular_velocity_max;
StringName orbit_velocity_max;
+ StringName radial_velocity_max;
StringName linear_accel_max;
StringName radial_accel_max;
StringName tangent_accel_max;
StringName damping_max;
StringName scale_max;
+ StringName scale_over_velocity_max;
StringName hue_variation_max;
StringName anim_speed_max;
StringName anim_offset_max;
+ StringName directional_velocity_max;
StringName angle_texture;
StringName angular_velocity_texture;
StringName orbit_velocity_texture;
+ StringName radial_velocity_texture;
StringName linear_accel_texture;
StringName radial_accel_texture;
StringName tangent_accel_texture;
StringName damping_texture;
StringName scale_texture;
+ StringName scale_over_velocity_texture;
StringName hue_variation_texture;
StringName anim_speed_texture;
StringName anim_offset_texture;
+ StringName velocity_limiter_texture;
+ StringName directional_velocity_texture;
StringName color;
StringName color_ramp;
+ StringName alpha_ramp;
+ StringName emission_ramp;
StringName color_initial_ramp;
+ StringName velocity_limit_curve;
+ StringName velocity_pivot;
+
StringName emission_sphere_radius;
StringName emission_box_extents;
StringName emission_texture_point_count;
@@ -228,6 +254,8 @@ private:
StringName emission_ring_height;
StringName emission_ring_radius;
StringName emission_ring_inner_radius;
+ StringName emission_shape_offset;
+ StringName emission_shape_scale;
StringName turbulence_enabled;
StringName turbulence_noise_strength;
@@ -241,6 +269,7 @@ private:
StringName turbulence_initial_displacement_max;
StringName gravity;
+ StringName inherit_emitter_velocity_ratio;
StringName lifetime_randomness;
@@ -272,7 +301,13 @@ private:
Ref<Texture2D> tex_parameters[PARAM_MAX];
Color color;
Ref<Texture2D> color_ramp;
+ Ref<Texture2D> alpha_curve;
+ Ref<Texture2D> emission_curve;
Ref<Texture2D> color_initial_ramp;
+ Ref<Texture2D> velocity_limit_curve;
+
+ bool directional_velocity_global = false;
+ Vector3 velocity_pivot;
bool particle_flags[PARTICLE_FLAG_MAX];
@@ -287,6 +322,8 @@ private:
real_t emission_ring_radius = 0.0f;
real_t emission_ring_inner_radius = 0.0f;
int emission_point_count = 1;
+ Vector3 emission_shape_offset;
+ Vector3 emission_shape_scale;
bool anim_loop = false;
@@ -300,6 +337,7 @@ private:
Vector3 gravity;
double lifetime_randomness = 0.0;
+ double inherit_emitter_velocity_ratio = 0.0;
SubEmitterMode sub_emitter_mode;
double sub_emitter_frequency = 0.0;
@@ -328,6 +366,9 @@ public:
void set_flatness(float p_flatness);
float get_flatness() const;
+ void set_velocity_pivot(const Vector3 &p_pivot);
+ Vector3 get_velocity_pivot();
+
void set_param_min(Parameter p_param, float p_value);
float get_param_min(Parameter p_param) const;
@@ -337,6 +378,11 @@ public:
void set_param_texture(Parameter p_param, const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_param_texture(Parameter p_param) const;
+ void set_velocity_limit_curve(const Ref<Texture2D> &p_texture);
+ Ref<Texture2D> get_velocity_limit_curve() const;
+
+ void set_alpha_curve(const Ref<Texture2D> &p_texture);
+ Ref<Texture2D> get_alpha_curve() const;
void set_color(const Color &p_color);
Color get_color() const;
@@ -346,6 +392,9 @@ public:
void set_color_initial_ramp(const Ref<Texture2D> &p_texture);
Ref<Texture2D> get_color_initial_ramp() const;
+ void set_emission_curve(const Ref<Texture2D> &p_texture);
+ Ref<Texture2D> get_emission_curve() const;
+
void set_particle_flag(ParticleFlags p_particle_flag, bool p_enable);
bool get_particle_flag(ParticleFlags p_particle_flag) const;
@@ -391,6 +440,9 @@ public:
void set_lifetime_randomness(double p_lifetime);
double get_lifetime_randomness() const;
+ void set_inherit_velocity_ratio(double p_ratio);
+ double get_inherit_velocity_ratio();
+
void set_attractor_interaction_enabled(bool p_enable);
bool is_attractor_interaction_enabled() const;
@@ -425,6 +477,12 @@ public:
void set_sub_emitter_keep_velocity(bool p_enable);
bool get_sub_emitter_keep_velocity() const;
+ void set_emission_shape_offset(const Vector3 &p_emission_shape_offset);
+ Vector3 get_emission_shape_offset() const;
+
+ void set_emission_shape_scale(const Vector3 &p_emission_shape_scale);
+ Vector3 get_emission_shape_scale() const;
+
virtual RID get_shader_rid() const override;
virtual Shader::Mode get_shader_mode() const override;
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index 6d848f5494..6c66fca254 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -2907,9 +2907,6 @@ void TextMesh::_create_mesh_array(Array &p_arr) const {
String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text;
TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
- }
TypedArray<Vector3i> stt;
if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) {
@@ -2927,9 +2924,6 @@ void TextMesh::_create_mesh_array(Array &p_arr) const {
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features());
}
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
- }
dirty_font = false;
dirty_lines = true;
diff --git a/scene/resources/skeleton_modification_2d_physicalbones.cpp b/scene/resources/skeleton_modification_2d_physicalbones.cpp
index 520b2ce483..5a72eb6bd8 100644
--- a/scene/resources/skeleton_modification_2d_physicalbones.cpp
+++ b/scene/resources/skeleton_modification_2d_physicalbones.cpp
@@ -186,7 +186,7 @@ void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_l
void SkeletonModification2DPhysicalBones::fetch_physical_bones() {
ERR_FAIL_NULL_MSG(stack, "No modification stack found! Cannot fetch physical bones!");
- ERR_FAIL_COND_MSG(!stack->skeleton, "No skeleton found! Cannot fetch physical bones!");
+ ERR_FAIL_NULL_MSG(stack->skeleton, "No skeleton found! Cannot fetch physical bones!");
physical_bone_chain.clear();
diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp
index 38a865b170..98527da96b 100644
--- a/scene/resources/text_line.cpp
+++ b/scene/resources/text_line.cpp
@@ -200,9 +200,6 @@ void TextLine::set_bidi_override(const Array &p_override) {
bool TextLine::add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language, const Variant &p_meta) {
ERR_FAIL_COND_V(p_font.is_null(), false);
bool res = TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language, p_meta);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
- }
dirty = true;
return res;
}
@@ -454,9 +451,6 @@ TextLine::TextLine(const String &p_text, const Ref<Font> &p_font, int p_font_siz
rid = TS->create_shaped_text(p_direction, p_orientation);
if (p_font.is_valid()) {
TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
- }
}
}
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index dbf595cb29..239ecb500e 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -397,9 +397,6 @@ bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_font, i
TS->shaped_text_clear(dropcap_rid);
dropcap_margins = p_dropcap_margins;
bool res = TS->shaped_text_add_string(dropcap_rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(dropcap_rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
- }
lines_dirty = true;
return res;
}
@@ -415,9 +412,6 @@ bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_font, in
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(p_font.is_null(), false);
bool res = TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language, p_meta);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
- }
lines_dirty = true;
return res;
}
@@ -1050,9 +1044,6 @@ TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_font, int
rid = TS->create_shaped_text(p_direction, p_orientation);
if (p_font.is_valid()) {
TS->shaped_text_add_string(rid, p_text, p_font->get_rids(), p_font_size, p_font->get_opentype_features(), p_language);
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(rid, TextServer::SpacingType(i), p_font->get_spacing(TextServer::SpacingType(i)));
- }
}
width = p_width;
}
diff --git a/servers/physics_2d/godot_area_2d.cpp b/servers/physics_2d/godot_area_2d.cpp
index 4d2148aa07..d6c786706c 100644
--- a/servers/physics_2d/godot_area_2d.cpp
+++ b/servers/physics_2d/godot_area_2d.cpp
@@ -78,13 +78,6 @@ void GodotArea2D::set_space(GodotSpace2D *p_space) {
}
void GodotArea2D::set_monitor_callback(const Callable &p_callback) {
- ObjectID id = p_callback.get_object_id();
-
- if (id == monitor_callback.get_object_id()) {
- monitor_callback = p_callback;
- return;
- }
-
_unregister_shapes();
monitor_callback = p_callback;
@@ -100,13 +93,6 @@ void GodotArea2D::set_monitor_callback(const Callable &p_callback) {
}
void GodotArea2D::set_area_monitor_callback(const Callable &p_callback) {
- ObjectID id = p_callback.get_object_id();
-
- if (id == area_monitor_callback.get_object_id()) {
- area_monitor_callback = p_callback;
- return;
- }
-
_unregister_shapes();
area_monitor_callback = p_callback;
@@ -194,7 +180,7 @@ Variant GodotArea2D::get_param(PhysicsServer2D::AreaParameter p_param) const {
}
void GodotArea2D::_queue_monitor_update() {
- ERR_FAIL_COND(!get_space());
+ ERR_FAIL_NULL(get_space());
if (!monitor_query_list.in_list()) {
get_space()->area_add_to_monitor_query_list(&monitor_query_list);
diff --git a/servers/physics_2d/godot_body_2d.cpp b/servers/physics_2d/godot_body_2d.cpp
index 4a7b470768..12c3e1e5b4 100644
--- a/servers/physics_2d/godot_body_2d.cpp
+++ b/servers/physics_2d/godot_body_2d.cpp
@@ -422,7 +422,7 @@ void GodotBody2D::integrate_forces(real_t p_step) {
return;
}
- ERR_FAIL_COND(!get_space());
+ ERR_FAIL_NULL(get_space());
int ac = areas.size();
@@ -615,7 +615,7 @@ void GodotBody2D::integrate_velocities(real_t p_step) {
return;
}
- if (fi_callback_data || body_state_callback.get_object()) {
+ if (fi_callback_data || body_state_callback.is_valid()) {
get_space()->body_add_to_state_query_list(&direct_state_query_list);
}
@@ -676,7 +676,7 @@ void GodotBody2D::call_queries() {
Variant direct_state_variant = get_direct_state();
if (fi_callback_data) {
- if (!fi_callback_data->callable.get_object()) {
+ if (!fi_callback_data->callable.is_valid()) {
set_force_integration_callback(Callable());
} else {
const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata };
@@ -692,7 +692,7 @@ void GodotBody2D::call_queries() {
}
}
- if (body_state_callback.get_object()) {
+ if (body_state_callback.is_valid()) {
body_state_callback.call(direct_state_variant);
}
}
@@ -719,7 +719,7 @@ void GodotBody2D::set_state_sync_callback(const Callable &p_callable) {
}
void GodotBody2D::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) {
- if (p_callable.get_object()) {
+ if (p_callable.is_valid()) {
if (!fi_callback_data) {
fi_callback_data = memnew(ForceIntegrationCallbackData);
}
diff --git a/servers/physics_2d/godot_physics_server_2d.cpp b/servers/physics_2d/godot_physics_server_2d.cpp
index 112ba240b6..8df17992ea 100644
--- a/servers/physics_2d/godot_physics_server_2d.cpp
+++ b/servers/physics_2d/godot_physics_server_2d.cpp
@@ -994,7 +994,7 @@ void GodotPhysicsServer2D::body_set_pickable(RID p_body, bool p_pickable) {
bool GodotPhysicsServer2D::body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result) {
GodotBody2D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, false);
- ERR_FAIL_COND_V(!body->get_space(), false);
+ ERR_FAIL_NULL_V(body->get_space(), false);
ERR_FAIL_COND_V(body->get_space()->is_locked(), false);
_update_shapes();
diff --git a/servers/physics_3d/godot_area_3d.cpp b/servers/physics_3d/godot_area_3d.cpp
index 5bf16aa688..d0b287b058 100644
--- a/servers/physics_3d/godot_area_3d.cpp
+++ b/servers/physics_3d/godot_area_3d.cpp
@@ -87,12 +87,6 @@ void GodotArea3D::set_space(GodotSpace3D *p_space) {
}
void GodotArea3D::set_monitor_callback(const Callable &p_callback) {
- ObjectID id = p_callback.get_object_id();
- if (id == monitor_callback.get_object_id()) {
- monitor_callback = p_callback;
- return;
- }
-
_unregister_shapes();
monitor_callback = p_callback;
@@ -108,12 +102,6 @@ void GodotArea3D::set_monitor_callback(const Callable &p_callback) {
}
void GodotArea3D::set_area_monitor_callback(const Callable &p_callback) {
- ObjectID id = p_callback.get_object_id();
- if (id == area_monitor_callback.get_object_id()) {
- area_monitor_callback = p_callback;
- return;
- }
-
_unregister_shapes();
area_monitor_callback = p_callback;
@@ -223,7 +211,7 @@ Variant GodotArea3D::get_param(PhysicsServer3D::AreaParameter p_param) const {
}
void GodotArea3D::_queue_monitor_update() {
- ERR_FAIL_COND(!get_space());
+ ERR_FAIL_NULL(get_space());
if (!monitor_query_list.in_list()) {
get_space()->area_add_to_monitor_query_list(&monitor_query_list);
diff --git a/servers/physics_3d/godot_body_3d.cpp b/servers/physics_3d/godot_body_3d.cpp
index 21d5e49828..e102d0f3c9 100644
--- a/servers/physics_3d/godot_body_3d.cpp
+++ b/servers/physics_3d/godot_body_3d.cpp
@@ -477,7 +477,7 @@ void GodotBody3D::integrate_forces(real_t p_step) {
return;
}
- ERR_FAIL_COND(!get_space());
+ ERR_FAIL_NULL(get_space());
int ac = areas.size();
@@ -674,7 +674,7 @@ void GodotBody3D::integrate_velocities(real_t p_step) {
return;
}
- if (fi_callback_data || body_state_callback.get_object()) {
+ if (fi_callback_data || body_state_callback.is_valid()) {
get_space()->body_add_to_state_query_list(&direct_state_query_list);
}
@@ -759,7 +759,7 @@ void GodotBody3D::call_queries() {
Variant direct_state_variant = get_direct_state();
if (fi_callback_data) {
- if (!fi_callback_data->callable.get_object()) {
+ if (!fi_callback_data->callable.is_valid()) {
set_force_integration_callback(Callable());
} else {
const Variant *vp[2] = { &direct_state_variant, &fi_callback_data->udata };
@@ -771,7 +771,7 @@ void GodotBody3D::call_queries() {
}
}
- if (body_state_callback.get_object()) {
+ if (body_state_callback.is_valid()) {
body_state_callback.call(direct_state_variant);
}
}
@@ -798,7 +798,7 @@ void GodotBody3D::set_state_sync_callback(const Callable &p_callable) {
}
void GodotBody3D::set_force_integration_callback(const Callable &p_callable, const Variant &p_udata) {
- if (p_callable.get_object()) {
+ if (p_callable.is_valid()) {
if (!fi_callback_data) {
fi_callback_data = memnew(ForceIntegrationCallbackData);
}
diff --git a/servers/physics_3d/godot_physics_server_3d.cpp b/servers/physics_3d/godot_physics_server_3d.cpp
index 4118a19f10..8a7b4e0f07 100644
--- a/servers/physics_3d/godot_physics_server_3d.cpp
+++ b/servers/physics_3d/godot_physics_server_3d.cpp
@@ -912,7 +912,7 @@ void GodotPhysicsServer3D::body_set_ray_pickable(RID p_body, bool p_enable) {
bool GodotPhysicsServer3D::body_test_motion(RID p_body, const MotionParameters &p_parameters, MotionResult *r_result) {
GodotBody3D *body = body_owner.get_or_null(p_body);
ERR_FAIL_NULL_V(body, false);
- ERR_FAIL_COND_V(!body->get_space(), false);
+ ERR_FAIL_NULL_V(body->get_space(), false);
ERR_FAIL_COND_V(body->get_space()->is_locked(), false);
_update_shapes();
@@ -1225,7 +1225,7 @@ void GodotPhysicsServer3D::joint_make_pin(RID p_joint, RID p_body_A, const Vecto
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
@@ -1297,7 +1297,7 @@ void GodotPhysicsServer3D::joint_make_hinge(RID p_joint, RID p_body_A, const Tra
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
@@ -1321,7 +1321,7 @@ void GodotPhysicsServer3D::joint_make_hinge_simple(RID p_joint, RID p_body_A, co
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
@@ -1422,7 +1422,7 @@ void GodotPhysicsServer3D::joint_make_slider(RID p_joint, RID p_body_A, const Tr
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
@@ -1462,7 +1462,7 @@ void GodotPhysicsServer3D::joint_make_cone_twist(RID p_joint, RID p_body_A, cons
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
@@ -1502,7 +1502,7 @@ void GodotPhysicsServer3D::joint_make_generic_6dof(RID p_joint, RID p_body_A, co
ERR_FAIL_NULL(body_A);
if (!p_body_B.is_valid()) {
- ERR_FAIL_COND(!body_A->get_space());
+ ERR_FAIL_NULL(body_A->get_space());
p_body_B = body_A->get_space()->get_static_global_body();
}
diff --git a/servers/physics_3d/godot_soft_body_3d.cpp b/servers/physics_3d/godot_soft_body_3d.cpp
index 20b19ab243..0c2b935554 100644
--- a/servers/physics_3d/godot_soft_body_3d.cpp
+++ b/servers/physics_3d/godot_soft_body_3d.cpp
@@ -967,7 +967,7 @@ Vector3 GodotSoftBody3D::_compute_area_windforce(const GodotArea3D *p_area, cons
void GodotSoftBody3D::predict_motion(real_t p_delta) {
const real_t inv_delta = 1.0 / p_delta;
- ERR_FAIL_COND(!get_space());
+ ERR_FAIL_NULL(get_space());
bool gravity_done = false;
Vector3 gravity;
diff --git a/servers/rendering/dummy/rasterizer_canvas_dummy.h b/servers/rendering/dummy/rasterizer_canvas_dummy.h
index 455a669277..862b941a73 100644
--- a/servers/rendering/dummy/rasterizer_canvas_dummy.h
+++ b/servers/rendering/dummy/rasterizer_canvas_dummy.h
@@ -55,6 +55,8 @@ public:
bool free(RID p_rid) override { return true; }
void update() override {}
+ virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) override {}
+
RasterizerCanvasDummy() {}
~RasterizerCanvasDummy() {}
};
diff --git a/servers/rendering/dummy/storage/particles_storage.h b/servers/rendering/dummy/storage/particles_storage.h
index a40c96a8f5..33dad3f2f4 100644
--- a/servers/rendering/dummy/storage/particles_storage.h
+++ b/servers/rendering/dummy/storage/particles_storage.h
@@ -47,6 +47,7 @@ public:
virtual void particles_emit(RID p_particles, const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) override {}
virtual void particles_set_emitting(RID p_particles, bool p_emitting) override {}
virtual void particles_set_amount(RID p_particles, int p_amount) override {}
+ virtual void particles_set_amount_ratio(RID p_particles, float p_amount_ratio) override {}
virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override {}
virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override {}
virtual void particles_set_pre_process_time(RID p_particles, double p_time) override {}
@@ -81,6 +82,8 @@ public:
virtual AABB particles_get_aabb(RID p_particles) const override { return AABB(); }
virtual void particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) override {}
+ virtual void particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) override {}
+ virtual void particles_set_interp_to_end(RID p_particles, float p_interp) override {}
virtual bool particles_get_emitting(RID p_particles) override { return false; }
virtual int particles_get_draw_passes(RID p_particles) const override { return 0; }
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index d6040e8820..c5206017f7 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -30,6 +30,7 @@
#include "renderer_canvas_cull.h"
+#include "core/config/project_settings.h"
#include "core/math/geometry_2d.h"
#include "renderer_viewport.h"
#include "rendering_server_default.h"
@@ -1557,6 +1558,11 @@ void RendererCanvasCull::canvas_item_clear(RID p_item) {
ERR_FAIL_NULL(canvas_item);
canvas_item->clear();
+#ifdef DEBUG_ENABLED
+ if (debug_redraw) {
+ canvas_item->debug_redraw_time = debug_redraw_time;
+ }
+#endif
}
void RendererCanvasCull::canvas_item_set_draw_index(RID p_item, int p_index) {
@@ -1612,6 +1618,15 @@ void RendererCanvasCull::canvas_item_set_visibility_notifier(RID p_item, bool p_
}
}
+void RendererCanvasCull::canvas_item_set_debug_redraw(bool p_enabled) {
+ debug_redraw = p_enabled;
+ RSG::canvas_render->set_debug_redraw(p_enabled, debug_redraw_time, debug_redraw_color);
+}
+
+bool RendererCanvasCull::canvas_item_get_debug_redraw() const {
+ return debug_redraw;
+}
+
void RendererCanvasCull::canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin, bool p_fit_empty, float p_fit_margin, bool p_blur_mipmaps) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
@@ -2151,6 +2166,9 @@ RendererCanvasCull::RendererCanvasCull() {
z_last_list = (RendererCanvasRender::Item **)memalloc(z_range * sizeof(RendererCanvasRender::Item *));
disable_scale = false;
+
+ debug_redraw_time = GLOBAL_DEF("debug/canvas_items/debug_redraw_time", 1.0);
+ debug_redraw_color = GLOBAL_DEF("debug/canvas_items/debug_redraw_color", Color(1.0, 0.2, 0.2, 0.5));
}
RendererCanvasCull::~RendererCanvasCull() {
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index 4f11d2c7b1..0f51abbb26 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -174,6 +174,10 @@ public:
bool sdf_used = false;
bool snapping_2d_transforms_to_pixel = false;
+ bool debug_redraw = false;
+ double debug_redraw_time = 0;
+ Color debug_redraw_color;
+
PagedAllocator<Item::VisibilityNotifierData> visibility_notifier_allocator;
SelfList<Item::VisibilityNotifierData>::List visibility_notifier_list;
@@ -260,6 +264,9 @@ public:
void canvas_item_set_canvas_group_mode(RID p_item, RS::CanvasGroupMode p_mode, float p_clear_margin = 5.0, bool p_fit_empty = false, float p_fit_margin = 0.0, bool p_blur_mipmaps = false);
+ void canvas_item_set_debug_redraw(bool p_enabled);
+ bool canvas_item_get_debug_redraw() const;
+
RID canvas_light_allocate();
void canvas_light_initialize(RID p_rid);
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index c30e53c29e..ef4de9ce54 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -358,6 +358,9 @@ public:
Command *last_command = nullptr;
Vector<CommandBlock> blocks;
uint32_t current_block;
+#ifdef DEBUG_ENABLED
+ mutable double debug_redraw_time = 0;
+#endif
template <class T>
T *alloc_command() {
@@ -517,6 +520,8 @@ public:
virtual bool free(RID p_rid) = 0;
virtual void update() = 0;
+ virtual void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) = 0;
+
RendererCanvasRender() { singleton = this; }
virtual ~RendererCanvasRender() {}
};
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 885a00856e..ecf2c29956 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -485,7 +485,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
continue;
}
- push_constant.flags = base_flags | (push_constant.flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); //reset on each command for sanity, keep canvastexture binding config
+ push_constant.flags = base_flags | (push_constant.flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); // Reset on each command for safety, keep canvastexture binding config.
switch (c->type) {
case Item::Command::TYPE_RECT: {
@@ -957,7 +957,48 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
c = c->next;
}
+#ifdef DEBUG_ENABLED
+ if (debug_redraw && p_item->debug_redraw_time > 0.0) {
+ Color dc = debug_redraw_color;
+ dc.a *= p_item->debug_redraw_time / debug_redraw_time;
+ RID pipeline = pipeline_variants->variants[PIPELINE_LIGHT_MODE_DISABLED][PIPELINE_VARIANT_QUAD].get_render_pipeline(RD::INVALID_ID, p_framebuffer_format);
+ RD::get_singleton()->draw_list_bind_render_pipeline(p_draw_list, pipeline);
+
+ //bind textures
+
+ _bind_canvas_texture(p_draw_list, RID(), current_filter, current_repeat, last_texture, push_constant, texpixel_size);
+
+ Rect2 src_rect;
+ Rect2 dst_rect;
+
+ dst_rect = Rect2(Vector2(), p_item->rect.size);
+ src_rect = Rect2(0, 0, 1, 1);
+
+ push_constant.modulation[0] = dc.r;
+ push_constant.modulation[1] = dc.g;
+ push_constant.modulation[2] = dc.b;
+ push_constant.modulation[3] = dc.a;
+
+ push_constant.src_rect[0] = src_rect.position.x;
+ push_constant.src_rect[1] = src_rect.position.y;
+ push_constant.src_rect[2] = src_rect.size.width;
+ push_constant.src_rect[3] = src_rect.size.height;
+
+ push_constant.dst_rect[0] = dst_rect.position.x;
+ push_constant.dst_rect[1] = dst_rect.position.y;
+ push_constant.dst_rect[2] = dst_rect.size.width;
+ push_constant.dst_rect[3] = dst_rect.size.height;
+
+ RD::get_singleton()->draw_list_set_push_constant(p_draw_list, &push_constant, sizeof(PushConstant));
+ RD::get_singleton()->draw_list_bind_index_array(p_draw_list, shader.quad_index_array);
+ RD::get_singleton()->draw_list_draw(p_draw_list, true);
+
+ p_item->debug_redraw_time -= RSG::rasterizer->get_frame_delta_time();
+
+ RenderingServerDefault::redraw_request();
+ }
+#endif
if (current_clip && reclip) {
//will make it re-enable clipping if needed afterwards
current_clip = nullptr;
@@ -2742,6 +2783,12 @@ void RendererCanvasRenderRD::set_shadow_texture_size(int p_size) {
}
}
+void RendererCanvasRenderRD::set_debug_redraw(bool p_enabled, double p_time, const Color &p_color) {
+ debug_redraw = p_enabled;
+ debug_redraw_time = p_time;
+ debug_redraw_color = p_color;
+}
+
RendererCanvasRenderRD::~RendererCanvasRenderRD() {
RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
//canvas state
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index 4c8cbd1c9f..7dbcd903e6 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -418,6 +418,10 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
RID _create_base_uniform_set(RID p_to_render_target, bool p_backbuffer);
+ bool debug_redraw = false;
+ Color debug_redraw_color;
+ double debug_redraw_time = 1.0;
+
inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead.
void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used);
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false);
@@ -450,6 +454,8 @@ public:
virtual void set_shadow_texture_size(int p_size);
+ void set_debug_redraw(bool p_enabled, double p_time, const Color &p_color);
+
void set_time(double p_time);
void update();
bool free(RID p_rid);
diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
index 1e01d94533..fecf70bd01 100644
--- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
@@ -184,7 +184,7 @@ void main() {
vec4 test_normal_roughness = imageLoad(source_normal_roughness, test_pos);
vec3 test_normal = test_normal_roughness.xyz * 2.0 - 1.0;
test_normal = normalize(test_normal);
- test_normal.y = -test_normal.y; //because this code reads flipped
+ test_normal.y = -test_normal.y; // Because this code reads flipped.
if (dot(ray_dir, test_normal) < 0.001) {
// if depth was surpassed
@@ -203,6 +203,7 @@ void main() {
if (found) {
float margin_blend = 1.0;
+ vec2 final_pos = pos;
vec2 margin = vec2((params.screen_size.x + params.screen_size.y) * 0.05); // make a uniform margin
if (any(bvec4(lessThan(pos, vec2(0.0, 0.0)), greaterThan(pos, params.screen_size)))) {
@@ -219,16 +220,40 @@ void main() {
//margin_blend = 1.0;
}
- vec2 final_pos;
+ // Fade In / Fade Out
float grad = (steps_taken + 1.0) / float(params.num_steps);
float initial_fade = params.curve_fade_in == 0.0 ? 1.0 : pow(clamp(grad, 0.0, 1.0), params.curve_fade_in);
float fade = pow(clamp(1.0 - grad, 0.0, 1.0), params.distance_fade) * initial_fade;
+
+ // Ensure that precision errors do not introduce any fade. Even if it is just slightly below 1.0,
+ // strong specular light can leak through the reflection.
+ if (fade > 0.999) {
+ fade = 1.0;
+ }
+
// This is an ad-hoc term to fade out the SSR as roughness increases. Values used
// are meant to match the visual appearance of a ReflectionProbe.
float roughness_fade = smoothstep(0.4, 0.7, 1.0 - normal_roughness.w);
- final_pos = pos;
- vec4 final_color;
+ // Schlick term.
+ float metallic = texelFetch(source_metallic, ssC << 1, 0).w;
+
+ // F0 is the reflectance of normally incident light (perpendicular to the surface).
+ // Dielectric materials have a widely accepted default value of 0.04. We assume that metals reflect all light, so their F0 is 1.0.
+ float f0 = mix(0.04, 1.0, metallic);
+ float m = clamp(1.0 - dot(normal, -view_dir), 0.0, 1.0);
+ float m2 = m * m;
+ m = m2 * m2 * m; // pow(m,5)
+ float fresnel_term = f0 + (1.0 - f0) * m; // Fresnel Schlick term.
+
+ // The alpha value of final_color controls the blending with specular light in specular_merge.glsl.
+ // Note that the Fresnel term is multiplied with the RGB color instead of being a part of the alpha value.
+ // There is a key difference:
+ // - multiplying a term with RGB darkens the SSR light without introducing/taking away specular light.
+ // - combining a term into the Alpha value introduces specular light at the expense of the SSR light.
+ vec4 final_color = vec4(imageLoad(source_diffuse, ivec2(final_pos - 0.5)).rgb * fresnel_term, fade * margin_blend * roughness_fade);
+
+ imageStore(ssr_image, ssC, final_color);
#ifdef MODE_ROUGH
@@ -259,20 +284,6 @@ void main() {
#endif // MODE_ROUGH
- final_color = vec4(imageLoad(source_diffuse, ivec2(final_pos - 0.5)).rgb, fade * margin_blend * roughness_fade);
-
- // Schlick term.
- float metallic = texelFetch(source_metallic, ssC << 1, 0).w;
- // F0 is the reflectance of normally incident light (perpendicular to the surface).
- // Dielectric materials have a widely accepted default value of 0.04. We assume that metals reflect all light, so their F0 is 1.0.
- float f0 = mix(0.04, 1.0, metallic);
- float m = clamp(1.0 - dot(normal, -view_dir), 0.0, 1.0);
- float m2 = m * m;
- m = m2 * m2 * m; // pow(m,5)
- final_color.a *= f0 + (1.0 - f0) * m; // Fresnel Schlick term.
-
- imageStore(ssr_image, ssC, final_color);
-
} else {
#ifdef MODE_ROUGH
imageStore(blur_radius_image, ssC, vec4(0.0));
diff --git a/servers/rendering/renderer_rd/shaders/particles.glsl b/servers/rendering/renderer_rd/shaders/particles.glsl
index 7ba03a27f7..5fa4154727 100644
--- a/servers/rendering/renderer_rd/shaders/particles.glsl
+++ b/servers/rendering/renderer_rd/shaders/particles.glsl
@@ -67,7 +67,7 @@ struct FrameParams {
float delta;
uint frame;
- uint pad0;
+ float amount_ratio;
uint pad1;
uint pad2;
@@ -77,6 +77,8 @@ struct FrameParams {
float particle_size;
mat4 emission_transform;
+ vec3 emitter_velocity;
+ float interp_to_end;
Attractor attractors[MAX_ATTRACTORS];
Collider colliders[MAX_COLLIDERS];
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index 3bd35c53cc..2e8c9d7f8e 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -2115,7 +2115,7 @@ void MaterialStorage::material_set_shader(RID p_material, RID p_shader) {
return;
}
- ERR_FAIL_COND(shader->data == nullptr);
+ ERR_FAIL_NULL(shader->data);
material->data = material_data_request_func[shader->type](shader->data);
material->data->self = p_material;
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
index bf5a597bb9..3065da4d05 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
@@ -72,6 +72,7 @@ ParticlesStorage::ParticlesStorage() {
actions.renames["ACTIVE"] = "particle_active";
actions.renames["RESTART"] = "restart";
actions.renames["CUSTOM"] = "PARTICLE.custom";
+ actions.renames["AMOUNT_RATIO"] = "FRAME.amount_ratio";
for (int i = 0; i < ParticlesShader::MAX_USERDATAS; i++) {
String udname = "USERDATA" + itos(i + 1);
actions.renames[udname] = "PARTICLE.userdata" + itos(i + 1);
@@ -88,6 +89,8 @@ ParticlesStorage::ParticlesStorage() {
actions.renames["INDEX"] = "index";
//actions.renames["GRAVITY"] = "current_gravity";
actions.renames["EMISSION_TRANSFORM"] = "FRAME.emission_transform";
+ actions.renames["EMITTER_VELOCITY"] = "FRAME.emitter_velocity";
+ actions.renames["INTERPOLATE_TO_END"] = "FRAME.interp_to_end";
actions.renames["RANDOM_SEED"] = "FRAME.random_seed";
actions.renames["FLAG_EMIT_POSITION"] = "EMISSION_FLAG_HAS_POSITION";
actions.renames["FLAG_EMIT_ROT_SCALE"] = "EMISSION_FLAG_HAS_ROTATION_SCALE";
@@ -329,6 +332,13 @@ void ParticlesStorage::particles_set_amount(RID p_particles, int p_amount) {
particles->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_PARTICLES);
}
+void ParticlesStorage::particles_set_amount_ratio(RID p_particles, float p_amount_ratio) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->amount_ratio = p_amount_ratio;
+}
+
void ParticlesStorage::particles_set_lifetime(RID p_particles, double p_lifetime) {
Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL(particles);
@@ -651,6 +661,20 @@ void ParticlesStorage::particles_set_emission_transform(RID p_particles, const T
particles->emission_transform = p_transform;
}
+void ParticlesStorage::particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->emitter_velocity = p_velocity;
+}
+
+void ParticlesStorage::particles_set_interp_to_end(RID p_particles, float p_interp) {
+ Particles *particles = particles_owner.get_or_null(p_particles);
+ ERR_FAIL_NULL(particles);
+
+ particles->interp_to_end = p_interp;
+}
+
int ParticlesStorage::particles_get_draw_passes(RID p_particles) const {
const Particles *particles = particles_owner.get_or_null(p_particles);
ERR_FAIL_NULL_V(particles, 0);
@@ -791,9 +815,13 @@ void ParticlesStorage::_particles_process(Particles *p_particles, double p_delta
frame_params.cycle = p_particles->cycle_number;
frame_params.frame = p_particles->frame_counter++;
- frame_params.pad0 = 0;
+ frame_params.amount_ratio = p_particles->amount_ratio;
frame_params.pad1 = 0;
frame_params.pad2 = 0;
+ frame_params.emitter_velocity[0] = p_particles->emitter_velocity.x;
+ frame_params.emitter_velocity[1] = p_particles->emitter_velocity.y;
+ frame_params.emitter_velocity[2] = p_particles->emitter_velocity.z;
+ frame_params.interp_to_end = p_particles->interp_to_end;
{ //collision and attractors
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
index b93932f482..435ef14d75 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
@@ -123,7 +123,7 @@ private:
float delta;
uint32_t frame;
- uint32_t pad0;
+ float amount_ratio;
uint32_t pad1;
uint32_t pad2;
@@ -134,6 +134,9 @@ private:
float emission_transform[16];
+ float emitter_velocity[3];
+ float interp_to_end;
+
Attractor attractors[MAX_ATTRACTORS];
Collider colliders[MAX_COLLIDERS];
};
@@ -235,6 +238,9 @@ private:
bool force_sub_emit = false;
Transform3D emission_transform;
+ Vector3 emitter_velocity;
+ float interp_to_end = 0.0;
+ float amount_ratio = 1.0;
Vector<uint8_t> emission_buffer_data;
@@ -425,6 +431,7 @@ public:
virtual void particles_set_mode(RID p_particles, RS::ParticlesMode p_mode) override;
virtual void particles_set_emitting(RID p_particles, bool p_emitting) override;
virtual void particles_set_amount(RID p_particles, int p_amount) override;
+ virtual void particles_set_amount_ratio(RID p_particles, float p_amount_ratio) override;
virtual void particles_set_lifetime(RID p_particles, double p_lifetime) override;
virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) override;
virtual void particles_set_pre_process_time(RID p_particles, double p_time) override;
@@ -460,6 +467,8 @@ public:
virtual AABB particles_get_aabb(RID p_particles) const override;
virtual void particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) override;
+ virtual void particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) override;
+ virtual void particles_set_interp_to_end(RID p_particles, float p_interp_to_end) override;
virtual bool particles_get_emitting(RID p_particles) override;
virtual int particles_get_draw_passes(RID p_particles) const override;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 9ad2175332..3d75ced3e3 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -490,6 +490,7 @@ public:
FUNC2(particles_set_emitting, RID, bool)
FUNC1R(bool, particles_get_emitting, RID)
FUNC2(particles_set_amount, RID, int)
+ FUNC2(particles_set_amount_ratio, RID, float)
FUNC2(particles_set_lifetime, RID, double)
FUNC2(particles_set_one_shot, RID, bool)
FUNC2(particles_set_pre_process_time, RID, double)
@@ -521,6 +522,8 @@ public:
FUNC1R(AABB, particles_get_current_aabb, RID)
FUNC2(particles_set_emission_transform, RID, const Transform3D &)
+ FUNC2(particles_set_emitter_velocity, RID, const Vector3 &)
+ FUNC2(particles_set_interp_to_end, RID, float)
/* PARTICLES COLLISION */
@@ -891,6 +894,9 @@ public:
FUNC6(canvas_item_set_canvas_group_mode, RID, CanvasGroupMode, float, bool, float, bool)
+ FUNC1(canvas_item_set_debug_redraw, bool)
+ FUNC0RC(bool, canvas_item_get_debug_redraw)
+
FUNCRIDSPLIT(canvas_light)
FUNC2(canvas_light_set_mode, RID, CanvasLightMode)
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 34ffc1e90f..41f2fbb03f 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1377,7 +1377,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
if (p_allow_reassign) {
break;
}
- ERR_FAIL_COND_V(!p_block->parent_block, false);
+ ERR_FAIL_NULL_V(p_block->parent_block, false);
p_block = p_block->parent_block;
}
}
@@ -5418,7 +5418,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
if (error) {
- _set_error(vformat(RTR("A constant value cannot be passed for '%s' parameter."), _get_qualifier_str(arg_qual)));
+ _set_error(vformat(RTR("A constant value cannot be passed for the '%s' parameter."), _get_qualifier_str(arg_qual)));
return nullptr;
}
}
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index 38574b0597..a6b4646f43 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -348,6 +348,8 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["NUMBER"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["INDEX"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["EMISSION_TRANSFORM"] = constt(ShaderLanguage::TYPE_MAT4);
+ shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["EMITTER_VELOCITY"] = constt(ShaderLanguage::TYPE_VEC3);
+ shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["INTERPOLATE_TO_END"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["RANDOM_SEED"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["FLAG_EMIT_POSITION"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["FLAG_EMIT_ROT_SCALE"] = constt(ShaderLanguage::TYPE_UINT);
@@ -359,6 +361,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["RESTART_VELOCITY"] = constt(ShaderLanguage::TYPE_BOOL);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["RESTART_COLOR"] = constt(ShaderLanguage::TYPE_BOOL);
shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["RESTART_CUSTOM"] = constt(ShaderLanguage::TYPE_BOOL);
+ shader_modes[RS::SHADER_PARTICLES].functions["start"].built_ins["AMOUNT_RATIO"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[RS::SHADER_PARTICLES].functions["start"].main_function = true;
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["COLOR"] = ShaderLanguage::TYPE_VEC4;
@@ -379,6 +382,8 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["NUMBER"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["INDEX"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["EMISSION_TRANSFORM"] = constt(ShaderLanguage::TYPE_MAT4);
+ shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["EMITTER_VELOCITY"] = constt(ShaderLanguage::TYPE_VEC3);
+ shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["INTERPOLATE_TO_END"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["RANDOM_SEED"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["FLAG_EMIT_POSITION"] = constt(ShaderLanguage::TYPE_UINT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["FLAG_EMIT_ROT_SCALE"] = constt(ShaderLanguage::TYPE_UINT);
@@ -389,6 +394,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["COLLISION_NORMAL"] = constt(ShaderLanguage::TYPE_VEC3);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["COLLISION_DEPTH"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["ATTRACTOR_FORCE"] = constt(ShaderLanguage::TYPE_VEC3);
+ shader_modes[RS::SHADER_PARTICLES].functions["process"].built_ins["AMOUNT_RATIO"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[RS::SHADER_PARTICLES].functions["process"].main_function = true;
{
diff --git a/servers/rendering/storage/particles_storage.h b/servers/rendering/storage/particles_storage.h
index 78c616f6d5..4f33de912c 100644
--- a/servers/rendering/storage/particles_storage.h
+++ b/servers/rendering/storage/particles_storage.h
@@ -49,6 +49,7 @@ public:
virtual bool particles_get_emitting(RID p_particles) = 0;
virtual void particles_set_amount(RID p_particles, int p_amount) = 0;
+ virtual void particles_set_amount_ratio(RID p_particles, float p_amount_ratio) = 0;
virtual void particles_set_lifetime(RID p_particles, double p_lifetime) = 0;
virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0;
virtual void particles_set_pre_process_time(RID p_particles, double p_time) = 0;
@@ -85,6 +86,8 @@ public:
virtual AABB particles_get_aabb(RID p_particles) const = 0;
virtual void particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) = 0;
+ virtual void particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) = 0;
+ virtual void particles_set_interp_to_end(RID p_particles, float p_interp_to_end) = 0;
virtual int particles_get_draw_passes(RID p_particles) const = 0;
virtual RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const = 0;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 94ff59f7e1..c5adb6cdf6 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -1833,9 +1833,7 @@ static RS::SurfaceData _dict_to_surf(const Dictionary &p_dictionary) {
RS::SurfaceData sd;
sd.primitive = RS::PrimitiveType(int(p_dictionary["primitive"]));
- if (p_dictionary.has("uv_scale")) {
- sd.format = p_dictionary["format"];
- }
+ sd.format = p_dictionary["format"];
sd.vertex_data = p_dictionary["vertex_data"];
if (p_dictionary.has("attribute_data")) {
sd.attribute_data = p_dictionary["attribute_data"];
@@ -1853,7 +1851,9 @@ static RS::SurfaceData _dict_to_surf(const Dictionary &p_dictionary) {
}
sd.aabb = p_dictionary["aabb"];
- sd.uv_scale = p_dictionary["uv_scale"];
+ if (p_dictionary.has("uv_scale")) {
+ sd.uv_scale = p_dictionary["uv_scale"];
+ }
if (p_dictionary.has("lods")) {
Array lods = p_dictionary["lods"];
@@ -2497,11 +2497,14 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("particles_set_emitting", "particles", "emitting"), &RenderingServer::particles_set_emitting);
ClassDB::bind_method(D_METHOD("particles_get_emitting", "particles"), &RenderingServer::particles_get_emitting);
ClassDB::bind_method(D_METHOD("particles_set_amount", "particles", "amount"), &RenderingServer::particles_set_amount);
+ ClassDB::bind_method(D_METHOD("particles_set_amount_ratio", "particles", "ratio"), &RenderingServer::particles_set_amount_ratio);
ClassDB::bind_method(D_METHOD("particles_set_lifetime", "particles", "lifetime"), &RenderingServer::particles_set_lifetime);
ClassDB::bind_method(D_METHOD("particles_set_one_shot", "particles", "one_shot"), &RenderingServer::particles_set_one_shot);
ClassDB::bind_method(D_METHOD("particles_set_pre_process_time", "particles", "time"), &RenderingServer::particles_set_pre_process_time);
ClassDB::bind_method(D_METHOD("particles_set_explosiveness_ratio", "particles", "ratio"), &RenderingServer::particles_set_explosiveness_ratio);
ClassDB::bind_method(D_METHOD("particles_set_randomness_ratio", "particles", "ratio"), &RenderingServer::particles_set_randomness_ratio);
+ ClassDB::bind_method(D_METHOD("particles_set_interp_to_end", "particles", "factor"), &RenderingServer::particles_set_interp_to_end);
+ ClassDB::bind_method(D_METHOD("particles_set_emitter_velocity", "particles", "velocity"), &RenderingServer::particles_set_emitter_velocity);
ClassDB::bind_method(D_METHOD("particles_set_custom_aabb", "particles", "aabb"), &RenderingServer::particles_set_custom_aabb);
ClassDB::bind_method(D_METHOD("particles_set_speed_scale", "particles", "scale"), &RenderingServer::particles_set_speed_scale);
ClassDB::bind_method(D_METHOD("particles_set_use_local_coordinates", "particles", "enable"), &RenderingServer::particles_set_use_local_coordinates);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 9d186f086c..ba8cc3ba80 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -660,6 +660,7 @@ public:
virtual void particles_set_emitting(RID p_particles, bool p_enable) = 0;
virtual bool particles_get_emitting(RID p_particles) = 0;
virtual void particles_set_amount(RID p_particles, int p_amount) = 0;
+ virtual void particles_set_amount_ratio(RID p_particles, float p_amount_ratio) = 0;
virtual void particles_set_lifetime(RID p_particles, double p_lifetime) = 0;
virtual void particles_set_one_shot(RID p_particles, bool p_one_shot) = 0;
virtual void particles_set_pre_process_time(RID p_particles, double p_time) = 0;
@@ -717,6 +718,8 @@ public:
virtual AABB particles_get_current_aabb(RID p_particles) = 0;
virtual void particles_set_emission_transform(RID p_particles, const Transform3D &p_transform) = 0; // This is only used for 2D, in 3D it's automatic.
+ virtual void particles_set_emitter_velocity(RID p_particles, const Vector3 &p_velocity) = 0;
+ virtual void particles_set_interp_to_end(RID p_particles, float p_interp) = 0;
/* PARTICLES COLLISION API */
@@ -1409,6 +1412,9 @@ public:
virtual void canvas_item_set_canvas_group_mode(RID p_item, CanvasGroupMode p_mode, float p_clear_margin = 5.0, bool p_fit_empty = false, float p_fit_margin = 0.0, bool p_blur_mipmaps = false) = 0;
+ virtual void canvas_item_set_debug_redraw(bool p_enabled) = 0;
+ virtual bool canvas_item_get_debug_redraw() const = 0;
+
/* CANVAS LIGHT */
virtual RID canvas_light_create() = 0;
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index 27907b8bb5..e69ac448e4 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -3364,7 +3364,9 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
/* After update, pending add should not be counted, */
/* also does not work on col 0 */
+ int before_text_caret_column = code_edit->get_caret_column();
code_edit->insert_text_at_caret("i");
+
code_edit->update_code_completion_options();
code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), Ref<Resource>(), Color(1, 0, 0));
code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
@@ -3400,12 +3402,40 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
/* Set size for mouse input. */
code_edit->set_size(Size2(100, 100));
+ /* Test home and end keys close the completion and move the caret */
+ /* => ui_text_caret_line_start */
+ code_edit->set_caret_column(before_text_caret_column + 1);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), Ref<Resource>(), Color(1, 0, 0));
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_2.", "item_2");
+
+ code_edit->update_code_completion_options();
+
+ SEND_GUI_ACTION("ui_text_caret_line_start");
+ code_edit->update_code_completion_options();
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_caret_column() == 0);
+
+ /* => ui_text_caret_line_end */
+ code_edit->set_caret_column(before_text_caret_column + 1);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), Ref<Resource>(), Color(1, 0, 0));
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_2.", "item_2");
+
+ code_edit->update_code_completion_options();
+
+ SEND_GUI_ACTION("ui_text_caret_line_end");
+ code_edit->update_code_completion_options();
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_caret_column() == before_text_caret_column + 1);
+
/* Check input. */
- SEND_GUI_ACTION("ui_end");
- CHECK(code_edit->get_code_completion_selected_index() == 2);
+ code_edit->set_caret_column(before_text_caret_column + 1);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), Ref<Resource>(), Color(1, 0, 0));
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_2.", "item_2");
- SEND_GUI_ACTION("ui_home");
- CHECK(code_edit->get_code_completion_selected_index() == 0);
+ code_edit->update_code_completion_options();
SEND_GUI_ACTION("ui_page_down");
CHECK(code_edit->get_code_completion_selected_index() == 2);
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 7b2b20ec84..8a0f36a384 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -94,8 +94,8 @@ Files extracted from upstream source:
## clipper2
- Upstream: https://github.com/AngusJohnson/Clipper2
-- Version: 1.2.2 (756c5079aacab5837e812a143c59dc48a09f22e7, 2023)
-- License: Boost Software License 1.0
+- Version: 1.2.2 (756c5079aacab5837e812a143c59dc48a09f22e7, 2023)
+- License: BSL 1.0
Files extracted from upstream source:
@@ -111,7 +111,8 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- all .cpp, .h, and .txt files except the folders MakeTables and etc2packer.
+- All `.cpp` and `.h` files except the folders `MakeTables` and `etc2packer`
+- `LICENSE.txt`
Changes related to BC6H packing and unpacking made upstream in
https://github.com/elasota/cvtt/commit/2e4b6b2747aec11f4cc6dd09ef43fa8ce769f6e2
@@ -139,7 +140,7 @@ Files extracted from upstream source:
Files extracted from upstream:
-- All cpp files listed in `modules/raycast/godot_update_embree.py`
+- All `.cpp` files listed in `modules/raycast/godot_update_embree.py`
- All header files in the directories listed in `modules/raycast/godot_update_embree.py`
The `modules/raycast/godot_update_embree.py` script can be used to pull the
@@ -153,26 +154,26 @@ commits.
## enet
-- Upstream: http://enet.bespin.org
+- Upstream: https://github.com/lsalzman/enet
- Version: git (ea4607a90dbfbcf4da2669ea998585253d8e70b1, 2023)
- License: MIT
Files extracted from upstream source:
-- all .c files in the main directory (except unix.c win32.c)
-- the include/enet/ folder as enet/ (except unix.h win32.h)
-- LICENSE file
+- All `.c` files in the main directory (except `unix.c` and `win32.c`)
+- The `include/enet/` folder as `enet/` (except `unix.h` and `win32.h`)
+- `LICENSE` file
-Important: enet.h, host.c, protocol.c have been slightly modified
+Important: `enet.h`, `host.c`, `protocol.c` have been slightly modified
to be usable by Godot's socket implementation and allow IPv6 and DTLS.
Apply the patches in the `patches/` folder when syncing on newer upstream
commits.
-Three files (godot.cpp, enet/godot.h, enet/godot_ext.h) have been added to provide
-enet socket implementation using Godot classes.
+Three files (`godot.cpp`, `enet/godot.h`, `enet/godot_ext.h`) have been added to
+provide ENet socket implementation using Godot classes.
-It is still possible to build against a system wide ENet but doing so
-will limit its functionality to IPv4 only.
+It is still possible to build against a system wide ENet but doing so will limit
+its functionality to IPv4 only.
## etcpak
@@ -253,8 +254,12 @@ Files extracted from upstream source:
* Upstream: https://fonts.google.com/specimen/Open+Sans
* Version: 1.10 (downloaded from Google Fonts in February 2021)
* License: Apache 2.0
-- All fonts are converted from the unhinted `.ttf` sources using `https://github.com/google/woff2` tool.
-- Comment: Use UI font variant if available, because it has tight vertical metrics and good for UI.
+
+All fonts are converted from the unhinted `.ttf` sources using the
+`https://github.com/google/woff2` tool.
+
+Use UI font variant if available, because it has tight vertical metrics and good
+for UI.
## freetype
@@ -315,7 +320,7 @@ Files extracted from upstream source:
- Run `cmake . && make` and copy generated `include/glslang/build_info.h`
to `glslang/build_info.h`
- `LICENSE.txt`
-- Unnecessary files like `CMakeLists.txt`, `*.m4` and `updateGrammar` removed.
+- Unnecessary files like `CMakeLists.txt`, `*.m4` and `updateGrammar` removed
## graphite
@@ -326,8 +331,8 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- the `include` folder
-- the `src` folder (minus `CMakeLists.txt` and `files.mk`)
+- The `include` folder
+- The `src` folder (minus `CMakeLists.txt` and `files.mk`)
- `COPYING`
@@ -340,9 +345,9 @@ Files extracted from upstream source:
Files extracted from upstream source:
- `AUTHORS`, `COPYING`, `THANKS`
-- from the `src` folder, recursively
- - all the `*.c`, `*.cc`, `*.h`, `*.hh` files
- - _except_ `main.cc`, `harfbuzz*.cc`, `failing-alloc.c`, `test*.cc`, `hb-wasm*.*`
+- From the `src` folder, recursively:
+ - All the `.c`, `.cc`, `.h`, `.hh` files
+ - Except `main.cc`, `harfbuzz*.cc`, `failing-alloc.c`, `test*.cc`, `hb-wasm*.*`
## icu4c
@@ -353,22 +358,24 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- the `common` folder
+- The `common` folder
- `scriptset.*`, `ucln_in.*`, `uspoof.cpp"` and `uspoof_impl.cpp` from the `i18n` folder
- `uspoof.h` from the `i18n/unicode` folder
- `LICENSE`
Files generated from upstream source:
-- the `icudt73l.dat` built with the provided `godot_data.json` config file (see
+- The `icudt73l.dat` built with the provided `godot_data.json` config file (see
https://github.com/unicode-org/icu/blob/master/docs/userguide/icu_data/buildtool.md
for instructions).
-- Step 1: Download and extract both `icu4c-{version}-src.tgz` and `icu4c-{version}-data.zip` (replace `data` subfolder from the main source archive).
-- Step 2: Build ICU with default options - `./runConfigureICU {PLATFORM} && make`.
-- Step 3: Reconfigure ICU with custom data config - `ICU_DATA_FILTER_FILE={GODOT_SOURCE}/thirdparty/icu4c/godot_data.json ./runConfigureICU {PLATFORM} --with-data-packaging=common`.
-- Step 4: Delete `data/out` folder and rebuild data - `cd data && rm -rf ./out && make`.
-- Step 5: Copy `source/data/out/icudt73l.dat` to the `{GODOT_SOURCE}/thirdparty/icu4c/icudt73l.dat`.
+1. Download and extract both `icu4c-{version}-src.tgz` and `icu4c-{version}-data.zip`
+ (replace `data` subfolder from the main source archive)
+2. Build ICU with default options: `./runConfigureICU {PLATFORM} && make`
+3. Reconfigure ICU with custom data config:
+ `ICU_DATA_FILTER_FILE={GODOT_SOURCE}/thirdparty/icu4c/godot_data.json ./runConfigureICU {PLATFORM} --with-data-packaging=common`
+4. Delete `data/out` folder and rebuild data: `cd data && rm -rf ./out && make`
+5. Copy `source/data/out/icudt73l.dat` to the `{GODOT_SOURCE}/thirdparty/icu4c/icudt73l.dat`
## jpeg-compressor
@@ -422,8 +429,8 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- all .c and .h files of the main directory, except from
- `example.c` and `pngtest.c`
+- All `.c` and `.h` files of the main directory, except from `example.c` and
+ `pngtest.c`
- `arm/`, `intel/` and `powerpc/` folders
- `scripts/pnglibconf.h.prebuilt` as `pnglibconf.h`
- `LICENSE`
@@ -437,9 +444,9 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- all .c, .h in lib/, except arm/ and c64x/ folders
-- all .h files in include/theora/ as theora/
-- COPYING and LICENSE
+- All `.c` and `.h` files in `lib/`, except `arm/` and `c64x/` folders
+- All `.h` files in `include/theora/` as `theora/`
+- `COPYING` and `LICENSE`
## libvorbis
@@ -463,10 +470,11 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- `src/` and `sharpyuv/` except from: `.am`, `.rc` and `.in` files
+- `src/` and `sharpyuv/` except from `.am`, `.rc` and `.in` files
- `AUTHORS`, `COPYING`, `PATENTS`
-Patch `godot-node-debug-fix.patch` workarounds shadowing of godot's Node class in the MSVC debugger.
+Patch `godot-node-debug-fix.patch` workarounds shadowing of Godot's Node class
+in the MSVC debugger.
## mbedtls
@@ -477,14 +485,17 @@ Patch `godot-node-debug-fix.patch` workarounds shadowing of godot's Node class i
File extracted from upstream release tarball:
-- All `*.h` from `include/mbedtls/` to `thirdparty/mbedtls/include/mbedtls/` except `config_psa.h` and `psa_util.h`.
-- All `*.c` and `*.h` from `library/` to `thirdparty/mbedtls/library/` except those starting with `psa_*`.
-- The `LICENSE` file.
-- Applied the patch in `patches/windows-arm64-hardclock.diff`.
- Applied the patch in `aesni-no-arm-intrinsics.patch` to fix MSVC ARM build.
+- All `.h` from `include/mbedtls/` to `thirdparty/mbedtls/include/mbedtls/`
+ except `config_psa.h` and `psa_util.h`
+- All `.c` and `.h` from `library/` to `thirdparty/mbedtls/library/` except
+ those starting with `psa_*`
+- The `LICENSE` file
+- Applied the patch in `patches/windows-arm64-hardclock.diff`
+ Applied the patch in `aesni-no-arm-intrinsics.patch` to fix MSVC ARM build
- Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h`
- providing configuration for light bundling with core.
-- Added the file `godot_module_mbedtls_config.h` to customize the build configuration when bundling the full library.
+ providing configuration for light bundling with core
+- Added the file `godot_module_mbedtls_config.h` to customize the build
+ configuration when bundling the full library
## meshoptimizer
@@ -495,13 +506,13 @@ File extracted from upstream release tarball:
Files extracted from upstream repository:
-- All files in `src/`.
-- `LICENSE.md`.
+- All files in `src/`
+- `LICENSE.md`
An [experimental upstream feature](https://github.com/zeux/meshoptimizer/tree/simplify-attr),
-has been backported. On top of that, it was modified to report only distance error metrics
-instead of a combination of distance and attribute errors. Patches for both changes can be
-found in the `patches` directory.
+has been backported. On top of that, it was modified to report only distance
+error metrics instead of a combination of distance and attribute errors. Patches
+for both changes can be found in the `patches` directory.
## minimp3
@@ -571,10 +582,6 @@ Collection of single-file libraries used in Godot components.
* Upstream: https://github.com/ariya/FastLZ
* Version: 0.5.0 (4f20f54d46f5a6dd4fae4def134933369b7602d2, 2020)
* License: MIT
-- `hq2x.{cpp,h}`
- * Upstream: https://github.com/brunexgeek/hqx
- * Version: TBD, file structure differs
- * License: Apache 2.0
- `ifaddrs-android.{cc,h}`
* Upstream: https://chromium.googlesource.com/external/webrtc/stable/talk/+/master/base/ifaddrs-android.h
* Version: git (5976650443d68ccfadf1dea24999ee459dd2819d, 2013)
@@ -583,10 +590,6 @@ Collection of single-file libraries used in Godot components.
* Upstream: https://archive.blender.org/wiki/index.php/Dev:Shading/Tangent_Space_Normal_Maps/
* Version: 1.0 (2011)
* License: zlib
-- `FastNoiseLite.h}`
- * Upstream: https://github.com/Auburn/FastNoiseLite
- * Version: git (6be3d6bf7fb408de341285f9ee8a29b67fd953f1, 2022) + custom changes
- * License: MIT
- `ok_color.h`
* Upstream: https://github.com/bottosson/bottosson.github.io/blob/master/misc/ok_color.h
* Version: git (d69831edb90ffdcd08b7e64da3c5405acd48ad2c, 2022)
@@ -605,7 +608,7 @@ Collection of single-file libraries used in Godot components.
* Version: git (7bdffb428b2b19ad1c43aa44c714dcc104177e84, 2021)
* Modifications: Change from STL to Godot types (see provided patch).
* License: MIT
-- `r128.h`
+- `r128.{c,h}`
* Upstream: https://github.com/fahickman/r128
* Version: 1.4.4 (cf2e88fc3e7d7dfe99189686f914874cd0bda15e, 2020)
* License: Public Domain or Unlicense
@@ -614,7 +617,7 @@ Collection of single-file libraries used in Godot components.
* Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012)
* License: BSD-3-Clause
* Modifications: use `const char*` instead of `char*` for input string
-- `smolv.h`
+- `smolv.{cpp,h}`
* Upstream: https://github.com/aras-p/smol-v
* Version: git (4b52c165c13763051a18e80ffbc2ee436314ceb2, 2020)
* License: Public Domain or MIT
@@ -637,17 +640,33 @@ Collection of single-file libraries used in Godot components.
Files extracted from the upstream source:
- `msdfgen.h`
-- Files in `core/` folder.
+- Files in `core/` folder
- `LICENSE.txt`
+## noise
+
+- Upstream: https://github.com/Auburn/FastNoiseLite
+- Version: git (6be3d6bf7fb408de341285f9ee8a29b67fd953f1, 2022)
+- License: MIT
+
+Files extracted from the upstream source:
+
+- `FastNoiseLite.h`
+- `LICENSE`
+
+Some custom changes were made to fix compiler warnings, and can be re-applied
+with the provided patch.
+
+
## nvapi
- Upstream: http://download.nvidia.com/XFree86/nvapi-open-source-sdk
- Version: R525
- License: MIT
-- `nvapi_minimal.h` was created by using `nvapi.h` from upstream and removing unnecessary code.
+- `nvapi_minimal.h` was created by using `nvapi.h` from upstream and removing
+ unnecessary code.
## openxr
@@ -658,20 +677,21 @@ Files extracted from the upstream source:
Files extracted from upstream source:
-- include/
-- src/common/
-- src/loader/
-- src/*.{c,h}
-- src/external/jsoncpp/include/
-- src/external/jsoncpp/src/lib_json/
-- LICENSE and COPYING.adoc
+- `include/`
+- `src/common/`
+- `src/loader/`
+- `src/*.{c,h}`
+- `src/external/jsoncpp/include/`
+- `src/external/jsoncpp/src/lib_json/`
+- `LICENSE` and `COPYING.adoc`
Exclude:
-- src/external/android-jni-wrappers and src/external/jnipp (not used yet)
-- All CMake stuff: cmake/, CMakeLists.txt and *.cmake
-- All Gradle stuff: *gradle*, AndroidManifest.xml
-- All following files (and their .license files): *.{def,expsym,in,json,map,pom,rc,txt}
+- `src/external/android-jni-wrappers` and `src/external/jnipp` (not used yet)
+- All CMake stuff: `cmake/`, `CMakeLists.txt` and `*.cmake`
+- All Gradle stuff: `*gradle*`, `AndroidManifest.xml`
+- All following files (and their `.license` files):
+ `*.{def,expsym,in,json,map,pom,rc,txt}`
- All dotfiles
@@ -683,12 +703,12 @@ Exclude:
Files extracted from upstream source:
-- Files listed in the file NON-AUTOTOOLS-BUILD steps 1-4
-- All .h files in src/ apart from pcre2posix.h
-- src/pcre2_jit_match.c
-- src/pcre2_jit_misc.c
-- src/sljit/
-- AUTHORS and LICENCE
+- Files listed in the file `NON-AUTOTOOLS-BUILD` steps 1-4
+- All `.h` files in `src/` apart from `pcre2posix.h`
+- `src/pcre2_jit_match.c`
+- `src/pcre2_jit_misc.c`
+- `src/sljit/`
+- `AUTHORS` and `LICENCE`
A sljit patch from upstream was backported to fix macOS < 11.0 compilation
in 10.40, it can be found in the `patches` folder.
@@ -703,7 +723,7 @@ in 10.40, it can be found in the `patches` folder.
Files extracted from upstream source:
- `Recast/` folder without `CMakeLists.txt`
-- License.txt
+- `License.txt`
## rvo2
@@ -722,8 +742,8 @@ For 3D in `rvo2_3d` folder
Files extracted from upstream source:
-- All .cpp and .h files in the `src/` folder except for Export.h and RVO.h
-- LICENSE
+- All `.cpp` and `.h` files in the `src/` folder except for `Export.h` and `RVO.h`
+- `LICENSE`
Important: Nearly all files have Godot-made changes and renames
to make the 2D and 3D rvo libraries compatible with each other
@@ -760,7 +780,7 @@ folder.
Files extracted from upstream source:
-- all .cpp, .h and .inl files
+- All `.cpp`, `.h` and `.inl` files
Important: Some files have Godot-made changes.
They are marked with `// -- GODOT start --` and `// -- GODOT end --`
@@ -819,9 +839,9 @@ folder.
Unless there is a specific reason to package a more recent version, please stick
to tagged releases. All Vulkan libraries and headers should be kept in sync so:
-- Update Vulkan SDK components to the matching tag (see "vulkan").
-- Update glslang (see "glslang").
-- Update spirv-reflect (see "spirv-reflect").
+- Update Vulkan SDK components to the matching tag (see "vulkan")
+- Update glslang (see "glslang")
+- Update spirv-reflect (see "spirv-reflect")
Files extracted from upstream source:
@@ -861,11 +881,11 @@ Patches in the `patches` directory should be re-applied after updates.
File extracted from upstream release tarball:
-- Run `cmake .` to generate `config.h` and `wslayver.h`.
- Contents might need tweaking for Godot, review diff.
-- All `*.c` and `*.h` files from `lib/`
-- All `*.h` in `lib/includes/wslay/` as `wslay/`
-- `wslay/wslay.h` has a small Godot addition to fix MSVC build.
+- Run `cmake .` to generate `config.h` and `wslayver.h`
+ Contents might need tweaking for Godot, review diff
+- All `.c` and `.h` files from `lib/`
+- All `.h` in `lib/includes/wslay/` as `wslay/`
+- `wslay/wslay.h` has a small Godot addition to fix MSVC build
See `patches/msvcfix.diff`
- `COPYING`
@@ -890,7 +910,7 @@ Files extracted from upstream source:
Files extracted from upstream source:
-- All `*.c` and `*.h` files, minus `infback.c`
+- All `.c` and `.h` files, minus `infback.c`
- `LICENSE`
diff --git a/thirdparty/cvtt/etc_notes.txt b/thirdparty/cvtt/etc_notes.txt
deleted file mode 100644
index bb041a8435..0000000000
--- a/thirdparty/cvtt/etc_notes.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-The ETC1 compressor uses modified cluster fit:
-
-Assume that there exists an ideal base color and set of selectors for a given table.
-For a given table and set of selectors, the ideal base color can be determined by subtracting the offsets from each pixel and averaging them.
-Doing that is equivalent to subtracting the average offset from the average color.
-Because positive and negative selectors of the same magnitude cancel out, the search space of possible average offsets is reduced: 57 unique offsets for the first table and 81 for the others.
-Most of the offsets result in the same color as another average offset due to quantization of the base color, so those can be de-duplicated.
-So:
-- Start with a high-precision average color.
-- Apply precomputed luma offsets to it.
-- Quantize and de-duplicate the base colors.
-- Find the ideal selectors for each base color.
-
-Differential mode is solved by just finding the best legal combination from those attempts.
-
-There are several scenarios where this is not ideal:
-- Clamping behavior can sometimes be leveraged for a more accurate block.
-- Differentials can sometimes be moved slightly closer to become legal.
-- This only works when MSE is the error metric (i.e. not normal maps)
-- This only works when pixel weights are of equal importance (i.e. not using weight by alpha or edge deblocking)
-
-T and H mode just work by generating clustering assignments by computing a chrominance line and splitting the block in half by the chrominance midpoint and using those to determine the averages.
-
-Planar mode is just solved algebraically.
-
-If you want to emulate etc2comp's default settings, add the flag ETC_UseFakeBT709 to use its modified Rec. 709 error coefficients.
-Doing that will significantly slow down encoding because it requires much more complicated quantization math. \ No newline at end of file
diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp
index 37239e8462..ecf0a7e6dd 100644
--- a/thirdparty/enet/godot.cpp
+++ b/thirdparty/enet/godot.cpp
@@ -458,7 +458,7 @@ int enet_host_dtls_client_setup(ENetHost *host, const char *p_for_hostname, void
}
void enet_host_refuse_new_connections(ENetHost *host, int p_refuse) {
- ERR_FAIL_COND(!host->socket);
+ ERR_FAIL_NULL(host->socket);
((ENetGodotSocket *)host->socket)->set_refuse_new_connections(p_refuse);
}
diff --git a/thirdparty/misc/clipper-exceptions.patch b/thirdparty/misc/patches/clipper-exceptions.patch
index 537afd59b3..537afd59b3 100644
--- a/thirdparty/misc/clipper-exceptions.patch
+++ b/thirdparty/misc/patches/clipper-exceptions.patch
diff --git a/thirdparty/noise/FastNoise-LICENSE b/thirdparty/noise/LICENSE
index dd6df2c160..dd6df2c160 100644
--- a/thirdparty/noise/FastNoise-LICENSE
+++ b/thirdparty/noise/LICENSE