summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux_builds.yml2
-rw-r--r--COPYRIGHT.txt2
-rw-r--r--SConstruct6
-rw-r--r--core/config/engine.cpp6
-rw-r--r--core/config/engine.h6
-rw-r--r--core/config/project_settings.cpp18
-rw-r--r--core/core_bind.cpp15
-rw-r--r--core/core_bind.h3
-rw-r--r--core/error/error_macros.cpp22
-rw-r--r--core/error/error_macros.h1
-rw-r--r--core/extension/gdextension_interface.cpp1
-rw-r--r--core/input/input.cpp2
-rw-r--r--core/input/input_event.cpp3
-rw-r--r--core/input/input_event.h5
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/input/input_map.h5
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/io/resource_format_binary.cpp5
-rw-r--r--core/io/translation_loader_po.cpp3
-rw-r--r--core/math/geometry_2d.h12
-rw-r--r--core/os/os.h1
-rw-r--r--core/string/char_range.inc613
-rw-r--r--core/string/char_utils.h66
-rw-r--r--core/string/translation_domain.cpp4
-rw-r--r--core/string/translation_server.cpp35
-rw-r--r--core/string/translation_server.h2
-rw-r--r--core/string/ustring.cpp4
-rw-r--r--core/templates/a_hash_map.cpp39
-rw-r--r--core/templates/a_hash_map.h732
-rw-r--r--core/templates/hashfuncs.h7
-rw-r--r--core/variant/variant.h13
-rw-r--r--core/variant/variant_op.cpp2
-rw-r--r--core/variant/variant_parser.cpp2
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/AtlasTexture.xml2
-rw-r--r--doc/classes/AudioEffectFilter.xml5
-rw-r--r--doc/classes/AudioEffectStereoEnhance.xml4
-rw-r--r--doc/classes/AudioServer.xml6
-rw-r--r--doc/classes/AudioStreamPlayback.xml48
-rw-r--r--doc/classes/BackBufferCopy.xml1
-rw-r--r--doc/classes/CanvasItem.xml8
-rw-r--r--doc/classes/CanvasModulate.xml1
-rw-r--r--doc/classes/Control.xml12
-rw-r--r--doc/classes/DisplayServer.xml22
-rw-r--r--doc/classes/EditorDebuggerPlugin.xml32
-rw-r--r--doc/classes/EditorDebuggerSession.xml2
-rw-r--r--doc/classes/EngineDebugger.xml3
-rw-r--r--doc/classes/Geometry2D.xml14
-rw-r--r--doc/classes/HTTPRequest.xml2
-rw-r--r--doc/classes/InputEventMouseMotion.xml2
-rw-r--r--doc/classes/Light3D.xml3
-rw-r--r--doc/classes/LineEdit.xml3
-rw-r--r--doc/classes/Material.xml2
-rw-r--r--doc/classes/Node.xml6
-rw-r--r--doc/classes/ProjectSettings.xml18
-rw-r--r--doc/classes/RenderingServer.xml18
-rw-r--r--doc/classes/ResourceImporterOBJ.xml13
-rw-r--r--doc/classes/Shader.xml6
-rw-r--r--doc/classes/String.xml1
-rw-r--r--doc/classes/Transform2D.xml4
-rw-r--r--doc/classes/Tree.xml15
-rw-r--r--doc/classes/Viewport.xml13
-rw-r--r--doc/classes/Window.xml22
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.cpp2
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp4
-rw-r--r--drivers/egl/egl_manager.cpp24
-rw-r--r--drivers/egl/egl_manager.h2
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp1
-rw-r--r--drivers/gles3/storage/light_storage.cpp17
-rw-r--r--drivers/gles3/storage/light_storage.h3
-rw-r--r--drivers/gles3/storage/material_storage.cpp4
-rw-r--r--drivers/gles3/storage/texture_storage.cpp63
-rw-r--r--drivers/gles3/storage/texture_storage.h6
-rw-r--r--drivers/metal/metal_objects.h53
-rw-r--r--drivers/metal/metal_objects.mm130
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm6
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp278
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.h14
-rw-r--r--editor/add_metadata_dialog.cpp2
-rw-r--r--editor/animation_track_editor.cpp4
-rw-r--r--editor/debugger/editor_profiler.cpp1
-rw-r--r--editor/debugger/editor_visual_profiler.cpp7
-rw-r--r--editor/debugger/script_editor_debugger.cpp2
-rw-r--r--editor/editor_data.cpp2
-rw-r--r--editor/editor_file_system.cpp202
-rw-r--r--editor/editor_file_system.h19
-rw-r--r--editor/editor_node.cpp8
-rw-r--r--editor/editor_properties.cpp9
-rw-r--r--editor/editor_properties.h2
-rw-r--r--editor/editor_property_name_processor.cpp3
-rw-r--r--editor/editor_resource_picker.cpp23
-rw-r--r--editor/editor_resource_picker.h5
-rw-r--r--editor/event_listener_line_edit.cpp2
-rw-r--r--editor/export/editor_export_platform.cpp6
-rw-r--r--editor/filesystem_dock.cpp92
-rw-r--r--editor/gui/editor_file_dialog.cpp9
-rw-r--r--editor/gui/editor_spin_slider.cpp4
-rw-r--r--editor/gui/editor_spin_slider.h2
-rw-r--r--editor/import/3d/resource_importer_obj.cpp83
-rw-r--r--editor/import/resource_importer_wav.cpp15
-rw-r--r--editor/import_dock.cpp17
-rw-r--r--editor/input_event_configuration_dialog.cpp15
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp14
-rw-r--r--editor/plugins/control_editor_plugin.cpp1
-rw-r--r--editor/plugins/editor_preview_plugins.cpp1
-rw-r--r--editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp57
-rw-r--r--editor/plugins/gizmos/gizmo_3d_helper.cpp95
-rw-r--r--editor/plugins/gizmos/gizmo_3d_helper.h5
-rw-r--r--editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp5
-rw-r--r--editor/plugins/script_editor_plugin.cpp50
-rw-r--r--editor/plugins/script_editor_plugin.h18
-rw-r--r--editor/plugins/shader_editor_plugin.cpp8
-rw-r--r--editor/plugins/shader_editor_plugin.h1
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp3
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp9
-rw-r--r--editor/project_manager/project_list.cpp2
-rw-r--r--editor/scene_tree_dock.cpp130
-rw-r--r--editor/scene_tree_dock.h1
-rw-r--r--editor/themes/editor_theme_manager.cpp56
-rw-r--r--editor/themes/editor_theme_manager.h1
-rw-r--r--editor/window_wrapper.cpp7
-rw-r--r--editor/window_wrapper.h1
-rw-r--r--main/main.cpp22
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp1
-rw-r--r--modules/betsy/image_compress_betsy.cpp10
-rw-r--r--modules/csg/editor/csg_gizmos.cpp54
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml6
-rw-r--r--modules/gdscript/editor/gdscript_docgen.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp4
-rw-r--r--modules/gdscript/gdscript.cpp47
-rw-r--r--modules/gdscript/gdscript.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp14
-rw-r--r--modules/gdscript/gdscript_cache.cpp3
-rw-r--r--modules/gdscript/gdscript_editor.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.cpp18
-rw-r--r--modules/gdscript/gdscript_parser.h4
-rw-r--r--modules/gdscript/gdscript_utility_functions.cpp492
-rw-r--r--modules/gdscript/gdscript_vm.cpp22
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs4
-rw-r--r--modules/mono/editor/bindings_generator.cpp4
-rw-r--r--modules/navigation/nav_map.cpp22
-rw-r--r--modules/openxr/SCsub2
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp23
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.cpp123
-rw-r--r--modules/openxr/action_map/openxr_interaction_profile.h32
-rw-r--r--modules/openxr/doc_classes/OpenXRIPBinding.xml18
-rw-r--r--modules/openxr/editor/openxr_interaction_profile_editor.cpp41
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.cpp37
-rw-r--r--modules/openxr/extensions/platform/openxr_opengl_extension.h11
-rw-r--r--modules/openxr/openxr_interface.cpp5
-rw-r--r--modules/openxr/openxr_platform_inc.h7
-rw-r--r--modules/openxr/scene/openxr_composition_layer.cpp4
-rw-r--r--modules/raycast/raycast_occlusion_cull.cpp12
-rw-r--r--modules/svg/image_loader_svg.cpp26
-rw-r--r--modules/tga/image_loader_tga.cpp1
-rw-r--r--modules/theora/video_stream_theora.cpp1
-rw-r--r--modules/tinyexr/image_saver_tinyexr.cpp1
-rw-r--r--modules/tinyexr/image_saver_tinyexr.h2
-rw-r--r--platform/android/detect.py12
-rw-r--r--platform/android/display_server_android.cpp8
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml2
-rw-r--r--platform/android/export/export_plugin.h1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java6
-rw-r--r--platform/ios/detect.py10
-rw-r--r--platform/linuxbsd/detect.py9
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp12
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp12
-rw-r--r--platform/macos/detect.py9
-rw-r--r--platform/macos/display_server_macos.mm12
-rw-r--r--platform/macos/export/export_plugin.h1
-rw-r--r--platform/web/detect.py8
-rw-r--r--platform/web/js/libs/library_godot_fetch.js16
-rw-r--r--platform/windows/detect.py54
-rw-r--r--platform/windows/display_server_windows.cpp48
-rw-r--r--platform/windows/display_server_windows.h2
-rw-r--r--platform/windows/native_menu_windows.h1
-rw-r--r--platform_methods.py10
-rw-r--r--scene/2d/path_2d.cpp9
-rw-r--r--scene/3d/light_3d.cpp15
-rw-r--r--scene/3d/light_3d.h4
-rw-r--r--scene/3d/lightmap_gi.cpp15
-rw-r--r--scene/3d/mesh_instance_3d.cpp4
-rw-r--r--scene/3d/skeleton_3d.cpp2
-rw-r--r--scene/3d/skeleton_3d.h3
-rw-r--r--scene/animation/animation_mixer.cpp66
-rw-r--r--scene/animation/animation_mixer.h21
-rw-r--r--scene/animation/animation_node_state_machine.cpp2
-rw-r--r--scene/animation/animation_player.cpp4
-rw-r--r--scene/animation/animation_player.h4
-rw-r--r--scene/animation/animation_tree.cpp32
-rw-r--r--scene/animation/animation_tree.h47
-rw-r--r--scene/gui/button.cpp17
-rw-r--r--scene/gui/color_picker.cpp40
-rw-r--r--scene/gui/color_picker.h1
-rw-r--r--scene/gui/control.cpp20
-rw-r--r--scene/gui/control.h9
-rw-r--r--scene/gui/item_list.cpp2
-rw-r--r--scene/gui/menu_bar.cpp1
-rw-r--r--scene/gui/tree.cpp417
-rw-r--r--scene/gui/tree.h18
-rw-r--r--scene/main/canvas_item.cpp2
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/main/window.cpp20
-rw-r--r--scene/main/window.h10
-rw-r--r--scene/property_utils.cpp10
-rw-r--r--scene/resources/2d/tile_set.cpp8
-rw-r--r--scene/resources/3d/importer_mesh.cpp1
-rw-r--r--scene/resources/external_texture.cpp23
-rw-r--r--scene/resources/external_texture.h5
-rw-r--r--scene/resources/resource_format_text.cpp4
-rw-r--r--scene/resources/shader.cpp12
-rw-r--r--scene/resources/shader.h2
-rw-r--r--scene/resources/style_box_flat.cpp24
-rw-r--r--scene/resources/surface_tool.h8
-rw-r--r--scene/scene_string_names.cpp2
-rw-r--r--scene/scene_string_names.h2
-rw-r--r--scene/theme/default_theme.cpp6
-rw-r--r--servers/audio/audio_stream.cpp43
-rw-r--r--servers/audio/audio_stream.h6
-rw-r--r--servers/audio_server.cpp6
-rw-r--r--servers/audio_server.h2
-rw-r--r--servers/display_server.cpp7
-rw-r--r--servers/display_server.h6
-rw-r--r--servers/movie_writer/movie_writer.h1
-rw-r--r--servers/rendering/dummy/storage/light_storage.h2
-rw-r--r--servers/rendering/renderer_rd/environment/fog.cpp80
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp4
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.h1
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl1
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl6
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp17
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp71
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.h5
-rw-r--r--servers/rendering/renderer_scene_cull.cpp16
-rw-r--r--servers/rendering/renderer_scene_cull.h1
-rw-r--r--servers/rendering/renderer_scene_occlusion_cull.h6
-rw-r--r--servers/rendering/rendering_device.cpp85
-rw-r--r--servers/rendering/rendering_device.h14
-rw-r--r--servers/rendering/rendering_device_binds.cpp5
-rw-r--r--servers/rendering/rendering_device_driver.cpp2
-rw-r--r--servers/rendering/rendering_device_driver.h2
-rw-r--r--servers/rendering/rendering_device_graph.cpp21
-rw-r--r--servers/rendering/rendering_device_graph.h11
-rw-r--r--servers/rendering/rendering_server_default.cpp2
-rw-r--r--servers/rendering/rendering_server_default.h1
-rw-r--r--servers/rendering/shader_language.cpp129
-rw-r--r--servers/rendering/shader_language.h4
-rw-r--r--servers/rendering/shader_preprocessor.cpp42
-rw-r--r--servers/rendering/shader_preprocessor.h1
-rw-r--r--servers/rendering/shader_types.cpp11
-rw-r--r--servers/rendering/storage/light_storage.h2
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h1
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/core/string/test_string.h18
-rw-r--r--tests/core/templates/test_a_hash_map.h295
-rw-r--r--tests/core/variant/test_variant.h8
-rw-r--r--tests/scene/test_tree.h126
-rw-r--r--tests/test_main.cpp1
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/meshoptimizer/LICENSE.md2
-rw-r--r--thirdparty/meshoptimizer/allocator.cpp2
-rw-r--r--thirdparty/meshoptimizer/clusterizer.cpp137
-rw-r--r--thirdparty/meshoptimizer/indexcodec.cpp8
-rw-r--r--thirdparty/meshoptimizer/indexgenerator.cpp97
-rw-r--r--thirdparty/meshoptimizer/meshoptimizer.h143
-rw-r--r--thirdparty/meshoptimizer/overdrawanalyzer.cpp3
-rw-r--r--thirdparty/meshoptimizer/patches/distance-only-metric.patch40
-rw-r--r--thirdparty/meshoptimizer/quantization.cpp12
-rw-r--r--thirdparty/meshoptimizer/simplifier.cpp709
-rw-r--r--thirdparty/meshoptimizer/stripifier.cpp13
-rw-r--r--thirdparty/meshoptimizer/vcacheoptimizer.cpp13
-rw-r--r--thirdparty/meshoptimizer/vertexcodec.cpp83
-rw-r--r--thirdparty/meshoptimizer/vertexfilter.cpp17
290 files changed, 6082 insertions, 2071 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index dc3d9f3786..348e2bb317 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -106,7 +106,7 @@ jobs:
# TODO: Figure out somehow how to embed this one.
- name: wayland-scanner dependency
run: |
- sudo apt-get install libwayland-bin
+ sudo apt-get install libwayland-bin libegl-dev
- name: Free disk space on runner
run: |
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 6d227d6615..ecf9ed0f46 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -341,7 +341,7 @@ License: Apache-2.0
Files: ./thirdparty/meshoptimizer/
Comment: meshoptimizer
-Copyright: 2016-2023, Arseny Kapoulkine
+Copyright: 2016-2024, Arseny Kapoulkine
License: Expat
Files: ./thirdparty/mingw-std-threads/
diff --git a/SConstruct b/SConstruct
index 63cff4fe16..785cc0b1a3 100644
--- a/SConstruct
+++ b/SConstruct
@@ -799,8 +799,8 @@ if env["lto"] != "none":
# This needs to come after `configure`, otherwise we don't have env.msvc.
if not env.msvc:
# Specifying GNU extensions support explicitly, which are supported by
- # both GCC and Clang. Both currently default to gnu11 and gnu++17.
- env.Prepend(CFLAGS=["-std=gnu11"])
+ # both GCC and Clang. Both currently default to gnu17 and gnu++17.
+ env.Prepend(CFLAGS=["-std=gnu17"])
env.Prepend(CXXFLAGS=["-std=gnu++17"])
else:
# MSVC started offering C standard support with Visual Studio 2019 16.8, which covers all
@@ -809,7 +809,7 @@ else:
if cc_version_major < 16:
print_warning("Visual Studio 2017 cannot specify a C-Standard.")
else:
- env.Prepend(CFLAGS=["/std:c11"])
+ env.Prepend(CFLAGS=["/std:c17"])
# MSVC is non-conforming with the C++ standard by default, so we enable more conformance.
# Note that this is still not complete conformance, as certain Windows-related headers
# don't compile under complete conformance.
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index d77c913314..9db2e6fbe9 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -267,6 +267,12 @@ bool Engine::is_extra_gpu_memory_tracking_enabled() const {
return extra_gpu_memory_tracking;
}
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+bool Engine::is_accurate_breadcrumbs_enabled() const {
+ return accurate_breadcrumbs;
+}
+#endif
+
void Engine::set_print_to_stdout(bool p_enabled) {
CoreGlobals::print_line_enabled = p_enabled;
}
diff --git a/core/config/engine.h b/core/config/engine.h
index a0b1ffa981..fd7fbd717e 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -73,6 +73,9 @@ private:
bool use_validation_layers = false;
bool generate_spirv_debug_info = false;
bool extra_gpu_memory_tracking = false;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ bool accurate_breadcrumbs = false;
+#endif
int32_t gpu_idx = -1;
uint64_t _process_frames = 0;
@@ -186,6 +189,9 @@ public:
bool is_validation_layers_enabled() const;
bool is_generate_spirv_debug_info_enabled() const;
bool is_extra_gpu_memory_tracking_enabled() const;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ bool is_accurate_breadcrumbs_enabled() const;
+#endif
int32_t get_gpu_index() const;
void increment_frames_drawn();
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 01f15f9c8e..b389e5a58e 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -494,6 +494,7 @@ bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_f
}
void ProjectSettings::_convert_to_last_version(int p_from_version) {
+#ifndef DISABLE_DEPRECATED
if (p_from_version <= 3) {
// Converts the actions from array to dictionary (array of events to dictionary with deadzone + events)
for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) {
@@ -507,6 +508,22 @@ void ProjectSettings::_convert_to_last_version(int p_from_version) {
}
}
}
+ if (p_from_version <= 5) {
+ // Converts the device in events from -1 (emulated events) to -3 (all events).
+ for (KeyValue<StringName, ProjectSettings::VariantContainer> &E : props) {
+ if (String(E.key).begins_with("input/")) {
+ Dictionary action = E.value.variant;
+ Array events = action["events"];
+ for (int i = 0; i < events.size(); i++) {
+ Ref<InputEvent> ev = events[i];
+ if (ev.is_valid() && ev->get_device() == -1) { // -1 was the previous value (GH-97707).
+ ev->set_device(InputEvent::DEVICE_ID_ALL_DEVICES);
+ }
+ }
+ }
+ }
+ }
+#endif // DISABLE_DEPRECATED
}
/*
@@ -1460,6 +1477,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/size/transparent", false);
GLOBAL_DEF("display/window/size/extend_to_title", false);
GLOBAL_DEF("display/window/size/no_focus", false);
+ GLOBAL_DEF("display/window/size/sharp_corners", false);
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 891e3a28c9..095c6c44dd 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -920,6 +920,19 @@ Dictionary Geometry2D::make_atlas(const Vector<Size2> &p_rects) {
return ret;
}
+TypedArray<Point2i> Geometry2D::bresenham_line(const Point2i &p_from, const Point2i &p_to) {
+ Vector<Point2i> points = ::Geometry2D::bresenham_line(p_from, p_to);
+
+ TypedArray<Point2i> result;
+ result.resize(points.size());
+
+ for (int i = 0; i < points.size(); i++) {
+ result[i] = points[i];
+ }
+
+ return result;
+}
+
void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_point_in_circle", "point", "circle_position", "circle_radius"), &Geometry2D::is_point_in_circle);
ClassDB::bind_method(D_METHOD("segment_intersects_circle", "segment_from", "segment_to", "circle_position", "circle_radius"), &Geometry2D::segment_intersects_circle);
@@ -954,6 +967,8 @@ void Geometry2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &Geometry2D::make_atlas);
+ ClassDB::bind_method(D_METHOD("bresenham_line", "from", "to"), &Geometry2D::bresenham_line);
+
BIND_ENUM_CONSTANT(OPERATION_UNION);
BIND_ENUM_CONSTANT(OPERATION_DIFFERENCE);
BIND_ENUM_CONSTANT(OPERATION_INTERSECTION);
diff --git a/core/core_bind.h b/core/core_bind.h
index ce0bde3c05..b690376551 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -32,7 +32,6 @@
#define CORE_BIND_H
#include "core/debugger/engine_profiler.h"
-#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/script_language.h"
@@ -325,6 +324,8 @@ public:
Dictionary make_atlas(const Vector<Size2> &p_rects);
+ TypedArray<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to);
+
Geometry2D() { singleton = this; }
};
diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp
index 813ee7684f..a2369992e6 100644
--- a/core/error/error_macros.cpp
+++ b/core/error/error_macros.cpp
@@ -107,6 +107,28 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
_global_unlock();
}
+// For printing errors when we may crash at any point, so we must flush ASAP a lot of lines
+// but we don't want to make it noisy by printing lots of file & line info (because it's already
+// been printing by a preceding _err_print_error).
+void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type) {
+ if (OS::get_singleton()) {
+ OS::get_singleton()->printerr("ERROR: %s\n", p_error.utf8().get_data());
+ } else {
+ // Fallback if errors happen before OS init or after it's destroyed.
+ const char *err_details = p_error.utf8().get_data();
+ fprintf(stderr, "ERROR: %s\n", err_details);
+ }
+
+ _global_lock();
+ ErrorHandlerList *l = error_handler_list;
+ while (l) {
+ l->errfunc(l->userdata, "", "", 0, p_error.utf8().get_data(), "", false, p_type);
+ l = l->next;
+ }
+
+ _global_unlock();
+}
+
// Errors with message. (All combinations of p_error and p_message as String or char*.)
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify, ErrorHandlerType p_type) {
_err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_editor_notify, p_type);
diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index 19c16667d0..752fd605e0 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -68,6 +68,7 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
+void _err_print_error_asap(const String &p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false);
void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false);
void _err_flush_stdout();
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 66b0161160..91f0b4a2de 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -34,6 +34,7 @@
#include "core/extension/gdextension.h"
#include "core/extension/gdextension_compat_hashes.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/xml_parser.h"
#include "core/object/class_db.h"
#include "core/object/script_language_extension.h"
diff --git a/core/input/input.cpp b/core/input/input.cpp
index eba7ded267..6261a435fa 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -690,6 +690,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
button_event->set_canceled(st->is_canceled());
button_event->set_button_index(MouseButton::LEFT);
button_event->set_double_click(st->is_double_tap());
+ button_event->set_window_id(st->get_window_id());
BitField<MouseButtonMask> ev_bm = mouse_button_mask;
if (st->is_pressed()) {
@@ -727,6 +728,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
motion_event->set_velocity(sd->get_velocity());
motion_event->set_screen_velocity(sd->get_screen_velocity());
motion_event->set_button_mask(mouse_button_mask);
+ motion_event->set_window_id(sd->get_window_id());
_parse_input_event_impl(motion_event, true);
}
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 905526bbbd..d125bad252 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -35,9 +35,6 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
-const int InputEvent::DEVICE_ID_EMULATION = -1;
-const int InputEvent::DEVICE_ID_INTERNAL = -2;
-
void InputEvent::set_device(int p_device) {
device = p_device;
emit_changed();
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 19176f748e..80bca28fbf 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -62,8 +62,9 @@ protected:
static void _bind_methods();
public:
- static const int DEVICE_ID_EMULATION;
- static const int DEVICE_ID_INTERNAL;
+ inline static constexpr int DEVICE_ID_EMULATION = -1;
+ inline static constexpr int DEVICE_ID_INTERNAL = -2;
+ inline static constexpr int DEVICE_ID_ALL_DEVICES = -3; // Signify that a given Action can be triggered by any device.
void set_device(int p_device);
int get_device() const;
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 27a50c79f6..5b9377fe59 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -39,8 +39,6 @@
InputMap *InputMap::singleton = nullptr;
-int InputMap::ALL_DEVICES = -1;
-
void InputMap::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_action", "action"), &InputMap::has_action);
ClassDB::bind_method(D_METHOD("get_actions"), &InputMap::_get_actions);
@@ -163,7 +161,7 @@ List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Re
int i = 0;
for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) {
int device = E->get()->get_device();
- if (device == ALL_DEVICES || device == p_event->get_device()) {
+ if (device == InputEvent::DEVICE_ID_ALL_DEVICES || device == p_event->get_device()) {
if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) {
if (r_event_index) {
*r_event_index = i;
diff --git a/core/input/input_map.h b/core/input/input_map.h
index b29687d144..45798490f7 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -43,11 +43,6 @@ class InputMap : public Object {
GDCLASS(InputMap, Object);
public:
- /**
- * A special value used to signify that a given Action can be triggered by any device
- */
- static int ALL_DEVICES;
-
struct Action {
int id;
float deadzone;
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 664ff7857b..22219fca29 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -121,7 +121,7 @@ String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_
d.get_key_list(&keys);
if (p_sort_keys) {
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
}
bool first_key = true;
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 109999d612..ecbb9c0104 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -1268,6 +1268,11 @@ void ResourceFormatLoaderBinary::get_recognized_extensions_for_type(const String
return;
}
+ // res files not supported for GDExtension.
+ if (p_type == "GDExtension") {
+ return;
+ }
+
List<String> extensions;
ClassDB::get_extensions_for_type(p_type, &extensions);
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 578cd91c52..812fbc774e 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -31,7 +31,6 @@
#include "translation_loader_po.h"
#include "core/io/file_access.h"
-#include "core/string/translation.h"
#include "core/string/translation_po.h"
Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_error) {
@@ -361,7 +360,7 @@ void TranslationLoaderPO::get_recognized_extensions(List<String> *p_extensions)
}
bool TranslationLoaderPO::handles_type(const String &p_type) const {
- return (p_type == "Translation");
+ return (p_type == "Translation") || (p_type == "TranslationPO");
}
String TranslationLoaderPO::get_resource_type(const String &p_path) const {
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 83ebdc5a84..abd395d8df 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -451,17 +451,17 @@ public:
return H;
}
- static Vector<Point2i> bresenham_line(const Point2i &p_start, const Point2i &p_end) {
+ static Vector<Point2i> bresenham_line(const Point2i &p_from, const Point2i &p_to) {
Vector<Point2i> points;
- Vector2i delta = (p_end - p_start).abs() * 2;
- Vector2i step = (p_end - p_start).sign();
- Vector2i current = p_start;
+ Vector2i delta = (p_to - p_from).abs() * 2;
+ Vector2i step = (p_to - p_from).sign();
+ Vector2i current = p_from;
if (delta.x > delta.y) {
int err = delta.x / 2;
- for (; current.x != p_end.x; current.x += step.x) {
+ for (; current.x != p_to.x; current.x += step.x) {
points.push_back(current);
err -= delta.y;
@@ -473,7 +473,7 @@ public:
} else {
int err = delta.y / 2;
- for (; current.y != p_end.y; current.y += step.y) {
+ for (; current.y != p_to.y; current.y += step.y) {
points.push_back(current);
err -= delta.x;
diff --git a/core/os/os.h b/core/os/os.h
index 30d2a4266f..c42a39e0a4 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -32,7 +32,6 @@
#define OS_H
#include "core/config/engine.h"
-#include "core/io/image.h"
#include "core/io/logger.h"
#include "core/io/remote_filesystem_client.h"
#include "core/os/time_enums.h"
diff --git a/core/string/char_range.inc b/core/string/char_range.inc
index 2b081b96de..efae757802 100644
--- a/core/string/char_range.inc
+++ b/core/string/char_range.inc
@@ -33,14 +33,17 @@
#include "core/typedefs.h"
+// Unicode Derived Core Properties
+// Source: https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt
+
struct CharRange {
char32_t start;
char32_t end;
};
-inline constexpr CharRange xid_start[] = {
+constexpr inline CharRange xid_start[] = {
{ 0x41, 0x5a },
- { 0x5f, 0x5f },
+ { 0x5f, 0x5f }, // Underscore technically isn't in XID_Start, but for our purposes it's included.
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
{ 0xb5, 0xb5 },
@@ -54,7 +57,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x2ee, 0x2ee },
{ 0x370, 0x374 },
{ 0x376, 0x377 },
- { 0x37a, 0x37d },
+ { 0x37b, 0x37d },
{ 0x37f, 0x37f },
{ 0x386, 0x386 },
{ 0x388, 0x38a },
@@ -182,7 +185,7 @@ inline constexpr CharRange xid_start[] = {
{ 0xdbd, 0xdbd },
{ 0xdc0, 0xdc6 },
{ 0xe01, 0xe30 },
- { 0xe32, 0xe33 },
+ { 0xe32, 0xe32 },
{ 0xe40, 0xe46 },
{ 0xe81, 0xe82 },
{ 0xe84, 0xe84 },
@@ -190,7 +193,7 @@ inline constexpr CharRange xid_start[] = {
{ 0xe8c, 0xea3 },
{ 0xea5, 0xea5 },
{ 0xea7, 0xeb0 },
- { 0xeb2, 0xeb3 },
+ { 0xeb2, 0xeb2 },
{ 0xebd, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
@@ -245,8 +248,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x17d7, 0x17d7 },
{ 0x17dc, 0x17dc },
{ 0x1820, 0x1878 },
- { 0x1880, 0x1884 },
- { 0x1887, 0x18a8 },
+ { 0x1880, 0x18a8 },
{ 0x18aa, 0x18aa },
{ 0x18b0, 0x18f5 },
{ 0x1900, 0x191e },
@@ -265,7 +267,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x1c00, 0x1c23 },
{ 0x1c4d, 0x1c4f },
{ 0x1c5a, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1ce9, 0x1cec },
@@ -330,7 +332,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x3031, 0x3035 },
{ 0x3038, 0x303c },
{ 0x3041, 0x3096 },
- { 0x309b, 0x309f },
+ { 0x309d, 0x309f },
{ 0x30a1, 0x30fa },
{ 0x30fc, 0x30ff },
{ 0x3105, 0x312f },
@@ -348,10 +350,10 @@ inline constexpr CharRange xid_start[] = {
{ 0xa6a0, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
+ { 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa801 },
{ 0xa803, 0xa805 },
{ 0xa807, 0xa80a },
@@ -406,15 +408,22 @@ inline constexpr CharRange xid_start[] = {
{ 0xfb40, 0xfb41 },
{ 0xfb43, 0xfb44 },
{ 0xfb46, 0xfbb1 },
- { 0xfbd3, 0xfd3d },
+ { 0xfbd3, 0xfc5d },
+ { 0xfc64, 0xfd3d },
{ 0xfd50, 0xfd8f },
{ 0xfd92, 0xfdc7 },
- { 0xfdf0, 0xfdfb },
- { 0xfe70, 0xfe74 },
- { 0xfe76, 0xfefc },
+ { 0xfdf0, 0xfdf9 },
+ { 0xfe71, 0xfe71 },
+ { 0xfe73, 0xfe73 },
+ { 0xfe77, 0xfe77 },
+ { 0xfe79, 0xfe79 },
+ { 0xfe7b, 0xfe7b },
+ { 0xfe7d, 0xfe7d },
+ { 0xfe7f, 0xfefc },
{ 0xff21, 0xff3a },
{ 0xff41, 0xff5a },
- { 0xff66, 0xffbe },
+ { 0xff66, 0xff9d },
+ { 0xffa0, 0xffbe },
{ 0xffc2, 0xffc7 },
{ 0xffca, 0xffcf },
{ 0xffd2, 0xffd7 },
@@ -449,6 +458,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -485,8 +495,11 @@ inline constexpr CharRange xid_start[] = {
{ 0x10c80, 0x10cb2 },
{ 0x10cc0, 0x10cf2 },
{ 0x10d00, 0x10d23 },
+ { 0x10d4a, 0x10d65 },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
{ 0x10eb0, 0x10eb1 },
+ { 0x10ec2, 0x10ec4 },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
@@ -509,6 +522,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
{ 0x11213, 0x1122b },
+ { 0x1123f, 0x11240 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
@@ -524,6 +538,13 @@ inline constexpr CharRange xid_start[] = {
{ 0x1133d, 0x1133d },
{ 0x11350, 0x11350 },
{ 0x1135d, 0x11361 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113b7 },
+ { 0x113d1, 0x113d1 },
+ { 0x113d3, 0x113d3 },
{ 0x11400, 0x11434 },
{ 0x11447, 0x1144a },
{ 0x1145f, 0x11461 },
@@ -558,6 +579,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x11a5c, 0x11a89 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
{ 0x11c00, 0x11c08 },
{ 0x11c0a, 0x11c2e },
{ 0x11c40, 0x11c40 },
@@ -571,13 +593,19 @@ inline constexpr CharRange xid_start[] = {
{ 0x11d6a, 0x11d89 },
{ 0x11d98, 0x11d98 },
{ 0x11ee0, 0x11ef2 },
+ { 0x11f02, 0x11f02 },
+ { 0x11f04, 0x11f10 },
+ { 0x11f12, 0x11f33 },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
{ 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
- { 0x13000, 0x1342e },
+ { 0x13000, 0x1342f },
+ { 0x13441, 0x13446 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x1611d },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a70, 0x16abe },
@@ -586,6 +614,7 @@ inline constexpr CharRange xid_start[] = {
{ 0x16b40, 0x16b43 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
{ 0x16f50, 0x16f50 },
@@ -594,12 +623,14 @@ inline constexpr CharRange xid_start[] = {
{ 0x16fe3, 0x16fe3 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
{ 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
{ 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
{ 0x1b164, 0x1b167 },
{ 0x1b170, 0x1b2fb },
{ 0x1bc00, 0x1bc6a },
@@ -637,11 +668,16 @@ inline constexpr CharRange xid_start[] = {
{ 0x1d7aa, 0x1d7c2 },
{ 0x1d7c4, 0x1d7cb },
{ 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
{ 0x1e100, 0x1e12c },
{ 0x1e137, 0x1e13d },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ad },
{ 0x1e2c0, 0x1e2eb },
+ { 0x1e4d0, 0x1e4eb },
+ { 0x1e5d0, 0x1e5ed },
+ { 0x1e5f0, 0x1e5f0 },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@@ -683,15 +719,17 @@ inline constexpr CharRange xid_start[] = {
{ 0x1eea5, 0x1eea9 },
{ 0x1eeab, 0x1eebb },
{ 0x20000, 0x2a6df },
- { 0x2a700, 0x2b738 },
+ { 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
};
-inline constexpr CharRange xid_continue[] = {
+constexpr inline CharRange xid_continue[] = {
{ 0x30, 0x39 },
{ 0x41, 0x5a },
{ 0x5f, 0x5f },
@@ -709,7 +747,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x2ee, 0x2ee },
{ 0x300, 0x374 },
{ 0x376, 0x377 },
- { 0x37a, 0x37d },
+ { 0x37b, 0x37d },
{ 0x37f, 0x37f },
{ 0x386, 0x38a },
{ 0x38c, 0x38c },
@@ -745,7 +783,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
- { 0x898, 0x8e1 },
+ { 0x897, 0x8e1 },
{ 0x8e3, 0x963 },
{ 0x966, 0x96f },
{ 0x971, 0x983 },
@@ -850,7 +888,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0xcdd, 0xcde },
{ 0xce0, 0xce3 },
{ 0xce6, 0xcef },
- { 0xcf1, 0xcf2 },
+ { 0xcf1, 0xcf3 },
{ 0xd00, 0xd0c },
{ 0xd0e, 0xd10 },
{ 0xd12, 0xd44 },
@@ -883,7 +921,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0xea7, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
- { 0xec8, 0xecd },
+ { 0xec8, 0xece },
{ 0xed0, 0xed9 },
{ 0xedc, 0xedf },
{ 0xf00, 0xf00 },
@@ -921,8 +959,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1312, 0x1315 },
{ 0x1318, 0x135a },
{ 0x135d, 0x135f },
- { 0x1369, 0x1369 },
- { 0x1371, 0x1371 },
+ { 0x1369, 0x1371 },
{ 0x1380, 0x138f },
{ 0x13a0, 0x13f5 },
{ 0x13f8, 0x13fd },
@@ -969,7 +1006,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1c00, 0x1c37 },
{ 0x1c40, 0x1c49 },
{ 0x1c4d, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1cd0, 0x1cd2 },
@@ -993,6 +1030,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1fe0, 0x1fec },
{ 0x1ff2, 0x1ff4 },
{ 0x1ff6, 0x1ffc },
+ { 0x200c, 0x200d },
{ 0x203f, 0x2040 },
{ 0x2054, 0x2054 },
{ 0x2071, 0x2071 },
@@ -1036,9 +1074,9 @@ inline constexpr CharRange xid_continue[] = {
{ 0x3031, 0x3035 },
{ 0x3038, 0x303c },
{ 0x3041, 0x3096 },
- { 0x3099, 0x309f },
- { 0x30a1, 0x30fa },
- { 0x30fc, 0x30ff },
+ { 0x3099, 0x309a },
+ { 0x309d, 0x309f },
+ { 0x30a1, 0x30ff },
{ 0x3105, 0x312f },
{ 0x3131, 0x318e },
{ 0x31a0, 0x31bf },
@@ -1053,10 +1091,10 @@ inline constexpr CharRange xid_continue[] = {
{ 0xa67f, 0xa6f1 },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
+ { 0xa7d5, 0xa7dc },
{ 0xa7f2, 0xa827 },
{ 0xa82c, 0xa82c },
{ 0xa840, 0xa873 },
@@ -1102,21 +1140,27 @@ inline constexpr CharRange xid_continue[] = {
{ 0xfb40, 0xfb41 },
{ 0xfb43, 0xfb44 },
{ 0xfb46, 0xfbb1 },
- { 0xfbd3, 0xfd3d },
+ { 0xfbd3, 0xfc5d },
+ { 0xfc64, 0xfd3d },
{ 0xfd50, 0xfd8f },
{ 0xfd92, 0xfdc7 },
- { 0xfdf0, 0xfdfb },
+ { 0xfdf0, 0xfdf9 },
{ 0xfe00, 0xfe0f },
{ 0xfe20, 0xfe2f },
{ 0xfe33, 0xfe34 },
{ 0xfe4d, 0xfe4f },
- { 0xfe70, 0xfe74 },
- { 0xfe76, 0xfefc },
+ { 0xfe71, 0xfe71 },
+ { 0xfe73, 0xfe73 },
+ { 0xfe77, 0xfe77 },
+ { 0xfe79, 0xfe79 },
+ { 0xfe7b, 0xfe7b },
+ { 0xfe7d, 0xfe7d },
+ { 0xfe7f, 0xfefc },
{ 0xff10, 0xff19 },
{ 0xff21, 0xff3a },
{ 0xff3f, 0xff3f },
{ 0xff41, 0xff5a },
- { 0xff66, 0xffbe },
+ { 0xff65, 0xffbe },
{ 0xffc2, 0xffc7 },
{ 0xffca, 0xffcf },
{ 0xffd2, 0xffd7 },
@@ -1154,6 +1198,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -1194,10 +1239,14 @@ inline constexpr CharRange xid_continue[] = {
{ 0x10cc0, 0x10cf2 },
{ 0x10d00, 0x10d27 },
{ 0x10d30, 0x10d39 },
+ { 0x10d40, 0x10d65 },
+ { 0x10d69, 0x10d6d },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
{ 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
- { 0x10f00, 0x10f1c },
+ { 0x10ec2, 0x10ec4 },
+ { 0x10efc, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f50 },
{ 0x10f70, 0x10f85 },
@@ -1220,7 +1269,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
{ 0x11213, 0x11237 },
- { 0x1123e, 0x1123e },
+ { 0x1123e, 0x11241 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
@@ -1243,6 +1292,16 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1135d, 0x11363 },
{ 0x11366, 0x1136c },
{ 0x11370, 0x11374 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113c0 },
+ { 0x113c2, 0x113c2 },
+ { 0x113c5, 0x113c5 },
+ { 0x113c7, 0x113ca },
+ { 0x113cc, 0x113d3 },
+ { 0x113e1, 0x113e2 },
{ 0x11400, 0x1144a },
{ 0x11450, 0x11459 },
{ 0x1145e, 0x11461 },
@@ -1257,6 +1316,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11650, 0x11659 },
{ 0x11680, 0x116b8 },
{ 0x116c0, 0x116c9 },
+ { 0x116d0, 0x116e3 },
{ 0x11700, 0x1171a },
{ 0x1171d, 0x1172b },
{ 0x11730, 0x11739 },
@@ -1280,6 +1340,8 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11a50, 0x11a99 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
+ { 0x11bf0, 0x11bf9 },
{ 0x11c00, 0x11c08 },
{ 0x11c0a, 0x11c36 },
{ 0x11c38, 0x11c40 },
@@ -1301,13 +1363,20 @@ inline constexpr CharRange xid_continue[] = {
{ 0x11d93, 0x11d98 },
{ 0x11da0, 0x11da9 },
{ 0x11ee0, 0x11ef6 },
+ { 0x11f00, 0x11f10 },
+ { 0x11f12, 0x11f3a },
+ { 0x11f3e, 0x11f42 },
+ { 0x11f50, 0x11f5a },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
{ 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
- { 0x13000, 0x1342e },
+ { 0x13000, 0x1342f },
+ { 0x13440, 0x13455 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x16139 },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a60, 0x16a69 },
@@ -1320,6 +1389,8 @@ inline constexpr CharRange xid_continue[] = {
{ 0x16b50, 0x16b59 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
+ { 0x16d70, 0x16d79 },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
{ 0x16f4f, 0x16f87 },
@@ -1329,12 +1400,14 @@ inline constexpr CharRange xid_continue[] = {
{ 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
{ 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
{ 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
{ 0x1b164, 0x1b167 },
{ 0x1b170, 0x1b2fb },
{ 0x1bc00, 0x1bc6a },
@@ -1342,6 +1415,7 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1bc80, 0x1bc88 },
{ 0x1bc90, 0x1bc99 },
{ 0x1bc9d, 0x1bc9e },
+ { 0x1ccf0, 0x1ccf9 },
{ 0x1cf00, 0x1cf2d },
{ 0x1cf30, 0x1cf46 },
{ 0x1d165, 0x1d169 },
@@ -1388,17 +1462,22 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1da9b, 0x1da9f },
{ 0x1daa1, 0x1daaf },
{ 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
{ 0x1e000, 0x1e006 },
{ 0x1e008, 0x1e018 },
{ 0x1e01b, 0x1e021 },
{ 0x1e023, 0x1e024 },
{ 0x1e026, 0x1e02a },
+ { 0x1e030, 0x1e06d },
+ { 0x1e08f, 0x1e08f },
{ 0x1e100, 0x1e12c },
{ 0x1e130, 0x1e13d },
{ 0x1e140, 0x1e149 },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ae },
{ 0x1e2c0, 0x1e2f9 },
+ { 0x1e4d0, 0x1e4f9 },
+ { 0x1e5d0, 0x1e5fa },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
@@ -1442,16 +1521,18 @@ inline constexpr CharRange xid_continue[] = {
{ 0x1eeab, 0x1eebb },
{ 0x1fbf0, 0x1fbf9 },
{ 0x20000, 0x2a6df },
- { 0x2a700, 0x2b738 },
+ { 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
{ 0x2b820, 0x2cea1 },
{ 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
{ 0x2f800, 0x2fa1d },
{ 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
{ 0xe0100, 0xe01ef },
};
-inline constexpr CharRange uppercase_letter[] = {
+constexpr inline CharRange uppercase_letter[] = {
{ 0x41, 0x5a },
{ 0xc0, 0xd6 },
{ 0xd8, 0xde },
@@ -1728,6 +1809,7 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x10c7, 0x10c7 },
{ 0x10cd, 0x10cd },
{ 0x13a0, 0x13f5 },
+ { 0x1c89, 0x1c89 },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1e00, 0x1e00 },
@@ -1882,7 +1964,9 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x2130, 0x2133 },
{ 0x213e, 0x213f },
{ 0x2145, 0x2145 },
+ { 0x2160, 0x216f },
{ 0x2183, 0x2183 },
+ { 0x24b6, 0x24cf },
{ 0x2c00, 0x2c2f },
{ 0x2c60, 0x2c60 },
{ 0x2c62, 0x2c64 },
@@ -2052,9 +2136,12 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0xa7c2, 0xa7c2 },
{ 0xa7c4, 0xa7c7 },
{ 0xa7c9, 0xa7c9 },
+ { 0xa7cb, 0xa7cc },
{ 0xa7d0, 0xa7d0 },
{ 0xa7d6, 0xa7d6 },
{ 0xa7d8, 0xa7d8 },
+ { 0xa7da, 0xa7da },
+ { 0xa7dc, 0xa7dc },
{ 0xa7f5, 0xa7f5 },
{ 0xff21, 0xff3a },
{ 0x10400, 0x10427 },
@@ -2064,6 +2151,7 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x1058c, 0x10592 },
{ 0x10594, 0x10595 },
{ 0x10c80, 0x10cb2 },
+ { 0x10d50, 0x10d65 },
{ 0x118a0, 0x118bf },
{ 0x16e40, 0x16e5f },
{ 0x1d400, 0x1d419 },
@@ -2098,11 +2186,16 @@ inline constexpr CharRange uppercase_letter[] = {
{ 0x1d790, 0x1d7a8 },
{ 0x1d7ca, 0x1d7ca },
{ 0x1e900, 0x1e921 },
+ { 0x1f130, 0x1f149 },
+ { 0x1f150, 0x1f169 },
+ { 0x1f170, 0x1f189 },
};
-inline constexpr CharRange lowercase_letter[] = {
+constexpr inline CharRange lowercase_letter[] = {
{ 0x61, 0x7a },
+ { 0xaa, 0xaa },
{ 0xb5, 0xb5 },
+ { 0xba, 0xba },
{ 0xdf, 0xf6 },
{ 0xf8, 0xff },
{ 0x101, 0x101 },
@@ -2246,11 +2339,14 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x24b, 0x24b },
{ 0x24d, 0x24d },
{ 0x24f, 0x293 },
- { 0x295, 0x2af },
+ { 0x295, 0x2b8 },
+ { 0x2c0, 0x2c1 },
+ { 0x2e0, 0x2e4 },
+ { 0x345, 0x345 },
{ 0x371, 0x371 },
{ 0x373, 0x373 },
{ 0x377, 0x377 },
- { 0x37b, 0x37d },
+ { 0x37a, 0x37d },
{ 0x390, 0x390 },
{ 0x3ac, 0x3ce },
{ 0x3d0, 0x3d1 },
@@ -2372,12 +2468,11 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x52f, 0x52f },
{ 0x560, 0x588 },
{ 0x10d0, 0x10fa },
- { 0x10fd, 0x10ff },
+ { 0x10fc, 0x10ff },
{ 0x13f8, 0x13fd },
{ 0x1c80, 0x1c88 },
- { 0x1d00, 0x1d2b },
- { 0x1d6b, 0x1d77 },
- { 0x1d79, 0x1d9a },
+ { 0x1c8a, 0x1c8a },
+ { 0x1d00, 0x1dbf },
{ 0x1e01, 0x1e01 },
{ 0x1e03, 0x1e03 },
{ 0x1e05, 0x1e05 },
@@ -2522,6 +2617,9 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1fe0, 0x1fe7 },
{ 0x1ff2, 0x1ff4 },
{ 0x1ff6, 0x1ff7 },
+ { 0x2071, 0x2071 },
+ { 0x207f, 0x207f },
+ { 0x2090, 0x209c },
{ 0x210a, 0x210a },
{ 0x210e, 0x210f },
{ 0x2113, 0x2113 },
@@ -2531,7 +2629,9 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x213c, 0x213d },
{ 0x2146, 0x2149 },
{ 0x214e, 0x214e },
+ { 0x2170, 0x217f },
{ 0x2184, 0x2184 },
+ { 0x24d0, 0x24e9 },
{ 0x2c30, 0x2c5f },
{ 0x2c61, 0x2c61 },
{ 0x2c65, 0x2c66 },
@@ -2540,7 +2640,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x2c6c, 0x2c6c },
{ 0x2c71, 0x2c71 },
{ 0x2c73, 0x2c74 },
- { 0x2c76, 0x2c7b },
+ { 0x2c76, 0x2c7d },
{ 0x2c81, 0x2c81 },
{ 0x2c83, 0x2c83 },
{ 0x2c85, 0x2c85 },
@@ -2633,7 +2733,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa695, 0xa695 },
{ 0xa697, 0xa697 },
{ 0xa699, 0xa699 },
- { 0xa69b, 0xa69b },
+ { 0xa69b, 0xa69d },
{ 0xa723, 0xa723 },
{ 0xa725, 0xa725 },
{ 0xa727, 0xa727 },
@@ -2671,8 +2771,7 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa769, 0xa769 },
{ 0xa76b, 0xa76b },
{ 0xa76d, 0xa76d },
- { 0xa76f, 0xa76f },
- { 0xa771, 0xa778 },
+ { 0xa76f, 0xa778 },
{ 0xa77a, 0xa77a },
{ 0xa77c, 0xa77c },
{ 0xa77f, 0xa77f },
@@ -2705,15 +2804,18 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0xa7c3, 0xa7c3 },
{ 0xa7c8, 0xa7c8 },
{ 0xa7ca, 0xa7ca },
+ { 0xa7cd, 0xa7cd },
{ 0xa7d1, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
{ 0xa7d5, 0xa7d5 },
{ 0xa7d7, 0xa7d7 },
{ 0xa7d9, 0xa7d9 },
+ { 0xa7db, 0xa7db },
+ { 0xa7f2, 0xa7f4 },
{ 0xa7f6, 0xa7f6 },
- { 0xa7fa, 0xa7fa },
+ { 0xa7f8, 0xa7fa },
{ 0xab30, 0xab5a },
- { 0xab60, 0xab68 },
+ { 0xab5c, 0xab69 },
{ 0xab70, 0xabbf },
{ 0xfb00, 0xfb06 },
{ 0xfb13, 0xfb17 },
@@ -2724,7 +2826,12 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x10780, 0x10780 },
+ { 0x10783, 0x10785 },
+ { 0x10787, 0x107b0 },
+ { 0x107b2, 0x107ba },
{ 0x10cc0, 0x10cf2 },
+ { 0x10d70, 0x10d85 },
{ 0x118c0, 0x118df },
{ 0x16e60, 0x16e7f },
{ 0x1d41a, 0x1d433 },
@@ -2758,10 +2865,11 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1df00, 0x1df09 },
{ 0x1df0b, 0x1df1e },
{ 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
{ 0x1e922, 0x1e943 },
};
-inline constexpr CharRange unicode_letter[] = {
+constexpr inline CharRange unicode_letter[] = {
{ 0x41, 0x5a },
{ 0x61, 0x7a },
{ 0xaa, 0xaa },
@@ -2774,7 +2882,8 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x2e0, 0x2e4 },
{ 0x2ec, 0x2ec },
{ 0x2ee, 0x2ee },
- { 0x370, 0x374 },
+ { 0x345, 0x345 },
+ { 0x363, 0x374 },
{ 0x376, 0x377 },
{ 0x37a, 0x37d },
{ 0x37f, 0x37f },
@@ -2788,49 +2897,58 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x531, 0x556 },
{ 0x559, 0x559 },
{ 0x560, 0x588 },
+ { 0x5b0, 0x5bd },
+ { 0x5bf, 0x5bf },
+ { 0x5c1, 0x5c2 },
+ { 0x5c4, 0x5c5 },
+ { 0x5c7, 0x5c7 },
{ 0x5d0, 0x5ea },
{ 0x5ef, 0x5f2 },
- { 0x620, 0x64a },
- { 0x66e, 0x66f },
- { 0x671, 0x6d3 },
- { 0x6d5, 0x6d5 },
- { 0x6e5, 0x6e6 },
- { 0x6ee, 0x6ef },
+ { 0x610, 0x61a },
+ { 0x620, 0x657 },
+ { 0x659, 0x65f },
+ { 0x66e, 0x6d3 },
+ { 0x6d5, 0x6dc },
+ { 0x6e1, 0x6e8 },
+ { 0x6ed, 0x6ef },
{ 0x6fa, 0x6fc },
{ 0x6ff, 0x6ff },
- { 0x710, 0x710 },
- { 0x712, 0x72f },
- { 0x74d, 0x7a5 },
- { 0x7b1, 0x7b1 },
+ { 0x710, 0x73f },
+ { 0x74d, 0x7b1 },
{ 0x7ca, 0x7ea },
{ 0x7f4, 0x7f5 },
{ 0x7fa, 0x7fa },
- { 0x800, 0x815 },
- { 0x81a, 0x81a },
- { 0x824, 0x824 },
- { 0x828, 0x828 },
+ { 0x800, 0x817 },
+ { 0x81a, 0x82c },
{ 0x840, 0x858 },
{ 0x860, 0x86a },
{ 0x870, 0x887 },
{ 0x889, 0x88e },
+ { 0x897, 0x897 },
{ 0x8a0, 0x8c9 },
- { 0x904, 0x939 },
- { 0x93d, 0x93d },
- { 0x950, 0x950 },
- { 0x958, 0x961 },
- { 0x971, 0x980 },
+ { 0x8d4, 0x8df },
+ { 0x8e3, 0x8e9 },
+ { 0x8f0, 0x93b },
+ { 0x93d, 0x94c },
+ { 0x94e, 0x950 },
+ { 0x955, 0x963 },
+ { 0x971, 0x983 },
{ 0x985, 0x98c },
{ 0x98f, 0x990 },
{ 0x993, 0x9a8 },
{ 0x9aa, 0x9b0 },
{ 0x9b2, 0x9b2 },
{ 0x9b6, 0x9b9 },
- { 0x9bd, 0x9bd },
+ { 0x9bd, 0x9c4 },
+ { 0x9c7, 0x9c8 },
+ { 0x9cb, 0x9cc },
{ 0x9ce, 0x9ce },
+ { 0x9d7, 0x9d7 },
{ 0x9dc, 0x9dd },
- { 0x9df, 0x9e1 },
+ { 0x9df, 0x9e3 },
{ 0x9f0, 0x9f1 },
{ 0x9fc, 0x9fc },
+ { 0xa01, 0xa03 },
{ 0xa05, 0xa0a },
{ 0xa0f, 0xa10 },
{ 0xa13, 0xa28 },
@@ -2838,30 +2956,41 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xa32, 0xa33 },
{ 0xa35, 0xa36 },
{ 0xa38, 0xa39 },
+ { 0xa3e, 0xa42 },
+ { 0xa47, 0xa48 },
+ { 0xa4b, 0xa4c },
+ { 0xa51, 0xa51 },
{ 0xa59, 0xa5c },
{ 0xa5e, 0xa5e },
- { 0xa72, 0xa74 },
+ { 0xa70, 0xa75 },
+ { 0xa81, 0xa83 },
{ 0xa85, 0xa8d },
{ 0xa8f, 0xa91 },
{ 0xa93, 0xaa8 },
{ 0xaaa, 0xab0 },
{ 0xab2, 0xab3 },
{ 0xab5, 0xab9 },
- { 0xabd, 0xabd },
+ { 0xabd, 0xac5 },
+ { 0xac7, 0xac9 },
+ { 0xacb, 0xacc },
{ 0xad0, 0xad0 },
- { 0xae0, 0xae1 },
- { 0xaf9, 0xaf9 },
+ { 0xae0, 0xae3 },
+ { 0xaf9, 0xafc },
+ { 0xb01, 0xb03 },
{ 0xb05, 0xb0c },
{ 0xb0f, 0xb10 },
{ 0xb13, 0xb28 },
{ 0xb2a, 0xb30 },
{ 0xb32, 0xb33 },
{ 0xb35, 0xb39 },
- { 0xb3d, 0xb3d },
+ { 0xb3d, 0xb44 },
+ { 0xb47, 0xb48 },
+ { 0xb4b, 0xb4c },
+ { 0xb56, 0xb57 },
{ 0xb5c, 0xb5d },
- { 0xb5f, 0xb61 },
+ { 0xb5f, 0xb63 },
{ 0xb71, 0xb71 },
- { 0xb83, 0xb83 },
+ { 0xb82, 0xb83 },
{ 0xb85, 0xb8a },
{ 0xb8e, 0xb90 },
{ 0xb92, 0xb95 },
@@ -2871,65 +3000,80 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xba3, 0xba4 },
{ 0xba8, 0xbaa },
{ 0xbae, 0xbb9 },
+ { 0xbbe, 0xbc2 },
+ { 0xbc6, 0xbc8 },
+ { 0xbca, 0xbcc },
{ 0xbd0, 0xbd0 },
- { 0xc05, 0xc0c },
+ { 0xbd7, 0xbd7 },
+ { 0xc00, 0xc0c },
{ 0xc0e, 0xc10 },
{ 0xc12, 0xc28 },
{ 0xc2a, 0xc39 },
- { 0xc3d, 0xc3d },
+ { 0xc3d, 0xc44 },
+ { 0xc46, 0xc48 },
+ { 0xc4a, 0xc4c },
+ { 0xc55, 0xc56 },
{ 0xc58, 0xc5a },
{ 0xc5d, 0xc5d },
- { 0xc60, 0xc61 },
- { 0xc80, 0xc80 },
+ { 0xc60, 0xc63 },
+ { 0xc80, 0xc83 },
{ 0xc85, 0xc8c },
{ 0xc8e, 0xc90 },
{ 0xc92, 0xca8 },
{ 0xcaa, 0xcb3 },
{ 0xcb5, 0xcb9 },
- { 0xcbd, 0xcbd },
+ { 0xcbd, 0xcc4 },
+ { 0xcc6, 0xcc8 },
+ { 0xcca, 0xccc },
+ { 0xcd5, 0xcd6 },
{ 0xcdd, 0xcde },
- { 0xce0, 0xce1 },
- { 0xcf1, 0xcf2 },
- { 0xd04, 0xd0c },
+ { 0xce0, 0xce3 },
+ { 0xcf1, 0xcf3 },
+ { 0xd00, 0xd0c },
{ 0xd0e, 0xd10 },
{ 0xd12, 0xd3a },
- { 0xd3d, 0xd3d },
+ { 0xd3d, 0xd44 },
+ { 0xd46, 0xd48 },
+ { 0xd4a, 0xd4c },
{ 0xd4e, 0xd4e },
- { 0xd54, 0xd56 },
- { 0xd5f, 0xd61 },
+ { 0xd54, 0xd57 },
+ { 0xd5f, 0xd63 },
{ 0xd7a, 0xd7f },
+ { 0xd81, 0xd83 },
{ 0xd85, 0xd96 },
{ 0xd9a, 0xdb1 },
{ 0xdb3, 0xdbb },
{ 0xdbd, 0xdbd },
{ 0xdc0, 0xdc6 },
- { 0xe01, 0xe30 },
- { 0xe32, 0xe33 },
+ { 0xdcf, 0xdd4 },
+ { 0xdd6, 0xdd6 },
+ { 0xdd8, 0xddf },
+ { 0xdf2, 0xdf3 },
+ { 0xe01, 0xe3a },
{ 0xe40, 0xe46 },
+ { 0xe4d, 0xe4d },
{ 0xe81, 0xe82 },
{ 0xe84, 0xe84 },
{ 0xe86, 0xe8a },
{ 0xe8c, 0xea3 },
{ 0xea5, 0xea5 },
- { 0xea7, 0xeb0 },
- { 0xeb2, 0xeb3 },
- { 0xebd, 0xebd },
+ { 0xea7, 0xeb9 },
+ { 0xebb, 0xebd },
{ 0xec0, 0xec4 },
{ 0xec6, 0xec6 },
+ { 0xecd, 0xecd },
{ 0xedc, 0xedf },
{ 0xf00, 0xf00 },
{ 0xf40, 0xf47 },
{ 0xf49, 0xf6c },
- { 0xf88, 0xf8c },
- { 0x1000, 0x102a },
- { 0x103f, 0x103f },
- { 0x1050, 0x1055 },
- { 0x105a, 0x105d },
- { 0x1061, 0x1061 },
- { 0x1065, 0x1066 },
- { 0x106e, 0x1070 },
- { 0x1075, 0x1081 },
- { 0x108e, 0x108e },
+ { 0xf71, 0xf83 },
+ { 0xf88, 0xf97 },
+ { 0xf99, 0xfbc },
+ { 0x1000, 0x1036 },
+ { 0x1038, 0x1038 },
+ { 0x103b, 0x103f },
+ { 0x1050, 0x108f },
+ { 0x109a, 0x109d },
{ 0x10a0, 0x10c5 },
{ 0x10c7, 0x10c7 },
{ 0x10cd, 0x10cd },
@@ -2957,37 +3101,44 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x166f, 0x167f },
{ 0x1681, 0x169a },
{ 0x16a0, 0x16ea },
- { 0x16f1, 0x16f8 },
- { 0x1700, 0x1711 },
- { 0x171f, 0x1731 },
- { 0x1740, 0x1751 },
+ { 0x16ee, 0x16f8 },
+ { 0x1700, 0x1713 },
+ { 0x171f, 0x1733 },
+ { 0x1740, 0x1753 },
{ 0x1760, 0x176c },
{ 0x176e, 0x1770 },
+ { 0x1772, 0x1773 },
{ 0x1780, 0x17b3 },
+ { 0x17b6, 0x17c8 },
{ 0x17d7, 0x17d7 },
{ 0x17dc, 0x17dc },
{ 0x1820, 0x1878 },
- { 0x1880, 0x1884 },
- { 0x1887, 0x18a8 },
- { 0x18aa, 0x18aa },
+ { 0x1880, 0x18aa },
{ 0x18b0, 0x18f5 },
{ 0x1900, 0x191e },
+ { 0x1920, 0x192b },
+ { 0x1930, 0x1938 },
{ 0x1950, 0x196d },
{ 0x1970, 0x1974 },
{ 0x1980, 0x19ab },
{ 0x19b0, 0x19c9 },
- { 0x1a00, 0x1a16 },
- { 0x1a20, 0x1a54 },
+ { 0x1a00, 0x1a1b },
+ { 0x1a20, 0x1a5e },
+ { 0x1a61, 0x1a74 },
{ 0x1aa7, 0x1aa7 },
- { 0x1b05, 0x1b33 },
+ { 0x1abf, 0x1ac0 },
+ { 0x1acc, 0x1ace },
+ { 0x1b00, 0x1b33 },
+ { 0x1b35, 0x1b43 },
{ 0x1b45, 0x1b4c },
- { 0x1b83, 0x1ba0 },
- { 0x1bae, 0x1baf },
+ { 0x1b80, 0x1ba9 },
+ { 0x1bac, 0x1baf },
{ 0x1bba, 0x1be5 },
- { 0x1c00, 0x1c23 },
+ { 0x1be7, 0x1bf1 },
+ { 0x1c00, 0x1c36 },
{ 0x1c4d, 0x1c4f },
{ 0x1c5a, 0x1c7d },
- { 0x1c80, 0x1c88 },
+ { 0x1c80, 0x1c8a },
{ 0x1c90, 0x1cba },
{ 0x1cbd, 0x1cbf },
{ 0x1ce9, 0x1cec },
@@ -2995,6 +3146,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1cf5, 0x1cf6 },
{ 0x1cfa, 0x1cfa },
{ 0x1d00, 0x1dbf },
+ { 0x1dd3, 0x1df4 },
{ 0x1e00, 0x1f15 },
{ 0x1f18, 0x1f1d },
{ 0x1f20, 0x1f45 },
@@ -3030,7 +3182,8 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x213c, 0x213f },
{ 0x2145, 0x2149 },
{ 0x214e, 0x214e },
- { 0x2183, 0x2184 },
+ { 0x2160, 0x2188 },
+ { 0x24b6, 0x24e9 },
{ 0x2c00, 0x2ce4 },
{ 0x2ceb, 0x2cee },
{ 0x2cf2, 0x2cf3 },
@@ -3048,10 +3201,12 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x2dc8, 0x2dce },
{ 0x2dd0, 0x2dd6 },
{ 0x2dd8, 0x2dde },
+ { 0x2de0, 0x2dff },
{ 0x2e2f, 0x2e2f },
- { 0x3005, 0x3006 },
+ { 0x3005, 0x3007 },
+ { 0x3021, 0x3029 },
{ 0x3031, 0x3035 },
- { 0x303b, 0x303c },
+ { 0x3038, 0x303c },
{ 0x3041, 0x3096 },
{ 0x309d, 0x309f },
{ 0x30a1, 0x30fa },
@@ -3067,45 +3222,39 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xa610, 0xa61f },
{ 0xa62a, 0xa62b },
{ 0xa640, 0xa66e },
- { 0xa67f, 0xa69d },
- { 0xa6a0, 0xa6e5 },
+ { 0xa674, 0xa67b },
+ { 0xa67f, 0xa6ef },
{ 0xa717, 0xa71f },
{ 0xa722, 0xa788 },
- { 0xa78b, 0xa7ca },
+ { 0xa78b, 0xa7cd },
{ 0xa7d0, 0xa7d1 },
{ 0xa7d3, 0xa7d3 },
- { 0xa7d5, 0xa7d9 },
- { 0xa7f2, 0xa801 },
- { 0xa803, 0xa805 },
- { 0xa807, 0xa80a },
- { 0xa80c, 0xa822 },
+ { 0xa7d5, 0xa7dc },
+ { 0xa7f2, 0xa805 },
+ { 0xa807, 0xa827 },
{ 0xa840, 0xa873 },
- { 0xa882, 0xa8b3 },
+ { 0xa880, 0xa8c3 },
+ { 0xa8c5, 0xa8c5 },
{ 0xa8f2, 0xa8f7 },
{ 0xa8fb, 0xa8fb },
- { 0xa8fd, 0xa8fe },
- { 0xa90a, 0xa925 },
- { 0xa930, 0xa946 },
+ { 0xa8fd, 0xa8ff },
+ { 0xa90a, 0xa92a },
+ { 0xa930, 0xa952 },
{ 0xa960, 0xa97c },
- { 0xa984, 0xa9b2 },
+ { 0xa980, 0xa9b2 },
+ { 0xa9b4, 0xa9bf },
{ 0xa9cf, 0xa9cf },
- { 0xa9e0, 0xa9e4 },
- { 0xa9e6, 0xa9ef },
+ { 0xa9e0, 0xa9ef },
{ 0xa9fa, 0xa9fe },
- { 0xaa00, 0xaa28 },
- { 0xaa40, 0xaa42 },
- { 0xaa44, 0xaa4b },
+ { 0xaa00, 0xaa36 },
+ { 0xaa40, 0xaa4d },
{ 0xaa60, 0xaa76 },
- { 0xaa7a, 0xaa7a },
- { 0xaa7e, 0xaaaf },
- { 0xaab1, 0xaab1 },
- { 0xaab5, 0xaab6 },
- { 0xaab9, 0xaabd },
+ { 0xaa7a, 0xaabe },
{ 0xaac0, 0xaac0 },
{ 0xaac2, 0xaac2 },
{ 0xaadb, 0xaadd },
- { 0xaae0, 0xaaea },
- { 0xaaf2, 0xaaf4 },
+ { 0xaae0, 0xaaef },
+ { 0xaaf2, 0xaaf5 },
{ 0xab01, 0xab06 },
{ 0xab09, 0xab0e },
{ 0xab11, 0xab16 },
@@ -3113,7 +3262,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xab28, 0xab2e },
{ 0xab30, 0xab5a },
{ 0xab5c, 0xab69 },
- { 0xab70, 0xabe2 },
+ { 0xab70, 0xabea },
{ 0xac00, 0xd7a3 },
{ 0xd7b0, 0xd7c6 },
{ 0xd7cb, 0xd7fb },
@@ -3121,8 +3270,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0xfa70, 0xfad9 },
{ 0xfb00, 0xfb06 },
{ 0xfb13, 0xfb17 },
- { 0xfb1d, 0xfb1d },
- { 0xfb1f, 0xfb28 },
+ { 0xfb1d, 0xfb28 },
{ 0xfb2a, 0xfb36 },
{ 0xfb38, 0xfb3c },
{ 0xfb3e, 0xfb3e },
@@ -3149,15 +3297,16 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1003f, 0x1004d },
{ 0x10050, 0x1005d },
{ 0x10080, 0x100fa },
+ { 0x10140, 0x10174 },
{ 0x10280, 0x1029c },
{ 0x102a0, 0x102d0 },
{ 0x10300, 0x1031f },
- { 0x1032d, 0x10340 },
- { 0x10342, 0x10349 },
- { 0x10350, 0x10375 },
+ { 0x1032d, 0x1034a },
+ { 0x10350, 0x1037a },
{ 0x10380, 0x1039d },
{ 0x103a0, 0x103c3 },
{ 0x103c8, 0x103cf },
+ { 0x103d1, 0x103d5 },
{ 0x10400, 0x1049d },
{ 0x104b0, 0x104d3 },
{ 0x104d8, 0x104fb },
@@ -3171,6 +3320,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x105a3, 0x105b1 },
{ 0x105b3, 0x105b9 },
{ 0x105bb, 0x105bc },
+ { 0x105c0, 0x105f3 },
{ 0x10600, 0x10736 },
{ 0x10740, 0x10755 },
{ 0x10760, 0x10767 },
@@ -3191,8 +3341,9 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x10920, 0x10939 },
{ 0x10980, 0x109b7 },
{ 0x109be, 0x109bf },
- { 0x10a00, 0x10a00 },
- { 0x10a10, 0x10a13 },
+ { 0x10a00, 0x10a03 },
+ { 0x10a05, 0x10a06 },
+ { 0x10a0c, 0x10a13 },
{ 0x10a15, 0x10a17 },
{ 0x10a19, 0x10a35 },
{ 0x10a60, 0x10a7c },
@@ -3206,104 +3357,143 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x10c00, 0x10c48 },
{ 0x10c80, 0x10cb2 },
{ 0x10cc0, 0x10cf2 },
- { 0x10d00, 0x10d23 },
+ { 0x10d00, 0x10d27 },
+ { 0x10d4a, 0x10d65 },
+ { 0x10d69, 0x10d69 },
+ { 0x10d6f, 0x10d85 },
{ 0x10e80, 0x10ea9 },
+ { 0x10eab, 0x10eac },
{ 0x10eb0, 0x10eb1 },
+ { 0x10ec2, 0x10ec4 },
+ { 0x10efc, 0x10efc },
{ 0x10f00, 0x10f1c },
{ 0x10f27, 0x10f27 },
{ 0x10f30, 0x10f45 },
{ 0x10f70, 0x10f81 },
{ 0x10fb0, 0x10fc4 },
{ 0x10fe0, 0x10ff6 },
- { 0x11003, 0x11037 },
- { 0x11071, 0x11072 },
- { 0x11075, 0x11075 },
- { 0x11083, 0x110af },
+ { 0x11000, 0x11045 },
+ { 0x11071, 0x11075 },
+ { 0x11080, 0x110b8 },
+ { 0x110c2, 0x110c2 },
{ 0x110d0, 0x110e8 },
- { 0x11103, 0x11126 },
- { 0x11144, 0x11144 },
- { 0x11147, 0x11147 },
+ { 0x11100, 0x11132 },
+ { 0x11144, 0x11147 },
{ 0x11150, 0x11172 },
{ 0x11176, 0x11176 },
- { 0x11183, 0x111b2 },
+ { 0x11180, 0x111bf },
{ 0x111c1, 0x111c4 },
+ { 0x111ce, 0x111cf },
{ 0x111da, 0x111da },
{ 0x111dc, 0x111dc },
{ 0x11200, 0x11211 },
- { 0x11213, 0x1122b },
- { 0x1123f, 0x11240 },
+ { 0x11213, 0x11234 },
+ { 0x11237, 0x11237 },
+ { 0x1123e, 0x11241 },
{ 0x11280, 0x11286 },
{ 0x11288, 0x11288 },
{ 0x1128a, 0x1128d },
{ 0x1128f, 0x1129d },
{ 0x1129f, 0x112a8 },
- { 0x112b0, 0x112de },
+ { 0x112b0, 0x112e8 },
+ { 0x11300, 0x11303 },
{ 0x11305, 0x1130c },
{ 0x1130f, 0x11310 },
{ 0x11313, 0x11328 },
{ 0x1132a, 0x11330 },
{ 0x11332, 0x11333 },
{ 0x11335, 0x11339 },
- { 0x1133d, 0x1133d },
+ { 0x1133d, 0x11344 },
+ { 0x11347, 0x11348 },
+ { 0x1134b, 0x1134c },
{ 0x11350, 0x11350 },
- { 0x1135d, 0x11361 },
- { 0x11400, 0x11434 },
+ { 0x11357, 0x11357 },
+ { 0x1135d, 0x11363 },
+ { 0x11380, 0x11389 },
+ { 0x1138b, 0x1138b },
+ { 0x1138e, 0x1138e },
+ { 0x11390, 0x113b5 },
+ { 0x113b7, 0x113c0 },
+ { 0x113c2, 0x113c2 },
+ { 0x113c5, 0x113c5 },
+ { 0x113c7, 0x113ca },
+ { 0x113cc, 0x113cd },
+ { 0x113d1, 0x113d1 },
+ { 0x113d3, 0x113d3 },
+ { 0x11400, 0x11441 },
+ { 0x11443, 0x11445 },
{ 0x11447, 0x1144a },
{ 0x1145f, 0x11461 },
- { 0x11480, 0x114af },
+ { 0x11480, 0x114c1 },
{ 0x114c4, 0x114c5 },
{ 0x114c7, 0x114c7 },
- { 0x11580, 0x115ae },
- { 0x115d8, 0x115db },
- { 0x11600, 0x1162f },
+ { 0x11580, 0x115b5 },
+ { 0x115b8, 0x115be },
+ { 0x115d8, 0x115dd },
+ { 0x11600, 0x1163e },
+ { 0x11640, 0x11640 },
{ 0x11644, 0x11644 },
- { 0x11680, 0x116aa },
+ { 0x11680, 0x116b5 },
{ 0x116b8, 0x116b8 },
{ 0x11700, 0x1171a },
+ { 0x1171d, 0x1172a },
{ 0x11740, 0x11746 },
- { 0x11800, 0x1182b },
+ { 0x11800, 0x11838 },
{ 0x118a0, 0x118df },
{ 0x118ff, 0x11906 },
{ 0x11909, 0x11909 },
{ 0x1190c, 0x11913 },
{ 0x11915, 0x11916 },
- { 0x11918, 0x1192f },
- { 0x1193f, 0x1193f },
- { 0x11941, 0x11941 },
+ { 0x11918, 0x11935 },
+ { 0x11937, 0x11938 },
+ { 0x1193b, 0x1193c },
+ { 0x1193f, 0x11942 },
{ 0x119a0, 0x119a7 },
- { 0x119aa, 0x119d0 },
+ { 0x119aa, 0x119d7 },
+ { 0x119da, 0x119df },
{ 0x119e1, 0x119e1 },
- { 0x119e3, 0x119e3 },
- { 0x11a00, 0x11a00 },
- { 0x11a0b, 0x11a32 },
- { 0x11a3a, 0x11a3a },
- { 0x11a50, 0x11a50 },
- { 0x11a5c, 0x11a89 },
+ { 0x119e3, 0x119e4 },
+ { 0x11a00, 0x11a32 },
+ { 0x11a35, 0x11a3e },
+ { 0x11a50, 0x11a97 },
{ 0x11a9d, 0x11a9d },
{ 0x11ab0, 0x11af8 },
+ { 0x11bc0, 0x11be0 },
{ 0x11c00, 0x11c08 },
- { 0x11c0a, 0x11c2e },
+ { 0x11c0a, 0x11c36 },
+ { 0x11c38, 0x11c3e },
{ 0x11c40, 0x11c40 },
{ 0x11c72, 0x11c8f },
+ { 0x11c92, 0x11ca7 },
+ { 0x11ca9, 0x11cb6 },
{ 0x11d00, 0x11d06 },
{ 0x11d08, 0x11d09 },
- { 0x11d0b, 0x11d30 },
- { 0x11d46, 0x11d46 },
+ { 0x11d0b, 0x11d36 },
+ { 0x11d3a, 0x11d3a },
+ { 0x11d3c, 0x11d3d },
+ { 0x11d3f, 0x11d41 },
+ { 0x11d43, 0x11d43 },
+ { 0x11d46, 0x11d47 },
{ 0x11d60, 0x11d65 },
{ 0x11d67, 0x11d68 },
- { 0x11d6a, 0x11d89 },
+ { 0x11d6a, 0x11d8e },
+ { 0x11d90, 0x11d91 },
+ { 0x11d93, 0x11d96 },
{ 0x11d98, 0x11d98 },
- { 0x11ee0, 0x11ef2 },
- { 0x11f02, 0x11f02 },
- { 0x11f04, 0x11f10 },
- { 0x11f12, 0x11f33 },
+ { 0x11ee0, 0x11ef6 },
+ { 0x11f00, 0x11f10 },
+ { 0x11f12, 0x11f3a },
+ { 0x11f3e, 0x11f40 },
{ 0x11fb0, 0x11fb0 },
{ 0x12000, 0x12399 },
+ { 0x12400, 0x1246e },
{ 0x12480, 0x12543 },
{ 0x12f90, 0x12ff0 },
{ 0x13000, 0x1342f },
{ 0x13441, 0x13446 },
+ { 0x13460, 0x143fa },
{ 0x14400, 0x14646 },
+ { 0x16100, 0x1612e },
{ 0x16800, 0x16a38 },
{ 0x16a40, 0x16a5e },
{ 0x16a70, 0x16abe },
@@ -3312,15 +3502,17 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x16b40, 0x16b43 },
{ 0x16b63, 0x16b77 },
{ 0x16b7d, 0x16b8f },
+ { 0x16d40, 0x16d6c },
{ 0x16e40, 0x16e7f },
{ 0x16f00, 0x16f4a },
- { 0x16f50, 0x16f50 },
- { 0x16f93, 0x16f9f },
+ { 0x16f4f, 0x16f87 },
+ { 0x16f8f, 0x16f9f },
{ 0x16fe0, 0x16fe1 },
{ 0x16fe3, 0x16fe3 },
+ { 0x16ff0, 0x16ff1 },
{ 0x17000, 0x187f7 },
{ 0x18800, 0x18cd5 },
- { 0x18d00, 0x18d08 },
+ { 0x18cff, 0x18d08 },
{ 0x1aff0, 0x1aff3 },
{ 0x1aff5, 0x1affb },
{ 0x1affd, 0x1affe },
@@ -3334,6 +3526,7 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1bc70, 0x1bc7c },
{ 0x1bc80, 0x1bc88 },
{ 0x1bc90, 0x1bc99 },
+ { 0x1bc9e, 0x1bc9e },
{ 0x1d400, 0x1d454 },
{ 0x1d456, 0x1d49c },
{ 0x1d49e, 0x1d49f },
@@ -3366,19 +3559,28 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1d7c4, 0x1d7cb },
{ 0x1df00, 0x1df1e },
{ 0x1df25, 0x1df2a },
+ { 0x1e000, 0x1e006 },
+ { 0x1e008, 0x1e018 },
+ { 0x1e01b, 0x1e021 },
+ { 0x1e023, 0x1e024 },
+ { 0x1e026, 0x1e02a },
{ 0x1e030, 0x1e06d },
+ { 0x1e08f, 0x1e08f },
{ 0x1e100, 0x1e12c },
{ 0x1e137, 0x1e13d },
{ 0x1e14e, 0x1e14e },
{ 0x1e290, 0x1e2ad },
{ 0x1e2c0, 0x1e2eb },
{ 0x1e4d0, 0x1e4eb },
+ { 0x1e5d0, 0x1e5ed },
+ { 0x1e5f0, 0x1e5f0 },
{ 0x1e7e0, 0x1e7e6 },
{ 0x1e7e8, 0x1e7eb },
{ 0x1e7ed, 0x1e7ee },
{ 0x1e7f0, 0x1e7fe },
{ 0x1e800, 0x1e8c4 },
{ 0x1e900, 0x1e943 },
+ { 0x1e947, 0x1e947 },
{ 0x1e94b, 0x1e94b },
{ 0x1ee00, 0x1ee03 },
{ 0x1ee05, 0x1ee1f },
@@ -3413,6 +3615,9 @@ inline constexpr CharRange unicode_letter[] = {
{ 0x1eea1, 0x1eea3 },
{ 0x1eea5, 0x1eea9 },
{ 0x1eeab, 0x1eebb },
+ { 0x1f130, 0x1f149 },
+ { 0x1f150, 0x1f169 },
+ { 0x1f170, 0x1f189 },
{ 0x20000, 0x2a6df },
{ 0x2a700, 0x2b739 },
{ 0x2b740, 0x2b81d },
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
index 4acb81253f..62ab4e9584 100644
--- a/core/string/char_utils.h
+++ b/core/string/char_utils.h
@@ -38,97 +38,97 @@
#define BSEARCH_CHAR_RANGE(m_array) \
int low = 0; \
int high = sizeof(m_array) / sizeof(m_array[0]) - 1; \
- int middle; \
+ int middle = (low + high) / 2; \
\
while (low <= high) { \
- middle = (low + high) / 2; \
- \
- if (c < m_array[middle].start) { \
+ if (p_char < m_array[middle].start) { \
high = middle - 1; \
- } else if (c > m_array[middle].end) { \
+ } else if (p_char > m_array[middle].end) { \
low = middle + 1; \
} else { \
return true; \
} \
+ \
+ middle = (low + high) / 2; \
} \
\
return false
-static _FORCE_INLINE_ bool is_unicode_identifier_start(char32_t c) {
+constexpr bool is_unicode_identifier_start(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_start);
}
-static _FORCE_INLINE_ bool is_unicode_identifier_continue(char32_t c) {
+constexpr bool is_unicode_identifier_continue(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_continue);
}
-static _FORCE_INLINE_ bool is_unicode_upper_case(char32_t c) {
+constexpr bool is_unicode_upper_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(uppercase_letter);
}
-static _FORCE_INLINE_ bool is_unicode_lower_case(char32_t c) {
+constexpr bool is_unicode_lower_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(lowercase_letter);
}
-static _FORCE_INLINE_ bool is_unicode_letter(char32_t c) {
+constexpr bool is_unicode_letter(char32_t p_char) {
BSEARCH_CHAR_RANGE(unicode_letter);
}
#undef BSEARCH_CHAR_RANGE
-static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
+constexpr bool is_ascii_upper_case(char32_t p_char) {
+ return (p_char >= 'A' && p_char <= 'Z');
}
-static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
+constexpr bool is_ascii_lower_case(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z');
}
-static _FORCE_INLINE_ bool is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
+constexpr bool is_digit(char32_t p_char) {
+ return (p_char >= '0' && p_char <= '9');
}
-static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
- return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+constexpr bool is_hex_digit(char32_t p_char) {
+ return (is_digit(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F'));
}
-static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
- return (c == '0' || c == '1');
+constexpr bool is_binary_digit(char32_t p_char) {
+ return (p_char == '0' || p_char == '1');
}
-static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+constexpr bool is_ascii_alphabet_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z');
}
-static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
+constexpr bool is_ascii_alphanumeric_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9');
}
-static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+constexpr bool is_ascii_identifier_char(char32_t p_char) {
+ return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9') || p_char == '_';
}
-static _FORCE_INLINE_ bool is_symbol(char32_t c) {
- return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
+constexpr bool is_symbol(char32_t p_char) {
+ return p_char != '_' && ((p_char >= '!' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '`') || (p_char >= '{' && p_char <= '~') || p_char == '\t' || p_char == ' ');
}
-static _FORCE_INLINE_ bool is_control(char32_t p_char) {
+constexpr bool is_control(char32_t p_char) {
return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
}
-static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
+constexpr bool is_whitespace(char32_t p_char) {
return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
}
-static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
+constexpr bool is_linebreak(char32_t p_char) {
return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
}
-static _FORCE_INLINE_ bool is_punct(char32_t p_char) {
+constexpr bool is_punct(char32_t p_char) {
return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f);
}
-static _FORCE_INLINE_ bool is_underscore(char32_t p_char) {
+constexpr bool is_underscore(char32_t p_char) {
return (p_char == '_');
}
diff --git a/core/string/translation_domain.cpp b/core/string/translation_domain.cpp
index 6a5e1b2af8..53b9ce8379 100644
--- a/core/string/translation_domain.cpp
+++ b/core/string/translation_domain.cpp
@@ -389,6 +389,10 @@ void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) {
}
StringName TranslationDomain::pseudolocalize(const StringName &p_message) const {
+ if (p_message.is_empty()) {
+ return p_message;
+ }
+
String message = p_message;
int length = message.length();
if (pseudolocalization.override_enabled) {
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 89b37d0b8a..92b473b61f 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -228,32 +228,41 @@ int TranslationServer::compare_locales(const String &p_locale_a, const String &p
return 10;
}
+ const String cache_key = p_locale_a + "|" + p_locale_b;
+ const int *cached_result = locale_compare_cache.getptr(cache_key);
+ if (cached_result) {
+ return *cached_result;
+ }
+
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
if (locale_a == locale_b) {
// Exact match.
+ locale_compare_cache.insert(cache_key, 10);
return 10;
}
Vector<String> locale_a_elements = locale_a.split("_");
Vector<String> locale_b_elements = locale_b.split("_");
- if (locale_a_elements[0] == locale_b_elements[0]) {
- // Matching language, both locales have extra parts.
- // Return number of matching elements.
- int matching_elements = 1;
- for (int i = 1; i < locale_a_elements.size(); i++) {
- for (int j = 1; j < locale_b_elements.size(); j++) {
- if (locale_a_elements[i] == locale_b_elements[j]) {
- matching_elements++;
- }
- }
- }
- return matching_elements;
- } else {
+ if (locale_a_elements[0] != locale_b_elements[0]) {
// No match.
+ locale_compare_cache.insert(cache_key, 0);
return 0;
}
+
+ // Matching language, both locales have extra parts.
+ // Return number of matching elements.
+ int matching_elements = 1;
+ for (int i = 1; i < locale_a_elements.size(); i++) {
+ for (int j = 1; j < locale_b_elements.size(); j++) {
+ if (locale_a_elements[i] == locale_b_elements[j]) {
+ matching_elements++;
+ }
+ }
+ }
+ locale_compare_cache.insert(cache_key, matching_elements);
+ return matching_elements;
}
String TranslationServer::get_locale_name(const String &p_locale) const {
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
index a09230c019..2438349a69 100644
--- a/core/string/translation_server.h
+++ b/core/string/translation_server.h
@@ -46,6 +46,8 @@ class TranslationServer : public Object {
Ref<TranslationDomain> doc_domain;
HashMap<StringName, Ref<TranslationDomain>> custom_domains;
+ mutable HashMap<String, int> locale_compare_cache;
+
bool enabled = true;
static TranslationServer *singleton;
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index e6f7492a18..4e9eb922f6 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1850,6 +1850,8 @@ String String::num(double p_num, int p_decimals) {
}
String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
bool sign = p_num < 0;
int64_t n = p_num;
@@ -1888,6 +1890,8 @@ String String::num_int64(int64_t p_num, int base, bool capitalize_hex) {
}
String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) {
+ ERR_FAIL_COND_V_MSG(base < 2 || base > 36, "", "Cannot convert to base " + itos(base) + ", since the value is " + (base < 2 ? "less than 2." : "greater than 36."));
+
uint64_t n = p_num;
int chars = 0;
diff --git a/core/templates/a_hash_map.cpp b/core/templates/a_hash_map.cpp
new file mode 100644
index 0000000000..04a14c261a
--- /dev/null
+++ b/core/templates/a_hash_map.cpp
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* a_hash_map.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "a_hash_map.h"
+#include "core/variant/variant.h"
+
+// Explicit instantiation.
+template class AHashMap<int, int>;
+template class AHashMap<String, int>;
+template class AHashMap<StringName, StringName>;
+template class AHashMap<StringName, Variant>;
+template class AHashMap<StringName, int>;
diff --git a/core/templates/a_hash_map.h b/core/templates/a_hash_map.h
new file mode 100644
index 0000000000..29983ea268
--- /dev/null
+++ b/core/templates/a_hash_map.h
@@ -0,0 +1,732 @@
+/**************************************************************************/
+/* a_hash_map.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 A_HASH_MAP_H
+#define A_HASH_MAP_H
+
+#include "core/templates/hash_map.h"
+
+struct HashMapData {
+ union {
+ struct
+ {
+ uint32_t hash;
+ uint32_t hash_to_key;
+ };
+ uint64_t data;
+ };
+};
+
+static_assert(sizeof(HashMapData) == 8);
+
+/**
+ * An array-based implementation of a hash map. It is very efficient in terms of performance and
+ * memory usage. Works like a dynamic array, adding elements to the end of the array, and
+ * allows you to access array elements by their index by using `get_by_index` method.
+ * Example:
+ * ```
+ * AHashMap<int, Object *> map;
+ *
+ * int get_object_id_by_number(int p_number) {
+ * int id = map.get_index(p_number);
+ * return id;
+ * }
+ *
+ * Object *get_object_by_id(int p_id) {
+ * map.get_by_index(p_id).value;
+ * }
+ * ```
+ * Still, don`t erase the elements because ID can break.
+ *
+ * When an element erase, its place is taken by the element from the end.
+ *
+ * <-------------
+ * | |
+ * 6 8 X 9 32 -1 5 -10 7 X X X
+ * 6 8 7 9 32 -1 5 -10 X X X X
+ *
+ *
+ * Use RBMap if you need to iterate over sorted elements.
+ *
+ * Use HashMap if:
+ * - You need to keep an iterator or const pointer to Key and you intend to add/remove elements in the meantime.
+ * - You need to preserve the insertion order when using erase.
+ *
+ * It is recommended to use `HashMap` if `KeyValue` size is very large.
+ */
+template <typename TKey, typename TValue,
+ typename Hasher = HashMapHasherDefault,
+ typename Comparator = HashMapComparatorDefault<TKey>>
+class AHashMap {
+public:
+ // Must be a power of two.
+ static constexpr uint32_t INITIAL_CAPACITY = 16;
+ static constexpr uint32_t EMPTY_HASH = 0;
+ static_assert(EMPTY_HASH == 0, "EMPTY_HASH must always be 0 for the memcpy() optimization.");
+
+private:
+ typedef KeyValue<TKey, TValue> MapKeyValue;
+ MapKeyValue *elements = nullptr;
+ HashMapData *map_data = nullptr;
+
+ // Due to optimization, this is `capacity - 1`. Use + 1 to get normal capacity.
+ uint32_t capacity = 0;
+ uint32_t num_elements = 0;
+
+ uint32_t _hash(const TKey &p_key) const {
+ uint32_t hash = Hasher::hash(p_key);
+
+ if (unlikely(hash == EMPTY_HASH)) {
+ hash = EMPTY_HASH + 1;
+ }
+
+ return hash;
+ }
+
+ static _FORCE_INLINE_ uint32_t _get_resize_count(uint32_t p_capacity) {
+ return p_capacity ^ (p_capacity + 1) >> 2; // = get_capacity() * 0.75 - 1; Works only if p_capacity = 2^n - 1.
+ }
+
+ static _FORCE_INLINE_ uint32_t _get_probe_length(uint32_t p_pos, uint32_t p_hash, uint32_t p_local_capacity) {
+ const uint32_t original_pos = p_hash & p_local_capacity;
+ return (p_pos - original_pos + p_local_capacity + 1) & p_local_capacity;
+ }
+
+ bool _lookup_pos(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos) const {
+ if (unlikely(elements == nullptr)) {
+ return false; // Failed lookups, no elements.
+ }
+ return _lookup_pos_with_hash(p_key, r_pos, r_hash_pos, _hash(p_key));
+ }
+
+ bool _lookup_pos_with_hash(const TKey &p_key, uint32_t &r_pos, uint32_t &r_hash_pos, uint32_t p_hash) const {
+ if (unlikely(elements == nullptr)) {
+ return false; // Failed lookups, no elements.
+ }
+
+ uint32_t pos = p_hash & capacity;
+ HashMapData data = map_data[pos];
+ if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+ r_pos = data.hash_to_key;
+ r_hash_pos = pos;
+ return true;
+ }
+
+ if (data.data == EMPTY_HASH) {
+ return false;
+ }
+
+ // A collision occurred.
+ pos = (pos + 1) & capacity;
+ uint32_t distance = 1;
+ while (true) {
+ data = map_data[pos];
+ if (data.hash == p_hash && Comparator::compare(elements[data.hash_to_key].key, p_key)) {
+ r_pos = data.hash_to_key;
+ r_hash_pos = pos;
+ return true;
+ }
+
+ if (data.data == EMPTY_HASH) {
+ return false;
+ }
+
+ if (distance > _get_probe_length(pos, data.hash, capacity)) {
+ return false;
+ }
+
+ pos = (pos + 1) & capacity;
+ distance++;
+ }
+ }
+
+ uint32_t _insert_with_hash(uint32_t p_hash, uint32_t p_index) {
+ uint32_t pos = p_hash & capacity;
+
+ if (map_data[pos].data == EMPTY_HASH) {
+ uint64_t data = ((uint64_t)p_index << 32) | p_hash;
+ map_data[pos].data = data;
+ return pos;
+ }
+
+ uint32_t distance = 1;
+ pos = (pos + 1) & capacity;
+ HashMapData c_data;
+ c_data.hash = p_hash;
+ c_data.hash_to_key = p_index;
+
+ while (true) {
+ if (map_data[pos].data == EMPTY_HASH) {
+#ifdef DEV_ENABLED
+ if (unlikely(distance > 12)) {
+ WARN_PRINT("Excessive collision count (" +
+ itos(distance) + "), is the right hash function being used?");
+ }
+#endif
+ map_data[pos] = c_data;
+ return pos;
+ }
+
+ // Not an empty slot, let's check the probing length of the existing one.
+ uint32_t existing_probe_len = _get_probe_length(pos, map_data[pos].hash, capacity);
+ if (existing_probe_len < distance) {
+ SWAP(c_data, map_data[pos]);
+ distance = existing_probe_len;
+ }
+
+ pos = (pos + 1) & capacity;
+ distance++;
+ }
+ }
+
+ void _resize_and_rehash(uint32_t p_new_capacity) {
+ uint32_t real_old_capacity = capacity + 1;
+ // Capacity can't be 0 and must be 2^n - 1.
+ capacity = MAX(4u, p_new_capacity);
+ uint32_t real_capacity = next_power_of_2(capacity);
+ capacity = real_capacity - 1;
+
+ HashMapData *old_map_data = map_data;
+
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::realloc_static(elements, sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData));
+
+ if (num_elements != 0) {
+ for (uint32_t i = 0; i < real_old_capacity; i++) {
+ HashMapData data = old_map_data[i];
+ if (data.data != EMPTY_HASH) {
+ _insert_with_hash(data.hash, data.hash_to_key);
+ }
+ }
+ }
+
+ Memory::free_static(old_map_data);
+ }
+
+ int32_t _insert_element(const TKey &p_key, const TValue &p_value, uint32_t p_hash) {
+ if (unlikely(elements == nullptr)) {
+ // Allocate on demand to save memory.
+
+ uint32_t real_capacity = capacity + 1;
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ memset(map_data, EMPTY_HASH, real_capacity * sizeof(HashMapData));
+ }
+
+ if (unlikely(num_elements > _get_resize_count(capacity))) {
+ _resize_and_rehash(capacity * 2);
+ }
+
+ memnew_placement(&elements[num_elements], MapKeyValue(p_key, p_value));
+
+ _insert_with_hash(p_hash, num_elements);
+ num_elements++;
+ return num_elements - 1;
+ }
+
+ void _init_from(const AHashMap &p_other) {
+ capacity = p_other.capacity;
+ uint32_t real_capacity = capacity + 1;
+ num_elements = p_other.num_elements;
+
+ if (p_other.num_elements == 0) {
+ return;
+ }
+
+ map_data = reinterpret_cast<HashMapData *>(Memory::alloc_static(sizeof(HashMapData) * real_capacity));
+ elements = reinterpret_cast<MapKeyValue *>(Memory::alloc_static(sizeof(MapKeyValue) * (_get_resize_count(capacity) + 1)));
+
+ if constexpr (std::is_trivially_copyable_v<TKey> && std::is_trivially_copyable_v<TValue>) {
+ void *destination = elements;
+ const void *source = p_other.elements;
+ memcpy(destination, source, sizeof(MapKeyValue) * num_elements);
+ } else {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ memnew_placement(&elements[i], MapKeyValue(p_other.elements[i]));
+ }
+ }
+
+ memcpy(map_data, p_other.map_data, sizeof(HashMapData) * real_capacity);
+ }
+
+public:
+ /* Standard Godot Container API */
+
+ _FORCE_INLINE_ uint32_t get_capacity() const { return capacity + 1; }
+ _FORCE_INLINE_ uint32_t size() const { return num_elements; }
+
+ _FORCE_INLINE_ bool is_empty() const {
+ return num_elements == 0;
+ }
+
+ void clear() {
+ if (elements == nullptr || num_elements == 0) {
+ return;
+ }
+
+ memset(map_data, EMPTY_HASH, (capacity + 1) * sizeof(HashMapData));
+ if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ elements[i].key.~TKey();
+ elements[i].value.~TValue();
+ }
+ }
+
+ num_elements = 0;
+ }
+
+ TValue &get(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+ CRASH_COND_MSG(!exists, "AHashMap key not found.");
+ return elements[pos].value;
+ }
+
+ const TValue &get(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+ CRASH_COND_MSG(!exists, "AHashMap key not found.");
+ return elements[pos].value;
+ }
+
+ const TValue *getptr(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+ if (exists) {
+ return &elements[pos].value;
+ }
+ return nullptr;
+ }
+
+ TValue *getptr(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t hash_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, hash_pos);
+
+ if (exists) {
+ return &elements[pos].value;
+ }
+ return nullptr;
+ }
+
+ bool has(const TKey &p_key) const {
+ uint32_t _pos = 0;
+ uint32_t h_pos = 0;
+ return _lookup_pos(p_key, _pos, h_pos);
+ }
+
+ bool erase(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t element_pos = 0;
+ bool exists = _lookup_pos(p_key, element_pos, pos);
+
+ if (!exists) {
+ return false;
+ }
+
+ uint32_t next_pos = (pos + 1) & capacity;
+ while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+ SWAP(map_data[next_pos], map_data[pos]);
+
+ pos = next_pos;
+ next_pos = (next_pos + 1) & capacity;
+ }
+
+ map_data[pos].data = EMPTY_HASH;
+ elements[element_pos].key.~TKey();
+ elements[element_pos].value.~TValue();
+ num_elements--;
+
+ if (element_pos < num_elements) {
+ void *destination = &elements[element_pos];
+ const void *source = &elements[num_elements];
+ memcpy(destination, source, sizeof(MapKeyValue));
+ uint32_t h_pos = 0;
+ _lookup_pos(elements[num_elements].key, pos, h_pos);
+ map_data[h_pos].hash_to_key = element_pos;
+ }
+
+ return true;
+ }
+
+ // Replace the key of an entry in-place, without invalidating iterators or changing the entries position during iteration.
+ // p_old_key must exist in the map and p_new_key must not, unless it is equal to p_old_key.
+ bool replace_key(const TKey &p_old_key, const TKey &p_new_key) {
+ if (p_old_key == p_new_key) {
+ return true;
+ }
+ uint32_t pos = 0;
+ uint32_t element_pos = 0;
+ ERR_FAIL_COND_V(_lookup_pos(p_new_key, element_pos, pos), false);
+ ERR_FAIL_COND_V(!_lookup_pos(p_old_key, element_pos, pos), false);
+ MapKeyValue &element = elements[element_pos];
+ const_cast<TKey &>(element.key) = p_new_key;
+
+ uint32_t next_pos = (pos + 1) & capacity;
+ while (map_data[next_pos].hash != EMPTY_HASH && _get_probe_length(next_pos, map_data[next_pos].hash, capacity) != 0) {
+ SWAP(map_data[next_pos], map_data[pos]);
+
+ pos = next_pos;
+ next_pos = (next_pos + 1) & capacity;
+ }
+
+ map_data[pos].data = EMPTY_HASH;
+
+ uint32_t hash = _hash(p_new_key);
+ _insert_with_hash(hash, element_pos);
+
+ return true;
+ }
+
+ // Reserves space for a number of elements, useful to avoid many resizes and rehashes.
+ // If adding a known (possibly large) number of elements at once, must be larger than old capacity.
+ void reserve(uint32_t p_new_capacity) {
+ ERR_FAIL_COND_MSG(p_new_capacity < get_capacity(), "It is impossible to reserve less capacity than is currently available.");
+ if (elements == nullptr) {
+ capacity = MAX(4u, p_new_capacity);
+ capacity = next_power_of_2(capacity) - 1;
+ return; // Unallocated yet.
+ }
+ _resize_and_rehash(p_new_capacity);
+ }
+
+ /** Iterator API **/
+
+ struct ConstIterator {
+ _FORCE_INLINE_ const MapKeyValue &operator*() const {
+ return *pair;
+ }
+ _FORCE_INLINE_ const MapKeyValue *operator->() const {
+ return pair;
+ }
+ _FORCE_INLINE_ ConstIterator &operator++() {
+ pair++;
+ return *this;
+ }
+
+ _FORCE_INLINE_ ConstIterator &operator--() {
+ pair--;
+ if (pair < begin) {
+ pair = end;
+ }
+ return *this;
+ }
+
+ _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return pair == b.pair; }
+ _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return pair != b.pair; }
+
+ _FORCE_INLINE_ explicit operator bool() const {
+ return pair != end;
+ }
+
+ _FORCE_INLINE_ ConstIterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+ pair = p_key;
+ begin = p_begin;
+ end = p_end;
+ }
+ _FORCE_INLINE_ ConstIterator() {}
+ _FORCE_INLINE_ ConstIterator(const ConstIterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+ _FORCE_INLINE_ void operator=(const ConstIterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+
+ private:
+ MapKeyValue *pair = nullptr;
+ MapKeyValue *begin = nullptr;
+ MapKeyValue *end = nullptr;
+ };
+
+ struct Iterator {
+ _FORCE_INLINE_ MapKeyValue &operator*() const {
+ return *pair;
+ }
+ _FORCE_INLINE_ MapKeyValue *operator->() const {
+ return pair;
+ }
+ _FORCE_INLINE_ Iterator &operator++() {
+ pair++;
+ return *this;
+ }
+ _FORCE_INLINE_ Iterator &operator--() {
+ pair--;
+ if (pair < begin) {
+ pair = end;
+ }
+ return *this;
+ }
+
+ _FORCE_INLINE_ bool operator==(const Iterator &b) const { return pair == b.pair; }
+ _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return pair != b.pair; }
+
+ _FORCE_INLINE_ explicit operator bool() const {
+ return pair != end;
+ }
+
+ _FORCE_INLINE_ Iterator(MapKeyValue *p_key, MapKeyValue *p_begin, MapKeyValue *p_end) {
+ pair = p_key;
+ begin = p_begin;
+ end = p_end;
+ }
+ _FORCE_INLINE_ Iterator() {}
+ _FORCE_INLINE_ Iterator(const Iterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+ _FORCE_INLINE_ void operator=(const Iterator &p_it) {
+ pair = p_it.pair;
+ begin = p_it.begin;
+ end = p_it.end;
+ }
+
+ operator ConstIterator() const {
+ return ConstIterator(pair, begin, end);
+ }
+
+ private:
+ MapKeyValue *pair = nullptr;
+ MapKeyValue *begin = nullptr;
+ MapKeyValue *end = nullptr;
+ };
+
+ _FORCE_INLINE_ Iterator begin() {
+ return Iterator(elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ Iterator end() {
+ return Iterator(elements + num_elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ Iterator last() {
+ if (unlikely(num_elements == 0)) {
+ return Iterator(nullptr, nullptr, nullptr);
+ }
+ return Iterator(elements + num_elements - 1, elements, elements + num_elements);
+ }
+
+ Iterator find(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return end();
+ }
+ return Iterator(elements + pos, elements, elements + num_elements);
+ }
+
+ void remove(const Iterator &p_iter) {
+ if (p_iter) {
+ erase(p_iter->key);
+ }
+ }
+
+ _FORCE_INLINE_ ConstIterator begin() const {
+ return ConstIterator(elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ ConstIterator end() const {
+ return ConstIterator(elements + num_elements, elements, elements + num_elements);
+ }
+ _FORCE_INLINE_ ConstIterator last() const {
+ if (unlikely(num_elements == 0)) {
+ return ConstIterator(nullptr, nullptr, nullptr);
+ }
+ return ConstIterator(elements + num_elements - 1, elements, elements + num_elements);
+ }
+
+ ConstIterator find(const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return end();
+ }
+ return ConstIterator(elements + pos, elements, elements + num_elements);
+ }
+
+ /* Indexing */
+
+ const TValue &operator[](const TKey &p_key) const {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ CRASH_COND(!exists);
+ return elements[pos].value;
+ }
+
+ TValue &operator[](const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ uint32_t hash = _hash(p_key);
+ bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+ if (exists) {
+ return elements[pos].value;
+ } else {
+ pos = _insert_element(p_key, TValue(), hash);
+ return elements[pos].value;
+ }
+ }
+
+ /* Insert */
+
+ Iterator insert(const TKey &p_key, const TValue &p_value) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ uint32_t hash = _hash(p_key);
+ bool exists = _lookup_pos_with_hash(p_key, pos, h_pos, hash);
+
+ if (!exists) {
+ pos = _insert_element(p_key, p_value, hash);
+ } else {
+ elements[pos].value = p_value;
+ }
+ return Iterator(elements + pos, elements, elements + num_elements);
+ }
+
+ // Inserts an element without checking if it already exists.
+ void insert_new(const TKey &p_key, const TValue &p_value) {
+ DEV_ASSERT(!has(p_key));
+ uint32_t hash = _hash(p_key);
+ _insert_element(p_key, p_value, hash);
+ }
+
+ /* Array methods. */
+
+ // Unsafe. Changing keys and going outside the bounds of an array can lead to undefined behavior.
+ KeyValue<TKey, TValue> *get_elements_ptr() {
+ return elements;
+ }
+
+ // Returns the element index. If not found, returns -1.
+ int get_index(const TKey &p_key) {
+ uint32_t pos = 0;
+ uint32_t h_pos = 0;
+ bool exists = _lookup_pos(p_key, pos, h_pos);
+ if (!exists) {
+ return -1;
+ }
+ return pos;
+ }
+
+ KeyValue<TKey, TValue> &get_by_index(uint32_t p_index) {
+ CRASH_BAD_UNSIGNED_INDEX(p_index, num_elements);
+ return elements[p_index];
+ }
+
+ bool erase_by_index(uint32_t p_index) {
+ if (p_index >= size()) {
+ return false;
+ }
+ return erase(elements[p_index].key);
+ }
+
+ /* Constructors */
+
+ AHashMap(const AHashMap &p_other) {
+ _init_from(p_other);
+ }
+
+ AHashMap(const HashMap<TKey, TValue> &p_other) {
+ reserve(p_other.size());
+ for (const KeyValue<TKey, TValue> &E : p_other) {
+ uint32_t hash = _hash(E.key);
+ _insert_element(E.key, E.value, hash);
+ }
+ }
+
+ void operator=(const AHashMap &p_other) {
+ if (this == &p_other) {
+ return; // Ignore self assignment.
+ }
+
+ reset();
+
+ _init_from(p_other);
+ }
+
+ void operator=(const HashMap<TKey, TValue> &p_other) {
+ reset();
+ if (p_other.size() > get_capacity()) {
+ reserve(p_other.size());
+ }
+ for (const KeyValue<TKey, TValue> &E : p_other) {
+ uint32_t hash = _hash(E.key);
+ _insert_element(E.key, E.value, hash);
+ }
+ }
+
+ AHashMap(uint32_t p_initial_capacity) {
+ // Capacity can't be 0 and must be 2^n - 1.
+ capacity = MAX(4u, p_initial_capacity);
+ capacity = next_power_of_2(capacity) - 1;
+ }
+ AHashMap() :
+ capacity(INITIAL_CAPACITY - 1) {
+ }
+
+ void reset() {
+ if (elements != nullptr) {
+ if constexpr (!(std::is_trivially_destructible_v<TKey> && std::is_trivially_destructible_v<TValue>)) {
+ for (uint32_t i = 0; i < num_elements; i++) {
+ elements[i].key.~TKey();
+ elements[i].value.~TValue();
+ }
+ }
+ Memory::free_static(elements);
+ Memory::free_static(map_data);
+ elements = nullptr;
+ }
+ capacity = INITIAL_CAPACITY - 1;
+ num_elements = 0;
+ }
+
+ ~AHashMap() {
+ reset();
+ }
+};
+
+extern template class AHashMap<int, int>;
+extern template class AHashMap<String, int>;
+extern template class AHashMap<StringName, StringName>;
+extern template class AHashMap<StringName, Variant>;
+extern template class AHashMap<StringName, int>;
+
+#endif // A_HASH_MAP_H
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index 21eef10297..7818ed0706 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -393,6 +393,13 @@ struct HashMapHasherDefault {
}
};
+struct HashHasher {
+ static _FORCE_INLINE_ uint32_t hash(const int32_t hash) { return hash; }
+ static _FORCE_INLINE_ uint32_t hash(const uint32_t hash) { return hash; }
+ static _FORCE_INLINE_ uint64_t hash(const int64_t hash) { return hash; }
+ static _FORCE_INLINE_ uint64_t hash(const uint64_t hash) { return hash; }
+};
+
// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed
template <typename T>
struct HashableHasher {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index c76b849abd..3b1924e8ea 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -854,6 +854,19 @@ struct StringLikeVariantComparator {
static bool compare(const Variant &p_lhs, const Variant &p_rhs);
};
+struct StringLikeVariantOrder {
+ static _ALWAYS_INLINE_ bool compare(const Variant &p_lhs, const Variant &p_rhs) {
+ if (p_lhs.is_string() && p_rhs.is_string()) {
+ return p_lhs.operator String() < p_rhs.operator String();
+ }
+ return p_lhs < p_rhs;
+ }
+
+ _ALWAYS_INLINE_ bool operator()(const Variant &p_lhs, const Variant &p_rhs) const {
+ return compare(p_lhs, p_rhs);
+ }
+};
+
Variant::ObjData &Variant::_get_obj() {
return *reinterpret_cast<ObjData *>(&_data._mem[0]);
}
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index d2c1cde970..ce27fbdf67 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -980,6 +980,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInDictionaryHas<Color>>(Variant::OP_IN, Variant::COLOR, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<StringName>>(Variant::OP_IN, Variant::STRING_NAME, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<NodePath>>(Variant::OP_IN, Variant::NODE_PATH, Variant::DICTIONARY);
+ register_op<OperatorEvaluatorInDictionaryHas<::RID>>(Variant::OP_IN, Variant::RID, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHasObject>(Variant::OP_IN, Variant::OBJECT, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Callable>>(Variant::OP_IN, Variant::CALLABLE, Variant::DICTIONARY);
register_op<OperatorEvaluatorInDictionaryHas<Signal>>(Variant::OP_IN, Variant::SIGNAL, Variant::DICTIONARY);
@@ -1021,6 +1022,7 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorInArrayFind<Color, Array>>(Variant::OP_IN, Variant::COLOR, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<StringName, Array>>(Variant::OP_IN, Variant::STRING_NAME, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<NodePath, Array>>(Variant::OP_IN, Variant::NODE_PATH, Variant::ARRAY);
+ register_op<OperatorEvaluatorInArrayFind<::RID, Array>>(Variant::OP_IN, Variant::RID, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFindObject>(Variant::OP_IN, Variant::OBJECT, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Callable, Array>>(Variant::OP_IN, Variant::CALLABLE, Variant::ARRAY);
register_op<OperatorEvaluatorInArrayFind<Signal, Array>>(Variant::OP_IN, Variant::SIGNAL, Variant::ARRAY);
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index f5f96456d3..f05b9cd83a 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -2245,7 +2245,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
} else {
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
if (keys.is_empty()) {
// Avoid unnecessary line break.
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 55d00b6cf9..66f15f7494 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -6,7 +6,7 @@
<description>
A list of global scope enumerated constants and built-in functions. This is all that resides in the globals, constants regarding error codes, keycodes, property hints, etc.
Singletons are also documented here, since they can be accessed from anywhere.
- For the entries related to GDScript which can be accessed in any script see [@GDScript].
+ For the entries that can only be accessed from scripts written in GDScript, see [@GDScript].
</description>
<tutorials>
<link title="Random number generation">$DOCS_URL/tutorials/math/random_number_generation.html</link>
diff --git a/doc/classes/AtlasTexture.xml b/doc/classes/AtlasTexture.xml
index 45877f4003..287f2cc19a 100644
--- a/doc/classes/AtlasTexture.xml
+++ b/doc/classes/AtlasTexture.xml
@@ -6,7 +6,7 @@
<description>
[Texture2D] resource that draws only part of its [member atlas] texture, as defined by the [member region]. An additional [member margin] can also be set, which is useful for small adjustments.
Multiple [AtlasTexture] resources can be cropped from the same [member atlas]. Packing many smaller textures into a singular large texture helps to optimize video memory costs and render calls.
- [b]Note:[/b] [AtlasTexture] cannot be used in an [AnimatedTexture], and may not tile properly in nodes such as [TextureRect], when inside other [AtlasTexture] resources.
+ [b]Note:[/b] [AtlasTexture] cannot be used in an [AnimatedTexture], and will not tile properly in nodes such as [TextureRect] or [Sprite2D]. To tile an [AtlasTexture], modify its [member region] instead.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/AudioEffectFilter.xml b/doc/classes/AudioEffectFilter.xml
index e5c1f4ccf4..18540de736 100644
--- a/doc/classes/AudioEffectFilter.xml
+++ b/doc/classes/AudioEffectFilter.xml
@@ -14,6 +14,7 @@
Threshold frequency for the filter, in Hz.
</member>
<member name="db" type="int" setter="set_db" getter="get_db" enum="AudioEffectFilter.FilterDB" default="0">
+ Steepness of the cutoff curve in dB per octave, also known as the order of the filter. Higher orders have a more aggressive cutoff.
</member>
<member name="gain" type="float" setter="set_gain" getter="get_gain" default="1.0">
Gain amount of the frequencies after the filter.
@@ -24,12 +25,16 @@
</members>
<constants>
<constant name="FILTER_6DB" value="0" enum="FilterDB">
+ Cutting off at 6dB per octave.
</constant>
<constant name="FILTER_12DB" value="1" enum="FilterDB">
+ Cutting off at 12dB per octave.
</constant>
<constant name="FILTER_18DB" value="2" enum="FilterDB">
+ Cutting off at 18dB per octave.
</constant>
<constant name="FILTER_24DB" value="3" enum="FilterDB">
+ Cutting off at 24dB per octave.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioEffectStereoEnhance.xml b/doc/classes/AudioEffectStereoEnhance.xml
index f009bec5bb..459ae3ebc6 100644
--- a/doc/classes/AudioEffectStereoEnhance.xml
+++ b/doc/classes/AudioEffectStereoEnhance.xml
@@ -11,11 +11,13 @@
</tutorials>
<members>
<member name="pan_pullout" type="float" setter="set_pan_pullout" getter="get_pan_pullout" default="1.0">
- Values greater than 1.0 increase intensity of any panning on audio passing through this effect, whereas values less than 1.0 will decrease the panning intensity. A value of 0.0 will downmix audio to mono.
+ Amplifies the difference between stereo channels, increasing or decreasing existing panning. A value of 0.0 will downmix stereo to mono. Does not affect a mono signal.
</member>
<member name="surround" type="float" setter="set_surround" getter="get_surround" default="0.0">
+ Widens sound stage through phase shifting in conjunction with [member time_pullout_ms]. Just pans sound to the left channel if [member time_pullout_ms] is 0.
</member>
<member name="time_pullout_ms" type="float" setter="set_time_pullout" getter="get_time_pullout" default="0.0">
+ Widens sound stage through phase shifting in conjunction with [member surround]. Just delays the right channel if [member surround] is 0.
</member>
</members>
</class>
diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml
index 6830c632cf..4a20736164 100644
--- a/doc/classes/AudioServer.xml
+++ b/doc/classes/AudioServer.xml
@@ -110,6 +110,12 @@
Returns the volume of the bus at index [param bus_idx] in dB.
</description>
</method>
+ <method name="get_driver_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns the name of the current audio driver. The default usually depends on the operating system, but may be overridden via the [code]--audio-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. [code]--headless[/code] also automatically sets the audio driver to [code]Dummy[/code]. See also [member ProjectSettings.audio/driver/driver].
+ </description>
+ </method>
<method name="get_input_device_list">
<return type="PackedStringArray" />
<description>
diff --git a/doc/classes/AudioStreamPlayback.xml b/doc/classes/AudioStreamPlayback.xml
index 02f3407f79..f01406d0f1 100644
--- a/doc/classes/AudioStreamPlayback.xml
+++ b/doc/classes/AudioStreamPlayback.xml
@@ -79,12 +79,47 @@
Overridable method. Called whenever the audio stream is mixed if the playback is active and [method AudioServer.set_enable_tagging_used_audio_streams] has been set to [code]true[/code]. Editor plugins may use this method to "tag" the current position along the audio stream and display it in a preview.
</description>
</method>
+ <method name="get_loop_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns the number of times the stream has looped.
+ </description>
+ </method>
+ <method name="get_playback_position" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the current position in the stream, in seconds.
+ </description>
+ </method>
<method name="get_sample_playback" qualifiers="const" experimental="">
<return type="AudioSamplePlayback" />
<description>
Returns the [AudioSamplePlayback] associated with this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
+ <method name="is_playing" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the stream is playing.
+ </description>
+ </method>
+ <method name="mix_audio">
+ <return type="PackedVector2Array" />
+ <param index="0" name="rate_scale" type="float" />
+ <param index="1" name="frames" type="int" />
+ <description>
+ Mixes up to [param frames] of audio from the stream from the current position, at a rate of [param rate_scale], advancing the stream.
+ Returns a [PackedVector2Array] where each element holds the left and right channel volume levels of each frame.
+ [b]Note:[/b] Can return fewer frames than requested, make sure to use the size of the return value.
+ </description>
+ </method>
+ <method name="seek">
+ <return type="void" />
+ <param index="0" name="time" type="float" default="0.0" />
+ <description>
+ Seeks the stream at the given [param time], in seconds.
+ </description>
+ </method>
<method name="set_sample_playback" experimental="">
<return type="void" />
<param index="0" name="playback_sample" type="AudioSamplePlayback" />
@@ -92,5 +127,18 @@
Associates [AudioSamplePlayback] to this [AudioStreamPlayback] for playing back the audio sample of this stream.
</description>
</method>
+ <method name="start">
+ <return type="void" />
+ <param index="0" name="from_pos" type="float" default="0.0" />
+ <description>
+ Starts the stream from the given [param from_pos], in seconds.
+ </description>
+ </method>
+ <method name="stop">
+ <return type="void" />
+ <description>
+ Stops the stream.
+ </description>
+ </method>
</methods>
</class>
diff --git a/doc/classes/BackBufferCopy.xml b/doc/classes/BackBufferCopy.xml
index 23d6bc3851..eb65765312 100644
--- a/doc/classes/BackBufferCopy.xml
+++ b/doc/classes/BackBufferCopy.xml
@@ -8,6 +8,7 @@
[b]Note:[/b] Since this node inherits from [Node2D] (and not [Control]), anchors and margins won't apply to child [Control]-derived nodes. This can be problematic when resizing the window. To avoid this, add [Control]-derived nodes as [i]siblings[/i] to the [BackBufferCopy] node instead of adding them as children.
</description>
<tutorials>
+ <link title="Screen-reading shaders">$DOCS_URL/tutorials/shaders/screen-reading_shaders.html</link>
</tutorials>
<members>
<member name="copy_mode" type="int" setter="set_copy_mode" getter="get_copy_mode" enum="BackBufferCopy.CopyMode" default="1">
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index bc17c8b008..78e9c392db 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -533,9 +533,13 @@
</method>
<method name="make_canvas_position_local" qualifiers="const">
<return type="Vector2" />
- <param index="0" name="screen_point" type="Vector2" />
+ <param index="0" name="viewport_point" type="Vector2" />
<description>
- Assigns [param screen_point] as this node's new local transform.
+ Transforms [param viewport_point] from the viewport's coordinates to this node's local coordinates.
+ For the opposite operation, use [method get_global_transform_with_canvas].
+ [codeblock]
+ var viewport_point = get_global_transform_with_canvas() * local_point
+ [/codeblock]
</description>
</method>
<method name="make_input_local" qualifiers="const">
diff --git a/doc/classes/CanvasModulate.xml b/doc/classes/CanvasModulate.xml
index 7db0361020..43505498b3 100644
--- a/doc/classes/CanvasModulate.xml
+++ b/doc/classes/CanvasModulate.xml
@@ -7,6 +7,7 @@
[CanvasModulate] applies a color tint to all nodes on a canvas. Only one can be used to tint a canvas, but [CanvasLayer]s can be used to render things independently.
</description>
<tutorials>
+ <link title="2D lights and shadows">$DOCS_URL/tutorials/2d/2d_lights_and_shadows.html</link>
</tutorials>
<members>
<member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)" keywords="colour">
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 9d36bc657b..702777466f 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -65,7 +65,7 @@
[csharp]
public override bool _CanDropData(Vector2 atPosition, Variant data)
{
- return data.VariantType == Variant.Type.Dictionary &amp;&amp; dict.AsGodotDictionary().ContainsKey("color");
+ return data.VariantType == Variant.Type.Dictionary &amp;&amp; data.AsGodotDictionary().ContainsKey("color");
}
public override void _DropData(Vector2 atPosition, Variant data)
@@ -1365,7 +1365,7 @@
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
Automatic layout direction, determined from the parent control layout direction.
</constant>
- <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">
+ <constant name="LAYOUT_DIRECTION_APPLICATION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale.
</constant>
<constant name="LAYOUT_DIRECTION_LTR" value="2" enum="LayoutDirection">
@@ -1374,6 +1374,14 @@
<constant name="LAYOUT_DIRECTION_RTL" value="3" enum="LayoutDirection">
Right-to-left layout direction.
</constant>
+ <constant name="LAYOUT_DIRECTION_SYSTEM_LOCALE" value="4" enum="LayoutDirection">
+ Automatic layout direction, determined from the system locale.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_MAX" value="5" enum="LayoutDirection">
+ Represents the size of the [enum LayoutDirection] enum.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection" deprecated="Use [constant LAYOUT_DIRECTION_APPLICATION_LOCALE] instead.">
+ </constant>
<constant name="TEXT_DIRECTION_INHERITED" value="3" enum="TextDirection">
Text writing direction is the same as layout direction.
</constant>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 37bf265d20..45e802519e 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1683,6 +1683,7 @@
<param index="1" name="window_id" type="int" default="0" />
<description>
Sets window mode for the given window to [param mode]. See [enum WindowMode] for possible values and how each mode behaves.
+ [b]Note:[/b] On Android, setting it to [constant WINDOW_MODE_FULLSCREEN] or [constant WINDOW_MODE_EXCLUSIVE_FULLSCREEN] will enable immersive mode.
[b]Note:[/b] Setting the window to full screen forcibly sets the borderless flag to [code]true[/code], so make sure to set it back to [code]false[/code] when not wanted.
</description>
</method>
@@ -2058,6 +2059,7 @@
<constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode">
Full screen mode with full multi-window support.
Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Android:[/b] This enables immersive mode.
[b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
[b]On macOS:[/b] A new desktop is used to display the running project.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
@@ -2065,6 +2067,7 @@
<constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode">
A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
+ [b]On Android:[/b] This enables immersive mode.
[b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
[b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
[b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
@@ -2099,7 +2102,11 @@
<constant name="WINDOW_FLAG_MOUSE_PASSTHROUGH" value="7" enum="WindowFlags">
All mouse events are passed to the underlying window of the same application.
</constant>
- <constant name="WINDOW_FLAG_MAX" value="8" enum="WindowFlags">
+ <constant name="WINDOW_FLAG_SHARP_CORNERS" value="8" enum="WindowFlags">
+ Window style is overridden, forcing sharp corners.
+ [b]Note:[/b] This flag is implemented only on Windows (11).
+ </constant>
+ <constant name="WINDOW_FLAG_MAX" value="9" enum="WindowFlags">
Max value of the [enum WindowFlags].
</constant>
<constant name="WINDOW_EVENT_MOUSE_ENTER" value="0" enum="WindowEvent">
@@ -2145,12 +2152,14 @@
<constant name="DISPLAY_HANDLE" value="0" enum="HandleType">
Display handle:
- Linux (X11): [code]X11::Display*[/code] for the display.
+ - Linux (Wayland): [code]wl_display[/code] for the display.
- Android: [code]EGLDisplay[/code] for the display.
</constant>
<constant name="WINDOW_HANDLE" value="1" enum="HandleType">
Window handle:
- Windows: [code]HWND[/code] for the window.
- Linux (X11): [code]X11::Window*[/code] for the window.
+ - Linux (Wayland): [code]wl_surface[/code] for the window.
- macOS: [code]NSWindow*[/code] for the window.
- iOS: [code]UIViewController*[/code] for the view controller.
- Android: [code]jObject[/code] for the activity.
@@ -2165,9 +2174,20 @@
OpenGL context (only with the GL Compatibility renderer):
- Windows: [code]HGLRC[/code] for the window (native GL), or [code]EGLContext[/code] for the window (ANGLE).
- Linux (X11): [code]GLXContext*[/code] for the window.
+ - Linux (Wayland): [code]EGLContext[/code] for the window.
- macOS: [code]NSOpenGLContext*[/code] for the window (native GL), or [code]EGLContext[/code] for the window (ANGLE).
- Android: [code]EGLContext[/code] for the window.
</constant>
+ <constant name="EGL_DISPLAY" value="4" enum="HandleType">
+ - Windows: [code]EGLDisplay[/code] for the window (ANGLE).
+ - macOS: [code]EGLDisplay[/code] for the window (ANGLE).
+ - Linux (Wayland): [code]EGLDisplay[/code] for the window.
+ </constant>
+ <constant name="EGL_CONFIG" value="5" enum="HandleType">
+ - Windows: [code]EGLConfig[/code] for the window (ANGLE).
+ - macOS: [code]EGLConfig[/code] for the window (ANGLE).
+ - Linux (Wayland): [code]EGLConfig[/code] for the window.
+ </constant>
<constant name="TTS_UTTERANCE_STARTED" value="0" enum="TTSUtteranceEvent">
Utterance has begun to be spoken.
</constant>
diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml
index a519e43bc6..d513a4fce0 100644
--- a/doc/classes/EditorDebuggerPlugin.xml
+++ b/doc/classes/EditorDebuggerPlugin.xml
@@ -15,18 +15,20 @@
class ExampleEditorDebugger extends EditorDebuggerPlugin:
- func _has_capture(prefix):
- # Return true if you wish to handle message with this prefix.
- return prefix == "my_plugin"
+ func _has_capture(capture):
+ # Return true if you wish to handle messages with the prefix "my_plugin:".
+ return capture == "my_plugin"
func _capture(message, data, session_id):
if message == "my_plugin:ping":
get_session(session_id).send_message("my_plugin:echo", data)
+ return true
+ return false
func _setup_session(session_id):
# Add a new tab in the debugger session UI containing a label.
var label = Label.new()
- label.name = "Example plugin"
+ label.name = "Example plugin" # Will be used as the tab title.
label.text = "Example plugin"
var session = get_session(session_id)
# Listens to the session started and stopped signals.
@@ -43,6 +45,24 @@
remove_debugger_plugin(debugger)
[/gdscript]
[/codeblocks]
+ To connect on the running game side, use the [EngineDebugger] singleton:
+ [codeblocks]
+ [gdscript]
+ extends Node
+
+ func _ready():
+ EngineDebugger.register_message_capture("my_plugin", _capture)
+ EngineDebugger.send_message("my_plugin:ping", ["test"])
+
+ func _capture(message, data):
+ # Note that the "my_plugin:" prefix is not used here.
+ if message == "echo":
+ prints("Echo received:", data)
+ return true
+ return false
+ [/gdscript]
+ [/codeblocks]
+ [b]Note:[/b] While the game is running, [method @GlobalScope.print] and similar functions [i]called in the editor[/i] do not print anything, the Output Log prints only game messages.
</description>
<tutorials>
</tutorials>
@@ -68,7 +88,7 @@
<param index="1" name="data" type="Array" />
<param index="2" name="session_id" type="int" />
<description>
- Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the message (which you can retrieve via [method get_session]).
+ Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the [param message]. Use [method get_session] to retrieve the session. This method should return [code]true[/code] if the message is recognized.
</description>
</method>
<method name="_goto_script_line" qualifiers="virtual">
@@ -90,7 +110,7 @@
<return type="void" />
<param index="0" name="session_id" type="int" />
<description>
- Override this method to be notified whenever a new [EditorDebuggerSession] is created (the session may be inactive during this stage).
+ Override this method to be notified whenever a new [EditorDebuggerSession] is created. Note that the session may be inactive during this stage.
</description>
</method>
<method name="get_session">
diff --git a/doc/classes/EditorDebuggerSession.xml b/doc/classes/EditorDebuggerSession.xml
index b4e754cc7e..f5fa3e34d5 100644
--- a/doc/classes/EditorDebuggerSession.xml
+++ b/doc/classes/EditorDebuggerSession.xml
@@ -14,7 +14,7 @@
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
- Adds the given [param control] to the debug session UI in the debugger bottom panel.
+ Adds the given [param control] to the debug session UI in the debugger bottom panel. The [param control]'s node name will be used as the tab title.
</description>
</method>
<method name="is_active">
diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml
index bcc1ac5299..3540f098ba 100644
--- a/doc/classes/EngineDebugger.xml
+++ b/doc/classes/EngineDebugger.xml
@@ -113,7 +113,8 @@
<param index="1" name="callable" type="Callable" />
<description>
Registers a message capture with given [param name]. If [param name] is "my_message" then messages starting with "my_message:" will be called with the given callable.
- Callable must accept a message string and a data array as argument. If the message and data are valid then callable must return [code]true[/code] otherwise [code]false[/code].
+ The callable must accept a message string and a data array as argument. The callable should return [code]true[/code] if the message is recognized.
+ [b]Note:[/b] The callable will receive the message with the prefix stripped, unlike [method EditorDebuggerPlugin._capture]. See the [EditorDebuggerPlugin] description for an example.
</description>
</method>
<method name="register_profiler">
diff --git a/doc/classes/Geometry2D.xml b/doc/classes/Geometry2D.xml
index 2dd76ad989..71e6cf93ae 100644
--- a/doc/classes/Geometry2D.xml
+++ b/doc/classes/Geometry2D.xml
@@ -9,6 +9,20 @@
<tutorials>
</tutorials>
<methods>
+ <method name="bresenham_line">
+ <return type="Vector2i[]" />
+ <param index="0" name="from" type="Vector2i" />
+ <param index="1" name="to" type="Vector2i" />
+ <description>
+ Returns the [url=https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm]Bresenham line[/url] between the [param from] and [param to] points. A Bresenham line is a series of pixels that draws a line and is always 1-pixel thick on every row and column of the drawing (never more, never less).
+ Example code to draw a line between two [Marker2D] nodes using a series of [method CanvasItem.draw_rect] calls:
+ [codeblock]
+ func _draw():
+ for pixel in Geometry2D.bresenham_line($MarkerA.position, $MarkerB.position):
+ draw_rect(Rect2(pixel, Vector2.ONE), Color.WHITE)
+ [/codeblock]
+ </description>
+ </method>
<method name="clip_polygons">
<return type="PackedVector2Array[]" />
<param index="0" name="polygon_a" type="PackedVector2Array" />
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 6efa675a71..a4adf4d1b1 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -275,6 +275,7 @@
Request successful.
</constant>
<constant name="RESULT_CHUNKED_BODY_SIZE_MISMATCH" value="1" enum="Result">
+ Request failed due to a mismatch between the expected and actual chunked body size during transfer. Possible causes include network errors, server misconfiguration, or issues with chunked encoding.
</constant>
<constant name="RESULT_CANT_CONNECT" value="2" enum="Result">
Request failed while connecting.
@@ -295,6 +296,7 @@
Request exceeded its maximum size limit, see [member body_size_limit].
</constant>
<constant name="RESULT_BODY_DECOMPRESS_FAILED" value="8" enum="Result">
+ Request failed due to an error while decompressing the response body. Possible causes include unsupported or incorrect compression format, corrupted data, or incomplete transfer.
</constant>
<constant name="RESULT_REQUEST_FAILED" value="9" enum="Result">
Request failed (currently unused).
diff --git a/doc/classes/InputEventMouseMotion.xml b/doc/classes/InputEventMouseMotion.xml
index bcfe5b70fd..4c1461d03a 100644
--- a/doc/classes/InputEventMouseMotion.xml
+++ b/doc/classes/InputEventMouseMotion.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Stores information about a mouse or a pen motion. This includes relative position, absolute position, and velocity. See [method Node._input].
- [b]Note:[/b] By default, this event is only emitted once per frame rendered at most. If you need more precise input reporting, set [member Input.use_accumulated_input] to [code]false[/code] to make events emitted as often as possible. If you use InputEventMouseMotion to draw lines, consider implementing [url=https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm]Bresenham's line algorithm[/url] as well to avoid visible gaps in lines if the user is moving the mouse quickly.
+ [b]Note:[/b] By default, this event is only emitted once per frame rendered at most. If you need more precise input reporting, set [member Input.use_accumulated_input] to [code]false[/code] to make events emitted as often as possible. If you use InputEventMouseMotion to draw lines, consider using [method Geometry2D.bresenham_line] as well to avoid visible gaps in lines if the user is moving the mouse quickly.
[b]Note:[/b] This event may be emitted even when the mouse hasn't moved, either by the operating system or by Godot itself. If you really need to know if the mouse has moved (e.g. to suppress displaying a tooltip), you should check that [code]relative.is_zero_approx()[/code] is [code]false[/code].
</description>
<tutorials>
diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml
index bda5fb69de..966d0fdcb4 100644
--- a/doc/classes/Light3D.xml
+++ b/doc/classes/Light3D.xml
@@ -115,6 +115,9 @@
<member name="shadow_blur" type="float" setter="set_param" getter="get_param" default="1.0">
Blurs the edges of the shadow. Can be used to hide pixel artifacts in low-resolution shadow maps. A high value can impact performance, make shadows appear grainy and can cause other unwanted artifacts. Try to keep as near default as possible.
</member>
+ <member name="shadow_caster_mask" type="int" setter="set_shadow_caster_mask" getter="get_shadow_caster_mask" default="4294967295">
+ The light will only cast shadows using objects in the selected layers.
+ </member>
<member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false">
If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D].
</member>
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index 3e0c328dcb..91c9072f73 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -33,6 +33,7 @@
- [kbd]Cmd + E[/kbd]: Same as [kbd]End[/kbd], move the caret to the end of the line
- [kbd]Cmd + Left Arrow[/kbd]: Same as [kbd]Home[/kbd], move the caret to the beginning of the line
- [kbd]Cmd + Right Arrow[/kbd]: Same as [kbd]End[/kbd], move the caret to the end of the line
+ [b]Note:[/b] Caret movement shortcuts listed above are not affected by [member shortcut_keys_enabled].
</description>
<tutorials>
</tutorials>
@@ -334,7 +335,7 @@
If [code]false[/code], it's impossible to select the text using mouse nor keyboard.
</member>
<member name="shortcut_keys_enabled" type="bool" setter="set_shortcut_keys_enabled" getter="is_shortcut_keys_enabled" default="true">
- If [code]false[/code], using shortcuts will be disabled.
+ If [code]true[/code], shortcut keys for context menu items are enabled, even if the context menu is disabled.
</member>
<member name="structured_text_bidi_override" type="int" setter="set_structured_text_bidi_override" getter="get_structured_text_bidi_override" enum="TextServer.StructuredTextParser" default="0">
Set BiDi algorithm override for the structured text.
diff --git a/doc/classes/Material.xml b/doc/classes/Material.xml
index 760773d5d9..94d12018ca 100644
--- a/doc/classes/Material.xml
+++ b/doc/classes/Material.xml
@@ -45,7 +45,7 @@
<method name="inspect_native_shader_code">
<return type="void" />
<description>
- Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code.
+ Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code. See also [method Shader.inspect_native_shader_code].
</description>
</method>
</methods>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 42753f7071..c07948b546 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -73,6 +73,7 @@
<description>
Called during the physics processing step of the main loop. Physics processing means that the frame rate is synced to the physics, i.e. the [param delta] variable should be constant. [param delta] is in seconds.
It is only called if physics processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_physics_process].
+ Processing happens in order of [member process_physics_priority], lower priority values are called first. Nodes with the same priority are processed in tree order, or top to bottom as seen in the editor (also known as pre-order traversal).
Corresponds to the [constant NOTIFICATION_PHYSICS_PROCESS] notification in [method Object._notification].
[b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not an orphan).
</description>
@@ -83,6 +84,7 @@
<description>
Called during the processing step of the main loop. Processing happens at every frame and as fast as possible, so the [param delta] time since the previous frame is not constant. [param delta] is in seconds.
It is only called if processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process].
+ Processing happens in order of [member process_priority], lower priority values are called first. Nodes with the same priority are processed in tree order, or top to bottom as seen in the editor (also known as pre-order traversal).
Corresponds to the [constant NOTIFICATION_PROCESS] notification in [method Object._notification].
[b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not an orphan).
</description>
@@ -1015,10 +1017,10 @@
The node's processing behavior (see [enum ProcessMode]). To check if the node can process in its current mode, use [method can_process].
</member>
<member name="process_physics_priority" type="int" setter="set_physics_process_priority" getter="get_physics_process_priority" default="0">
- Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process] or the internal version.
+ Similar to [member process_priority] but for [constant NOTIFICATION_PHYSICS_PROCESS], [method _physics_process], or [constant NOTIFICATION_INTERNAL_PHYSICS_PROCESS].
</member>
<member name="process_priority" type="int" setter="set_process_priority" getter="get_process_priority" default="0">
- The node's execution order of the process callbacks ([method _process], [method _physics_process], and internal processing). Nodes whose priority value is [i]lower[/i] call their process callbacks first, regardless of tree order.
+ The node's execution order of the process callbacks ([method _process], [constant NOTIFICATION_PROCESS], and [constant NOTIFICATION_INTERNAL_PROCESS]). Nodes whose priority value is [i]lower[/i] call their process callbacks first, regardless of tree order.
</member>
<member name="process_thread_group" type="int" setter="set_process_thread_group" getter="get_process_thread_group" enum="Node.ProcessThreadGroup" default="0">
Set the process thread group for this node (basically, whether it receives [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS], [method _process] or [method _physics_process] (and the internal versions) on the main thread or in a sub-thread.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index d0f0459193..4d41d2e94a 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -10,6 +10,7 @@
[b]Overriding:[/b] Any project setting can be overridden by creating a file named [code]override.cfg[/code] in the project's root directory. This can also be used in exported projects by placing this file in the same directory as the project binary. Overriding will still take the base project settings' [url=$DOCS_URL/tutorials/export/feature_tags.html]feature tags[/url] in account. Therefore, make sure to [i]also[/i] override the setting with the desired feature tags if you want them to override base project settings on all platforms and configurations.
</description>
<tutorials>
+ <link title="Project Settings">$DOCS_URL/tutorials/editor/project_settings.html</link>
<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/2747</link>
<link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/2748</link>
<link title="Operating System Testing Demo">https://godotengine.org/asset-library/asset/2789</link>
@@ -376,6 +377,7 @@
<member name="audio/driver/driver" type="String" setter="" getter="">
Specifies the audio driver to use. This setting is platform-dependent as each platform supports different audio drivers. If left empty, the default audio driver will be used.
The [code]Dummy[/code] audio driver disables all audio playback and recording, which is useful for non-game applications as it reduces CPU usage. It also prevents the engine from appearing as an application playing audio in the OS' audio mixer.
+ To query the value that is being used at run-time (which may be overridden by command-line arguments or headless mode), use [method AudioServer.get_driver_name].
[b]Note:[/b] The driver in use can be overridden at runtime via the [code]--audio-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
</member>
<member name="audio/driver/enable_input" type="bool" setter="" getter="" default="false">
@@ -875,6 +877,10 @@
[b]Note:[/b] Certain window managers can be configured to ignore the non-resizable status of a window. Do not rely on this setting as a guarantee that the window will [i]never[/i] be resizable.
[b]Note:[/b] This setting is ignored on iOS.
</member>
+ <member name="display/window/size/sharp_corners" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], the main window uses sharp corners by default.
+ [b]Note:[/b] This property is implemented only on Windows (11).
+ </member>
<member name="display/window/size/transparent" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables a window manager hint that the main window background [i]can[/i] be transparent. This does not make the background actually transparent. For the background to be transparent, the root viewport must also be made transparent by enabling [member rendering/viewport/transparent_background].
[b]Note:[/b] To use a transparent splash screen, set [member application/boot_splash/bg_color] to [code]Color(0, 0, 0, 0)[/code].
@@ -2376,26 +2382,30 @@
[b]Note:[/b] It is not recommended to use this setting together with [member rendering/2d/snap/snap_2d_transforms_to_pixel], as movement may appear even less smooth. Prefer only enabling that setting instead.
</member>
<member name="rendering/anti_aliasing/quality/msaa_2d" type="int" setter="" getter="" default="0">
- Sets the number of MSAA samples to use for 2D/Canvas rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. This has no effect on shader-induced aliasing or texture aliasing.
+ Sets the number of multisample antialiasing (MSAA) samples to use for 2D/Canvas rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. This has no effect on shader-induced aliasing or texture aliasing.
[b]Note:[/b] MSAA is only supported in the Forward+ and Mobile rendering methods, not Compatibility.
+ [b]Note:[/b] This property is only read when the project starts. To set the number of 2D MSAA samples at runtime, set [member Viewport.msaa_2d] or use [method RenderingServer.viewport_set_msaa_2d].
</member>
<member name="rendering/anti_aliasing/quality/msaa_3d" type="int" setter="" getter="" default="0">
- Sets the number of MSAA samples to use for 3D rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. See also [member rendering/scaling_3d/mode] for supersampling, which provides higher quality but is much more expensive. This has no effect on shader-induced aliasing or texture aliasing.
+ Sets the number of multisample antialiasing (MSAA) samples to use for 3D rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. See also [member rendering/scaling_3d/mode] for supersampling, which provides higher quality but is much more expensive. This has no effect on shader-induced aliasing or texture aliasing.
+ [b]Note:[/b] This property is only read when the project starts. To set the number of 3D MSAA samples at runtime, set [member Viewport.msaa_3d] or use [method RenderingServer.viewport_set_msaa_3d].
</member>
<member name="rendering/anti_aliasing/quality/screen_space_aa" type="int" setter="" getter="" default="0">
Sets the screen-space antialiasing mode for the default screen [Viewport]. Screen-space antialiasing works by selectively blurring edges in a post-process shader. It differs from MSAA which takes multiple coverage samples while rendering objects. Screen-space AA methods are typically faster than MSAA and will smooth out specular aliasing, but tend to make scenes appear blurry. The blurriness is partially counteracted by automatically using a negative mipmap LOD bias (see [member rendering/textures/default_filters/texture_mipmap_bias]).
Another way to combat specular aliasing is to enable [member rendering/anti_aliasing/screen_space_roughness_limiter/enabled].
[b]Note:[/b] Screen-space antialiasing is only supported in the Forward+ and Mobile rendering methods, not Compatibility.
+ [b]Note:[/b] This property is only read when the project starts. To set the screen-space antialiasing mode at runtime, set [member Viewport.screen_space_aa] on the root [Viewport] instead, or use [method RenderingServer.viewport_set_screen_space_aa].
</member>
<member name="rendering/anti_aliasing/quality/use_debanding" type="bool" setter="" getter="" default="false">
If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible in 3D. 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS].
In some cases, debanding may introduce a slightly noticeable dithering pattern. It's recommended to enable debanding only when actually needed since the dithering pattern will make lossless-compressed screenshots larger.
- [b]Note:[/b] This property is only read when the project starts. To set debanding at run-time, set [member Viewport.use_debanding] on the root [Viewport] instead.
+ [b]Note:[/b] This property is only read when the project starts. To set debanding at runtime, set [member Viewport.use_debanding] on the root [Viewport] instead, or use [method RenderingServer.viewport_set_use_debanding].
</member>
<member name="rendering/anti_aliasing/quality/use_taa" type="bool" setter="" getter="" default="false">
- Enables Temporal Anti-Aliasing for the default screen [Viewport]. TAA works by jittering the camera and accumulating the images of the last rendered frames, motion vector rendering is used to account for camera and object motion. Enabling TAA can make the image blurrier, which is partially counteracted by automatically using a negative mipmap LOD bias (see [member rendering/textures/default_filters/texture_mipmap_bias]).
+ Enables temporal antialiasing for the default screen [Viewport]. TAA works by jittering the camera and accumulating the images of the last rendered frames, motion vector rendering is used to account for camera and object motion. Enabling TAA can make the image blurrier, which is partially counteracted by automatically using a negative mipmap LOD bias (see [member rendering/textures/default_filters/texture_mipmap_bias]).
[b]Note:[/b] The implementation is not complete yet. Some visual instances such as particles and skinned meshes may show ghosting artifacts in motion.
[b]Note:[/b] TAA is only supported in the Forward+ rendering method, not Mobile or Compatibility.
+ [b]Note:[/b] This property is only read when the project starts. To set TAA at runtime, set [member Viewport.use_taa] on the root [Viewport] instead, or use [method RenderingServer.viewport_set_use_taa].
</member>
<member name="rendering/anti_aliasing/screen_space_roughness_limiter/amount" type="float" setter="" getter="" default="0.25">
[b]Note:[/b] This property is only read when the project starts. To control the screen-space roughness limiter at runtime, call [method RenderingServer.screen_space_roughness_limiter_set_active] instead.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index b73315219b..16a554eded 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2137,6 +2137,14 @@
If [code]true[/code], light will cast shadows. Equivalent to [member Light3D.shadow_enabled].
</description>
</method>
+ <method name="light_set_shadow_caster_mask">
+ <return type="void" />
+ <param index="0" name="light" type="RID" />
+ <param index="1" name="mask" type="int" />
+ <description>
+ Sets the shadow caster mask for this 3D light. Shadows will only be cast using objects in the selected layers. Equivalent to [member Light3D.shadow_caster_mask].
+ </description>
+ </method>
<method name="lightmap_create">
<return type="RID" />
<description>
@@ -3917,7 +3925,7 @@
<param index="0" name="viewport" type="RID" />
<param index="1" name="msaa" type="int" enum="RenderingServer.ViewportMSAA" />
<description>
- Sets the multisample anti-aliasing mode for 2D/Canvas on the specified [param viewport] RID. See [enum ViewportMSAA] for options.
+ Sets the multisample antialiasing mode for 2D/Canvas on the specified [param viewport] RID. See [enum ViewportMSAA] for options. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/msaa_2d] or [member Viewport.msaa_2d].
</description>
</method>
<method name="viewport_set_msaa_3d">
@@ -3925,7 +3933,7 @@
<param index="0" name="viewport" type="RID" />
<param index="1" name="msaa" type="int" enum="RenderingServer.ViewportMSAA" />
<description>
- Sets the multisample anti-aliasing mode for 3D on the specified [param viewport] RID. See [enum ViewportMSAA] for options.
+ Sets the multisample antialiasing mode for 3D on the specified [param viewport] RID. See [enum ViewportMSAA] for options. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/msaa_3d] or [member Viewport.msaa_3d].
</description>
</method>
<method name="viewport_set_occlusion_culling_build_quality">
@@ -4007,7 +4015,7 @@
<param index="0" name="viewport" type="RID" />
<param index="1" name="mode" type="int" enum="RenderingServer.ViewportScreenSpaceAA" />
<description>
- Sets the viewport's screen-space antialiasing mode.
+ Sets the viewport's screen-space antialiasing mode. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/screen_space_aa] or [member Viewport.screen_space_aa].
</description>
</method>
<method name="viewport_set_sdf_oversize_and_scale">
@@ -4074,7 +4082,7 @@
<param index="0" name="viewport" type="RID" />
<param index="1" name="enable" type="bool" />
<description>
- If [code]true[/code], enables debanding on the specified viewport. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding].
+ If [code]true[/code], enables debanding on the specified viewport. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding] or [member Viewport.use_debanding].
</description>
</method>
<method name="viewport_set_use_hdr_2d">
@@ -4099,7 +4107,7 @@
<param index="0" name="viewport" type="RID" />
<param index="1" name="enable" type="bool" />
<description>
- If [code]true[/code], use Temporal Anti-Aliasing. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/use_taa].
+ If [code]true[/code], use temporal antialiasing. Equivalent to [member ProjectSettings.rendering/anti_aliasing/quality/use_taa] or [member Viewport.use_taa].
</description>
</method>
<method name="viewport_set_use_xr">
diff --git a/doc/classes/ResourceImporterOBJ.xml b/doc/classes/ResourceImporterOBJ.xml
index a63dddb0e8..084638f62b 100644
--- a/doc/classes/ResourceImporterOBJ.xml
+++ b/doc/classes/ResourceImporterOBJ.xml
@@ -14,6 +14,19 @@
<member name="force_disable_mesh_compression" type="bool" setter="" getter="" default="false">
If [code]true[/code], mesh compression will not be used. Consider enabling if you notice blocky artifacts in your mesh normals or UVs, or if you have meshes that are larger than a few thousand meters in each direction.
</member>
+ <member name="generate_lightmap_uv2" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], generates UV2 on import for [LightmapGI] baking.
+ </member>
+ <member name="generate_lightmap_uv2_texel_size" type="float" setter="" getter="" default="0.2">
+ Controls the size of each texel on the baked lightmap. A smaller value results in more precise lightmaps, at the cost of larger lightmap sizes and longer bake times.
+ [b]Note:[/b] Only effective if [member generate_lightmap_uv2] is [code]true[/code].
+ </member>
+ <member name="generate_lods" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], generates lower detail variants of the mesh which will be displayed in the distance to improve rendering performance. Not all meshes benefit from LOD, especially if they are never rendered from far away. Disabling this can reduce output file size and speed up importing. See [url=$DOCS_URL/tutorials/3d/mesh_lod.html#doc-mesh-lod]Mesh level of detail (LOD)[/url] for more information.
+ </member>
+ <member name="generate_shadow_mesh" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], enables the generation of shadow meshes on import. This optimizes shadow rendering without reducing quality by welding vertices together when possible. This in turn reduces the memory bandwidth required to render shadows. Shadow mesh generation currently doesn't support using a lower detail level than the source mesh (but shadow rendering will make use of LODs when relevant).
+ </member>
<member name="generate_tangents" type="bool" setter="" getter="" default="true">
If [code]true[/code], generate vertex tangents using [url=http://www.mikktspace.com/]Mikktspace[/url] if the source mesh doesn't have tangent data. When possible, it's recommended to let the 3D modeling software generate tangents on export instead on relying on this option. Tangents are required for correct display of normal and height maps, along with any material/shader features that require tangents.
If you don't need material features that require tangents, disabling this can reduce output file size and speed up importing if the source 3D file doesn't contain tangents.
diff --git a/doc/classes/Shader.xml b/doc/classes/Shader.xml
index 68176dea14..1e7f9e5ee5 100644
--- a/doc/classes/Shader.xml
+++ b/doc/classes/Shader.xml
@@ -35,6 +35,12 @@
If argument [param get_groups] is true, parameter grouping hints will be provided.
</description>
</method>
+ <method name="inspect_native_shader_code">
+ <return type="void" />
+ <description>
+ Only available when running in the editor. Opens a popup that visualizes the generated shader code, including all variants and internal shader code. See also [method Material.inspect_native_shader_code].
+ </description>
+ </method>
<method name="set_default_texture_parameter">
<return type="void" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 40f08dafe6..588d0c73f9 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -6,6 +6,7 @@
<description>
This is the built-in string Variant type (and the one used by GDScript). Strings may contain any number of Unicode characters, and expose methods useful for manipulating and generating strings. Strings are reference-counted and use a copy-on-write approach (every modification to a string returns a new [String]), so passing them around is cheap in resources.
Some string methods have corresponding variations. Variations suffixed with [code]n[/code] ([method countn], [method findn], [method replacen], etc.) are [b]case-insensitive[/b] (they make no distinction between uppercase and lowercase letters). Method variations prefixed with [code]r[/code] ([method rfind], [method rsplit], etc.) are reversed, and start from the end of the string, instead of the beginning.
+ To convert any Variant to or from a string, see [method @GlobalScope.str], [method @GlobalScope.str_to_var], and [method @GlobalScope.var_to_str].
[b]Note:[/b] In a boolean context, a string will evaluate to [code]false[/code] if it is empty ([code]""[/code]). Otherwise, a string will always evaluate to [code]true[/code].
</description>
<tutorials>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index 4158fbe710..756716433e 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -251,14 +251,14 @@
</member>
<member name="y" type="Vector2" setter="" getter="" default="Vector2(0, 1)">
The transform basis's Y axis, and the column [code]1[/code] of the matrix. Combined with [member x], this represents the transform's rotation, scale, and skew.
- On the identity transform, this vector points up ([constant Vector2.UP]).
+ On the identity transform, this vector points down ([constant Vector2.DOWN]).
</member>
</members>
<constants>
<constant name="IDENTITY" value="Transform2D(1, 0, 0, 1, 0, 0)">
The identity [Transform2D]. A transform with no translation, no rotation, and its scale being [code]1[/code]. When multiplied by another [Variant] such as [Rect2] or another [Transform2D], no transformation occurs. This means that:
- The [member x] points right ([constant Vector2.RIGHT]);
- - The [member y] points up ([constant Vector2.UP]).
+ - The [member y] points down ([constant Vector2.DOWN]).
[codeblock]
var transform = Transform2D.IDENTITY
print("| X | Y | Origin")
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index b0cb25fafd..9510d237da 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -509,6 +509,12 @@
<theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]).
</theme_item>
+ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Text [Color] used when the item is hovered.
+ </theme_item>
+ <theme_item name="font_hovered_dimmed_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
+ Text [Color] used when the item is hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)">
The tint of text outline of the item.
</theme_item>
@@ -645,6 +651,9 @@
<theme_item name="updown" data_type="icon" type="Texture2D">
The updown arrow icon to display for the [constant TreeItem.CELL_MODE_RANGE] mode cell.
</theme_item>
+ <theme_item name="button_hover" data_type="style" type="StyleBox">
+ [StyleBox] used when a button in the tree is hovered.
+ </theme_item>
<theme_item name="button_pressed" data_type="style" type="StyleBox">
[StyleBox] used when a button in the tree is pressed.
</theme_item>
@@ -666,6 +675,12 @@
<theme_item name="focus" data_type="style" type="StyleBox">
The focused style for the [Tree], drawn on top of everything.
</theme_item>
+ <theme_item name="hovered" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered.
+ </theme_item>
+ <theme_item name="hovered_dimmed" data_type="style" type="StyleBox">
+ [StyleBox] for the item being hovered, while a button of the same item is hovered as the same time.
+ </theme_item>
<theme_item name="panel" data_type="style" type="StyleBox">
The background style for the [Tree].
</theme_item>
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index 95a1d0f77e..333e61d03f 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -312,10 +312,12 @@
[b]Note:[/b] [member mesh_lod_threshold] does not affect [GeometryInstance3D] visibility ranges (also known as "manual" LOD or hierarchical LOD).
</member>
<member name="msaa_2d" type="int" setter="set_msaa_2d" getter="get_msaa_2d" enum="Viewport.MSAA" default="0">
- The multisample anti-aliasing mode for 2D/Canvas rendering. A higher number results in smoother edges at the cost of significantly worse performance. A value of 2 or 4 is best unless targeting very high-end systems. This has no effect on shader-induced aliasing or texture aliasing.
+ The multisample antialiasing mode for 2D/Canvas rendering. A higher number results in smoother edges at the cost of significantly worse performance. A value of [constant Viewport.MSAA_2X] or [constant Viewport.MSAA_4X] is best unless targeting very high-end systems. This has no effect on shader-induced aliasing or texture aliasing.
+ See also [member ProjectSettings.rendering/anti_aliasing/quality/msaa_2d] and [method RenderingServer.viewport_set_msaa_2d].
</member>
<member name="msaa_3d" type="int" setter="set_msaa_3d" getter="get_msaa_3d" enum="Viewport.MSAA" default="0">
- The multisample anti-aliasing mode for 3D rendering. A higher number results in smoother edges at the cost of significantly worse performance. A value of 2 or 4 is best unless targeting very high-end systems. See also bilinear scaling 3d [member scaling_3d_mode] for supersampling, which provides higher quality but is much more expensive. This has no effect on shader-induced aliasing or texture aliasing.
+ The multisample antialiasing mode for 3D rendering. A higher number results in smoother edges at the cost of significantly worse performance. A value of [constant Viewport.MSAA_2X] or [constant Viewport.MSAA_4X] is best unless targeting very high-end systems. See also bilinear scaling 3d [member scaling_3d_mode] for supersampling, which provides higher quality but is much more expensive. This has no effect on shader-induced aliasing or texture aliasing.
+ See also [member ProjectSettings.rendering/anti_aliasing/quality/msaa_3d] and [method RenderingServer.viewport_set_msaa_3d].
</member>
<member name="own_world_3d" type="bool" setter="set_use_own_world_3d" getter="is_using_own_world_3d" default="false">
If [code]true[/code], the viewport will use a unique copy of the [World3D] defined in [member world_3d].
@@ -365,6 +367,7 @@
</member>
<member name="screen_space_aa" type="int" setter="set_screen_space_aa" getter="get_screen_space_aa" enum="Viewport.ScreenSpaceAA" default="0">
Sets the screen-space antialiasing method used. Screen-space antialiasing works by selectively blurring edges in a post-process shader. It differs from MSAA which takes multiple coverage samples while rendering objects. Screen-space AA methods are typically faster than MSAA and will smooth out specular aliasing, but tend to make scenes appear blurry.
+ See also [member ProjectSettings.rendering/anti_aliasing/quality/screen_space_aa] and [method RenderingServer.viewport_set_screen_space_aa].
</member>
<member name="sdf_oversize" type="int" setter="set_sdf_oversize" getter="get_sdf_oversize" enum="Viewport.SDFOversize" default="1">
Controls how much of the original viewport's size should be covered by the 2D signed distance field. This SDF can be sampled in [CanvasItem] shaders and is also used for [GPUParticles2D] collision. Higher values allow portions of occluders located outside the viewport to still be taken into account in the generated signed distance field, at the cost of performance. If you notice particles falling through [LightOccluder2D]s as the occluders leave the viewport, increase this setting.
@@ -389,8 +392,9 @@
If [code]true[/code], the viewport should render its background as transparent.
</member>
<member name="use_debanding" type="bool" setter="set_use_debanding" getter="is_using_debanding" default="false">
- If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible in 3D. 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS]. See also [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding].
+ If [code]true[/code], uses a fast post-processing filter to make banding significantly less visible in 3D. 2D rendering is [i]not[/i] affected by debanding unless the [member Environment.background_mode] is [constant Environment.BG_CANVAS].
In some cases, debanding may introduce a slightly noticeable dithering pattern. It's recommended to enable debanding only when actually needed since the dithering pattern will make lossless-compressed screenshots larger.
+ See also [member ProjectSettings.rendering/anti_aliasing/quality/use_debanding] and [method RenderingServer.viewport_set_use_debanding].
</member>
<member name="use_hdr_2d" type="bool" setter="set_use_hdr_2d" getter="is_using_hdr_2d" default="false">
If [code]true[/code], 2D rendering will use an high dynamic range (HDR) format framebuffer matching the bit depth of the 3D framebuffer. When using the Forward+ renderer this will be an [code]RGBA16[/code] framebuffer, while when using the Mobile renderer it will be an [code]RGB10_A2[/code] framebuffer. Additionally, 2D rendering will take place in linear color space and will be converted to sRGB space immediately before blitting to the screen (if the Viewport is attached to the screen). Practically speaking, this means that the end result of the Viewport will not be clamped into the [code]0-1[/code] range and can be used in 3D rendering without color space adjustments. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow) as well as substantially improves the appearance of effects requiring highly detailed gradients.
@@ -402,8 +406,9 @@
[b]Note:[/b] Due to memory constraints, occlusion culling is not supported by default in Web export templates. It can be enabled by compiling custom Web export templates with [code]module_raycast_enabled=yes[/code].
</member>
<member name="use_taa" type="bool" setter="set_use_taa" getter="is_using_taa" default="false">
- Enables Temporal Anti-Aliasing for this viewport. TAA works by jittering the camera and accumulating the images of the last rendered frames, motion vector rendering is used to account for camera and object motion.
+ Enables temporal antialiasing for this viewport. TAA works by jittering the camera and accumulating the images of the last rendered frames, motion vector rendering is used to account for camera and object motion.
[b]Note:[/b] The implementation is not complete yet, some visual instances such as particles and skinned meshes may show artifacts.
+ See also [member ProjectSettings.rendering/anti_aliasing/quality/use_taa] and [method RenderingServer.viewport_set_use_taa].
</member>
<member name="use_xr" type="bool" setter="set_use_xr" getter="is_using_xr" default="false">
If [code]true[/code], the viewport will use the primary XR interface to render XR output. When applicable this can result in a stereoscopic image and the resulting render being output to a headset.
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index ca155881c8..02110f0162 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -659,6 +659,11 @@
If [member ProjectSettings.display/window/subwindows/embed_subwindows] is [code]false[/code], the position is in absolute screen coordinates. This typically applies to editor plugins. If the setting is [code]true[/code], the window's position is in the coordinates of its parent [Viewport].
[b]Note:[/b] This property only works if [member initial_position] is set to [constant WINDOW_INITIAL_POSITION_ABSOLUTE].
</member>
+ <member name="sharp_corners" type="bool" setter="set_flag" getter="get_flag" default="false">
+ If [code]true[/code], the [Window] will override the OS window style to display sharp corners.
+ [b]Note:[/b] This property is implemented only on Windows (11).
+ [b]Note:[/b] This property only works with native windows.
+ </member>
<member name="size" type="Vector2i" setter="set_size" getter="get_size" default="Vector2i(100, 100)">
The window's size in pixels.
</member>
@@ -842,7 +847,12 @@
All mouse events are passed to the underlying window of the same application.
[b]Note:[/b] This flag has no effect in embedded windows.
</constant>
- <constant name="FLAG_MAX" value="8" enum="Flags">
+ <constant name="FLAG_SHARP_CORNERS" value="8" enum="Flags">
+ Window style is overridden, forcing sharp corners.
+ [b]Note:[/b] This flag has no effect in embedded windows.
+ [b]Note:[/b] This flag is implemented only on Windows (11).
+ </constant>
+ <constant name="FLAG_MAX" value="9" enum="Flags">
Max value of the [enum Flags].
</constant>
<constant name="CONTENT_SCALE_MODE_DISABLED" value="0" enum="ContentScaleMode">
@@ -878,7 +888,7 @@
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
Automatic layout direction, determined from the parent window layout direction.
</constant>
- <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection">
+ <constant name="LAYOUT_DIRECTION_APPLICATION_LOCALE" value="1" enum="LayoutDirection">
Automatic layout direction, determined from the current locale.
</constant>
<constant name="LAYOUT_DIRECTION_LTR" value="2" enum="LayoutDirection">
@@ -887,6 +897,14 @@
<constant name="LAYOUT_DIRECTION_RTL" value="3" enum="LayoutDirection">
Right-to-left layout direction.
</constant>
+ <constant name="LAYOUT_DIRECTION_SYSTEM_LOCALE" value="4" enum="LayoutDirection">
+ Automatic layout direction, determined from the system locale.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_MAX" value="5" enum="LayoutDirection">
+ Represents the size of the [enum LayoutDirection] enum.
+ </constant>
+ <constant name="LAYOUT_DIRECTION_LOCALE" value="1" enum="LayoutDirection" deprecated="Use [constant LAYOUT_DIRECTION_APPLICATION_LOCALE] instead.">
+ </constant>
<constant name="WINDOW_INITIAL_POSITION_ABSOLUTE" value="0" enum="WindowInitialPosition">
Initial window position is determined by [member position].
</constant>
diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp
index fd0adb1fd1..433bbfb3f5 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.cpp
+++ b/drivers/coreaudio/audio_driver_coreaudio.cpp
@@ -250,7 +250,7 @@ OSStatus AudioDriverCoreAudio::input_callback(void *inRefCon,
}
void AudioDriverCoreAudio::start() {
- if (!active) {
+ if (!active && audio_unit != nullptr) {
OSStatus result = AudioOutputUnitStart(audio_unit);
if (result != noErr) {
ERR_PRINT("AudioOutputUnitStart failed, code: " + itos(result));
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index c367d9f1dd..7c546333c4 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -2003,6 +2003,8 @@ static D3D12_BARRIER_LAYOUT _rd_texture_layout_to_d3d12_barrier_layout(RDD::Text
switch (p_texture_layout) {
case RDD::TEXTURE_LAYOUT_UNDEFINED:
return D3D12_BARRIER_LAYOUT_UNDEFINED;
+ case RDD::TEXTURE_LAYOUT_GENERAL:
+ return D3D12_BARRIER_LAYOUT_COMMON;
case RDD::TEXTURE_LAYOUT_STORAGE_OPTIMAL:
return D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS;
case RDD::TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
@@ -6173,6 +6175,8 @@ uint64_t RenderingDeviceDriverD3D12::api_trait_get(ApiTrait p_trait) {
return false;
case API_TRAIT_CLEARS_WITH_COPY_ENGINE:
return false;
+ case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
+ return true;
default:
return RenderingDeviceDriver::api_trait_get(p_trait);
}
diff --git a/drivers/egl/egl_manager.cpp b/drivers/egl/egl_manager.cpp
index 603dfadd4b..8ca0aa5943 100644
--- a/drivers/egl/egl_manager.cpp
+++ b/drivers/egl/egl_manager.cpp
@@ -414,6 +414,30 @@ EGLContext EGLManager::get_context(DisplayServer::WindowID p_window_id) {
return display.egl_context;
}
+EGLDisplay EGLManager::get_display(DisplayServer::WindowID p_window_id) {
+ GLWindow &glwindow = windows[p_window_id];
+
+ if (!glwindow.initialized) {
+ return EGL_NO_CONTEXT;
+ }
+
+ GLDisplay &display = displays[glwindow.gldisplay_id];
+
+ return display.egl_display;
+}
+
+EGLConfig EGLManager::get_config(DisplayServer::WindowID p_window_id) {
+ GLWindow &glwindow = windows[p_window_id];
+
+ if (!glwindow.initialized) {
+ return nullptr;
+ }
+
+ GLDisplay &display = displays[glwindow.gldisplay_id];
+
+ return display.egl_config;
+}
+
Error EGLManager::initialize(void *p_native_display) {
#if defined(GLAD_ENABLED) && !defined(EGL_STATIC)
// Loading EGL with a new display gets us just the bare minimum API. We'll then
diff --git a/drivers/egl/egl_manager.h b/drivers/egl/egl_manager.h
index f1b3dc99b7..2e1ae6ec53 100644
--- a/drivers/egl/egl_manager.h
+++ b/drivers/egl/egl_manager.h
@@ -113,6 +113,8 @@ public:
bool is_using_vsync() const;
EGLContext get_context(DisplayServer::WindowID p_window_id);
+ EGLDisplay get_display(DisplayServer::WindowID p_window_id);
+ EGLConfig get_config(DisplayServer::WindowID p_window_id);
Error initialize(void *p_native_display = nullptr);
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index 6e508c6ebf..843b6eac05 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "storage/texture_storage.h"
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index 9b976c2206..9b81430d45 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -213,6 +213,23 @@ void LightStorage::light_set_cull_mask(RID p_light, uint32_t p_mask) {
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
}
+void LightStorage::light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(light);
+
+ light->shadow_caster_mask = p_caster_mask;
+
+ light->version++;
+ light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
+}
+
+uint32_t LightStorage::light_get_shadow_caster_mask(RID p_light) const {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL_V(light, 0);
+
+ return light->shadow_caster_mask;
+}
+
void LightStorage::light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_NULL(light);
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index ed00dd235f..5391e607c3 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -59,6 +59,7 @@ struct Light {
RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -327,6 +328,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {}
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 684f179492..04cbf7f2cd 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1237,6 +1237,8 @@ MaterialStorage::MaterialStorage() {
actions.renames["PI"] = _MKSTR(Math_PI);
actions.renames["TAU"] = _MKSTR(Math_TAU);
actions.renames["E"] = _MKSTR(Math_E);
+ actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["VIEWPORT_SIZE"] = "scene_data.viewport_size";
actions.renames["FRAGCOORD"] = "gl_FragCoord";
@@ -1276,8 +1278,6 @@ MaterialStorage::MaterialStorage() {
actions.renames["CUSTOM1"] = "custom1_attrib";
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
- actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
- actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "model_matrix[3].xyz";
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index d7b4d6911d..5f49a84fe8 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -230,6 +230,32 @@ TextureStorage::TextureStorage() {
sdf_shader.shader_version = sdf_shader.shader.version_create();
}
+ // Initialize texture placeholder data for the `texture_*_placeholder_initialize()` methods.
+
+ constexpr int placeholder_size = 4;
+ texture_2d_placeholder = Image::create_empty(placeholder_size, placeholder_size, false, Image::FORMAT_RGBA8);
+ // Draw a magenta/black checkerboard pattern.
+ for (int i = 0; i < placeholder_size * placeholder_size; i++) {
+ const int x = i % placeholder_size;
+ const int y = i / placeholder_size;
+ texture_2d_placeholder->set_pixel(x, y, (x + y) % 2 == 0 ? Color(1, 0, 1) : Color(0, 0, 0));
+ }
+
+ texture_2d_array_placeholder.push_back(texture_2d_placeholder);
+
+ for (int i = 0; i < 6; i++) {
+ cubemap_placeholder.push_back(texture_2d_placeholder);
+ }
+
+ Ref<Image> texture_2d_placeholder_rotated;
+ texture_2d_placeholder_rotated.instantiate();
+ texture_2d_placeholder_rotated->copy_from(texture_2d_placeholder);
+ texture_2d_placeholder_rotated->rotate_90(CLOCKWISE);
+ for (int i = 0; i < 4; i++) {
+ // Alternate checkerboard pattern on odd layers (by using a copy that is rotated 90 degrees).
+ texture_3d_placeholder.push_back(i % 2 == 0 ? texture_2d_placeholder : texture_2d_placeholder_rotated);
+ }
+
#ifdef GL_API_ENABLED
if (RasterizerGLES3::is_gles_over_gl()) {
glEnable(GL_PROGRAM_POINT_SIZE);
@@ -1014,46 +1040,19 @@ void TextureStorage::texture_proxy_update(RID p_texture, RID p_proxy_to) {
}
void TextureStorage::texture_2d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- texture_2d_initialize(p_texture, image);
+ texture_2d_initialize(p_texture, texture_2d_placeholder);
}
-void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
+void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RS::TextureLayeredType p_layered_type) {
if (p_layered_type == RS::TEXTURE_LAYERED_2D_ARRAY) {
- images.push_back(image);
+ texture_2d_layered_initialize(p_texture, texture_2d_array_placeholder, p_layered_type);
} else {
- //cube
- for (int i = 0; i < 6; i++) {
- images.push_back(image);
- }
+ texture_2d_layered_initialize(p_texture, cubemap_placeholder, p_layered_type);
}
-
- texture_2d_layered_initialize(p_texture, images, p_layered_type);
}
void TextureStorage::texture_3d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
- //cube
- for (int i = 0; i < 4; i++) {
- images.push_back(image);
- }
-
- texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, images);
+ texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, texture_3d_placeholder);
}
Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 3786c8c690..d85d10e235 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -36,6 +36,7 @@
#include "platform_gl.h"
#include "config.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/templates/rid_owner.h"
#include "servers/rendering/renderer_compositor.h"
@@ -521,6 +522,11 @@ public:
virtual void texture_external_update(RID p_texture, int p_width, int p_height, uint64_t p_external_buffer) override;
virtual void texture_proxy_update(RID p_proxy, RID p_base) override;
+ Ref<Image> texture_2d_placeholder;
+ Vector<Ref<Image>> texture_2d_array_placeholder;
+ Vector<Ref<Image>> cubemap_placeholder;
+ Vector<Ref<Image>> texture_3d_placeholder;
+
//these two APIs can be used together or in combination with the others.
virtual void texture_2d_placeholder_initialize(RID p_texture) override;
virtual void texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) override;
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index 030b353ee8..38d5b53ffa 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -96,6 +96,22 @@ _FORCE_INLINE_ ShaderStageUsage &operator|=(ShaderStageUsage &p_a, int p_b) {
return p_a;
}
+enum StageResourceUsage : uint32_t {
+ VertexRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_VERTEX * 2),
+ VertexWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_VERTEX * 2),
+ FragmentRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_FRAGMENT * 2),
+ FragmentWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_FRAGMENT * 2),
+ TesselationControlRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationControlWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
+ TesselationEvaluationRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ TesselationEvaluationWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
+ ComputeRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_COMPUTE * 2),
+ ComputeWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_COMPUTE * 2),
+};
+
+typedef LocalVector<__unsafe_unretained id<MTLResource>> ResourceVector;
+typedef HashMap<StageResourceUsage, ResourceVector> ResourceUsageMap;
+
enum class MDCommandBufferStateType {
None,
Render,
@@ -230,6 +246,7 @@ public:
uint32_t index_offset = 0;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> vertex_offsets;
+ ResourceUsageMap resource_usage;
// clang-format off
enum DirtyFlag: uint8_t {
DIRTY_NONE = 0b0000'0000,
@@ -271,8 +288,14 @@ public:
blend_constants.reset();
vertex_buffers.clear();
vertex_offsets.clear();
+ // Keep the keys, as they are likely to be used again.
+ for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
+ kv.value.clear();
+ }
}
+ void end_encoding();
+
_FORCE_INLINE_ void mark_viewport_dirty() {
if (viewports.is_empty()) {
return;
@@ -356,13 +379,20 @@ public:
} render;
// State specific for a compute pass.
- struct {
+ struct ComputeState {
MDComputePipeline *pipeline = nullptr;
id<MTLComputeCommandEncoder> encoder = nil;
+ ResourceUsageMap resource_usage;
_FORCE_INLINE_ void reset() {
pipeline = nil;
encoder = nil;
+ // Keep the keys, as they are likely to be used again.
+ for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
+ kv.value.clear();
+ }
}
+
+ void end_encoding();
} compute;
// State specific to a blit pass.
@@ -632,19 +662,6 @@ public:
MDRenderShader(CharString p_name, Vector<UniformSet> p_sets, MDLibrary *p_vert, MDLibrary *p_frag);
};
-enum StageResourceUsage : uint32_t {
- VertexRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_VERTEX * 2),
- VertexWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_VERTEX * 2),
- FragmentRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_FRAGMENT * 2),
- FragmentWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_FRAGMENT * 2),
- TesselationControlRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
- TesselationControlWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_CONTROL * 2),
- TesselationEvaluationRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
- TesselationEvaluationWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_TESSELATION_EVALUATION * 2),
- ComputeRead = (MTLResourceUsageRead << RDD::SHADER_STAGE_COMPUTE * 2),
- ComputeWrite = (MTLResourceUsageWrite << RDD::SHADER_STAGE_COMPUTE * 2),
-};
-
_FORCE_INLINE_ StageResourceUsage &operator|=(StageResourceUsage &p_a, uint32_t p_b) {
p_a = StageResourceUsage(uint32_t(p_a) | p_b);
return p_a;
@@ -667,7 +684,13 @@ struct HashMapComparatorDefault<RDD::ShaderID> {
struct BoundUniformSet {
id<MTLBuffer> buffer;
- HashMap<id<MTLResource>, StageResourceUsage> bound_resources;
+ ResourceUsageMap usage_to_resources;
+
+ /// Perform a 2-way merge each key of `ResourceVector` resources from this set into the
+ /// destination set.
+ ///
+ /// Assumes the vectors of resources are sorted.
+ void merge_into(ResourceUsageMap &p_dst) const;
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDUniformSet {
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index 596728212a..c3906af159 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -58,7 +58,7 @@
void MDCommandBuffer::begin() {
DEV_ASSERT(commandBuffer == nil);
- commandBuffer = queue.commandBuffer;
+ commandBuffer = queue.commandBufferWithUnretainedReferences;
}
void MDCommandBuffer::end() {
@@ -390,6 +390,38 @@ void MDCommandBuffer::render_set_blend_constants(const Color &p_constants) {
}
}
+void BoundUniformSet::merge_into(ResourceUsageMap &p_dst) const {
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : usage_to_resources) {
+ ResourceVector *resources = p_dst.getptr(keyval.key);
+ if (resources == nullptr) {
+ resources = &p_dst.insert(keyval.key, ResourceVector())->value;
+ }
+ // Reserve space for the new resources, assuming they are all added.
+ resources->reserve(resources->size() + keyval.value.size());
+
+ uint32_t i = 0, j = 0;
+ __unsafe_unretained id<MTLResource> *resources_ptr = resources->ptr();
+ const __unsafe_unretained id<MTLResource> *keyval_ptr = keyval.value.ptr();
+ // 2-way merge.
+ while (i < resources->size() && j < keyval.value.size()) {
+ if (resources_ptr[i] < keyval_ptr[j]) {
+ i++;
+ } else if (resources_ptr[i] > keyval_ptr[j]) {
+ resources->insert(i, keyval_ptr[j]);
+ i++;
+ j++;
+ } else {
+ i++;
+ j++;
+ }
+ }
+ // Append the remaining resources.
+ for (; j < keyval.value.size(); j++) {
+ resources->push_back(keyval_ptr[j]);
+ }
+ }
+}
+
void MDCommandBuffer::_render_bind_uniform_sets() {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
if (!render.dirty.has_flag(RenderState::DIRTY_UNIFORMS)) {
@@ -408,7 +440,7 @@ void MDCommandBuffer::_render_bind_uniform_sets() {
// Find the index of the next set bit.
int index = __builtin_ctzll(set_uniforms);
// Clear the set bit.
- set_uniforms &= ~(1ULL << index);
+ set_uniforms &= (set_uniforms - 1);
MDUniformSet *set = render.uniform_sets[index];
if (set == nullptr || set->index >= (uint32_t)shader->sets.size()) {
continue;
@@ -416,17 +448,7 @@ void MDCommandBuffer::_render_bind_uniform_sets() {
UniformSet const &set_info = shader->sets[set->index];
BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
-
- for (KeyValue<id<MTLResource>, StageResourceUsage> const &keyval : bus.bound_resources) {
- MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_VERTEX);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage stages:MTLRenderStageVertex];
- }
- usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_FRAGMENT);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage stages:MTLRenderStageFragment];
- }
- }
+ bus.merge_into(render.resource_usage);
// Set the buffer for the vertex stage.
{
@@ -545,8 +567,7 @@ void MDCommandBuffer::_end_render_pass() {
// see: https://github.com/KhronosGroup/MoltenVK/blob/d20d13fe2735adb845636a81522df1b9d89c0fba/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm#L407
}
- [render.encoder endEncoding];
- render.encoder = nil;
+ render.end_encoding();
}
void MDCommandBuffer::_render_clear_render_area() {
@@ -792,10 +813,59 @@ void MDCommandBuffer::render_draw_indirect_count(RDD::BufferID p_indirect_buffer
ERR_FAIL_MSG("not implemented");
}
+void MDCommandBuffer::RenderState::end_encoding() {
+ if (encoder == nil) {
+ return;
+ }
+
+ // Bind all resources.
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : resource_usage) {
+ if (keyval.value.is_empty()) {
+ continue;
+ }
+
+ MTLResourceUsage vert_usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_VERTEX);
+ MTLResourceUsage frag_usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_FRAGMENT);
+ if (vert_usage == frag_usage) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:vert_usage stages:MTLRenderStageVertex | MTLRenderStageFragment];
+ } else {
+ if (vert_usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:vert_usage stages:MTLRenderStageVertex];
+ }
+ if (frag_usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:frag_usage stages:MTLRenderStageFragment];
+ }
+ }
+ }
+
+ [encoder endEncoding];
+ encoder = nil;
+}
+
+void MDCommandBuffer::ComputeState::end_encoding() {
+ if (encoder == nil) {
+ return;
+ }
+
+ // Bind all resources.
+ for (KeyValue<StageResourceUsage, ResourceVector> const &keyval : resource_usage) {
+ if (keyval.value.is_empty()) {
+ continue;
+ }
+ MTLResourceUsage usage = resource_usage_for_stage(keyval.key, RDD::ShaderStage::SHADER_STAGE_COMPUTE);
+ if (usage != 0) {
+ [encoder useResources:keyval.value.ptr() count:keyval.value.size() usage:usage];
+ }
+ }
+
+ [encoder endEncoding];
+ encoder = nil;
+}
+
void MDCommandBuffer::render_end_pass() {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
- [render.encoder endEncoding];
+ render.end_encoding();
render.reset();
type = MDCommandBufferStateType::None;
}
@@ -813,13 +883,7 @@ void MDCommandBuffer::compute_bind_uniform_set(RDD::UniformSetID p_uniform_set,
MDUniformSet *set = (MDUniformSet *)(p_uniform_set.id);
BoundUniformSet &bus = set->boundUniformSetForShader(shader, device);
-
- for (KeyValue<id<MTLResource>, StageResourceUsage> &keyval : bus.bound_resources) {
- MTLResourceUsage usage = resource_usage_for_stage(keyval.value, RDD::ShaderStage::SHADER_STAGE_COMPUTE);
- if (usage != 0) {
- [enc useResource:keyval.key usage:usage];
- }
- }
+ bus.merge_into(compute.resource_usage);
uint32_t const *offset = set_info.offsets.getptr(RDD::SHADER_STAGE_COMPUTE);
if (offset) {
@@ -848,7 +912,7 @@ void MDCommandBuffer::compute_dispatch_indirect(RDD::BufferID p_indirect_buffer,
void MDCommandBuffer::_end_compute_dispatch() {
DEV_ASSERT(type == MDCommandBufferStateType::Compute);
- [compute.encoder endEncoding];
+ compute.end_encoding();
compute.reset();
type = MDCommandBufferStateType::None;
}
@@ -1052,7 +1116,20 @@ BoundUniformSet &MDUniformSet::boundUniformSetForShader(MDShader *p_shader, id<M
}
}
- BoundUniformSet bs = { .buffer = enc_buffer, .bound_resources = bound_resources };
+ SearchArray<__unsafe_unretained id<MTLResource>> search;
+ ResourceUsageMap usage_to_resources;
+ for (KeyValue<id<MTLResource>, StageResourceUsage> const &keyval : bound_resources) {
+ ResourceVector *resources = usage_to_resources.getptr(keyval.value);
+ if (resources == nullptr) {
+ resources = &usage_to_resources.insert(keyval.value, ResourceVector())->value;
+ }
+ int64_t pos = search.bisect(resources->ptr(), resources->size(), keyval.key, true);
+ if (pos == resources->size() || (*resources)[pos] != keyval.key) {
+ resources->insert(pos, keyval.key);
+ }
+ }
+
+ BoundUniformSet bs = { .buffer = enc_buffer, .usage_to_resources = usage_to_resources };
bound_uniforms.insert(p_shader, bs);
return bound_uniforms.get(p_shader);
}
@@ -1211,8 +1288,7 @@ vertex VaryingsPos vertClear(AttributesPos attributes [[stage_in]], constant Cle
varyings.layer = uint(attributes.a_position.w);
return varyings;
}
-)",
- ClearAttKey::DEPTH_INDEX];
+)", ClearAttKey::DEPTH_INDEX];
return new_func(msl, @"vertClear", nil);
}
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index a4a408356a..4da11ecd21 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -2060,6 +2060,10 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
case BT::Sampler: {
primary.dataType = MTLDataTypeSampler;
+ primary.arrayLength = 1;
+ for (uint32_t const &a : a_type.array) {
+ primary.arrayLength *= a;
+ }
} break;
default: {
@@ -2067,7 +2071,7 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
} break;
}
- // Find array length.
+ // Find array length of image.
if (basetype == BT::Image || basetype == BT::SampledImage) {
primary.arrayLength = 1;
for (uint32_t const &a : a_type.array) {
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 37c6d79c2b..0d908f4ace 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -43,6 +43,10 @@
/**** GENERIC ****/
/*****************/
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+static const uint32_t BREADCRUMB_BUFFER_ENTRIES = 512u;
+#endif
+
static const VkFormat RD_TO_VK_FORMAT[RDD::DATA_FORMAT_MAX] = {
VK_FORMAT_R4G4_UNORM_PACK8,
VK_FORMAT_R4G4B4A4_UNORM_PACK16,
@@ -266,6 +270,7 @@ static const VkFormat RD_TO_VK_FORMAT[RDD::DATA_FORMAT_MAX] = {
static VkImageLayout RD_TO_VK_LAYOUT[RDD::TEXTURE_LAYOUT_MAX] = {
VK_IMAGE_LAYOUT_UNDEFINED, // TEXTURE_LAYOUT_UNDEFINED
+ VK_IMAGE_LAYOUT_GENERAL, // TEXTURE_LAYOUT_GENERAL
VK_IMAGE_LAYOUT_GENERAL, // TEXTURE_LAYOUT_STORAGE_OPTIMAL
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, // TEXTURE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
@@ -1369,7 +1374,10 @@ Error RenderingDeviceDriverVulkan::initialize(uint32_t p_device_index, uint32_t
ERR_FAIL_COND_V(err != OK, err);
max_descriptor_sets_per_pool = GLOBAL_GET("rendering/rendering_device/vulkan/max_descriptors_per_pool");
- breadcrumb_buffer = buffer_create(sizeof(uint32_t), BufferUsageBits::BUFFER_USAGE_TRANSFER_TO_BIT, MemoryAllocationType::MEMORY_ALLOCATION_TYPE_CPU);
+
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ breadcrumb_buffer = buffer_create(2u * sizeof(uint32_t) * BREADCRUMB_BUFFER_ENTRIES, BufferUsageBits::BUFFER_USAGE_TRANSFER_TO_BIT, MemoryAllocationType::MEMORY_ALLOCATION_TYPE_CPU);
+#endif
return OK;
}
@@ -2636,11 +2644,13 @@ bool RenderingDeviceDriverVulkan::command_buffer_begin(CommandBufferID p_cmd_buf
bool RenderingDeviceDriverVulkan::command_buffer_begin_secondary(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, uint32_t p_subpass, FramebufferID p_framebuffer) {
// Reset is implicit (VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT).
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+
VkCommandBufferInheritanceInfo inheritance_info = {};
inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
inheritance_info.renderPass = (VkRenderPass)p_render_pass.id;
inheritance_info.subpass = p_subpass;
- inheritance_info.framebuffer = (VkFramebuffer)p_framebuffer.id;
+ inheritance_info.framebuffer = framebuffer->vk_framebuffer;
VkCommandBufferBeginInfo cmd_buf_begin_info = {};
cmd_buf_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@@ -2948,12 +2958,16 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue,
fb_create_info.height = surface->height;
fb_create_info.layers = 1;
- VkFramebuffer framebuffer;
+ VkFramebuffer vk_framebuffer;
for (uint32_t i = 0; i < image_count; i++) {
fb_create_info.pAttachments = &swap_chain->image_views[i];
- err = vkCreateFramebuffer(vk_device, &fb_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER), &framebuffer);
+ err = vkCreateFramebuffer(vk_device, &fb_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER), &vk_framebuffer);
ERR_FAIL_COND_V(err != VK_SUCCESS, ERR_CANT_CREATE);
+ Framebuffer *framebuffer = memnew(Framebuffer);
+ framebuffer->vk_framebuffer = vk_framebuffer;
+ framebuffer->swap_chain_image = swap_chain->images[i];
+ framebuffer->swap_chain_image_subresource_range = view_create_info.subresourceRange;
swap_chain->framebuffers.push_back(RDD::FramebufferID(framebuffer));
}
@@ -3022,7 +3036,10 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::swap_chain_acquire_framebuffer(C
command_queue->pending_semaphores_for_fence.push_back(semaphore_index);
// Return the corresponding framebuffer to the new current image.
- return swap_chain->framebuffers[swap_chain->image_index];
+ FramebufferID framebuffer_id = swap_chain->framebuffers[swap_chain->image_index];
+ Framebuffer *framebuffer = (Framebuffer *)(framebuffer_id.id);
+ framebuffer->swap_chain_acquired = true;
+ return framebuffer_id;
}
RDD::RenderPassID RenderingDeviceDriverVulkan::swap_chain_get_render_pass(SwapChainID p_swap_chain) {
@@ -3091,11 +3108,15 @@ RDD::FramebufferID RenderingDeviceDriverVulkan::framebuffer_create(RenderPassID
}
#endif
- return FramebufferID(vk_framebuffer);
+ Framebuffer *framebuffer = memnew(Framebuffer);
+ framebuffer->vk_framebuffer = vk_framebuffer;
+ return FramebufferID(framebuffer);
}
void RenderingDeviceDriverVulkan::framebuffer_free(FramebufferID p_framebuffer) {
- vkDestroyFramebuffer(vk_device, (VkFramebuffer)p_framebuffer.id, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER));
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+ vkDestroyFramebuffer(vk_device, framebuffer->vk_framebuffer, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_FRAMEBUFFER));
+ memdelete(framebuffer);
}
/****************/
@@ -4313,10 +4334,25 @@ void RenderingDeviceDriverVulkan::render_pass_free(RenderPassID p_render_pass) {
static_assert(ARRAYS_COMPATIBLE_FIELDWISE(RDD::RenderPassClearValue, VkClearValue));
void RenderingDeviceDriverVulkan::command_begin_render_pass(CommandBufferID p_cmd_buffer, RenderPassID p_render_pass, FramebufferID p_framebuffer, CommandBufferType p_cmd_buffer_type, const Rect2i &p_rect, VectorView<RenderPassClearValue> p_clear_values) {
+ Framebuffer *framebuffer = (Framebuffer *)(p_framebuffer.id);
+ if (framebuffer->swap_chain_acquired) {
+ // Insert a barrier to wait for the acquisition of the framebuffer before the render pass begins.
+ VkImageMemoryBarrier image_barrier = {};
+ image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ image_barrier.image = framebuffer->swap_chain_image;
+ image_barrier.subresourceRange = framebuffer->swap_chain_image_subresource_range;
+ vkCmdPipelineBarrier((VkCommandBuffer)p_cmd_buffer.id, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_barrier);
+ framebuffer->swap_chain_acquired = false;
+ }
+
VkRenderPassBeginInfo render_pass_begin = {};
render_pass_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
render_pass_begin.renderPass = (VkRenderPass)p_render_pass.id;
- render_pass_begin.framebuffer = (VkFramebuffer)p_framebuffer.id;
+ render_pass_begin.framebuffer = framebuffer->vk_framebuffer;
render_pass_begin.renderArea.offset.x = p_rect.position.x;
render_pass_begin.renderArea.offset.y = p_rect.position.y;
@@ -4973,10 +5009,65 @@ void RenderingDeviceDriverVulkan::command_end_label(CommandBufferID p_cmd_buffer
/**** DEBUG *****/
/****************/
void RenderingDeviceDriverVulkan::command_insert_breadcrumb(CommandBufferID p_cmd_buffer, uint32_t p_data) {
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
if (p_data == BreadcrumbMarker::NONE) {
return;
}
- vkCmdFillBuffer((VkCommandBuffer)p_cmd_buffer.id, ((BufferInfo *)breadcrumb_buffer.id)->vk_buffer, 0, sizeof(uint32_t), p_data);
+
+ if (Engine::get_singleton()->is_accurate_breadcrumbs_enabled()) {
+ // Force a full barrier so commands are not executed in parallel.
+ // This will mean that the last breadcrumb to see was actually the
+ // last (group of) command to be executed (hence, the one causing the crash).
+ VkMemoryBarrier memoryBarrier;
+ memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+ memoryBarrier.pNext = nullptr;
+ memoryBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
+ VK_ACCESS_INDEX_READ_BIT |
+ VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
+ VK_ACCESS_UNIFORM_READ_BIT |
+ VK_ACCESS_INPUT_ATTACHMENT_READ_BIT |
+ VK_ACCESS_SHADER_READ_BIT |
+ VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_READ_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT |
+ VK_ACCESS_HOST_READ_BIT |
+ VK_ACCESS_HOST_WRITE_BIT;
+ memoryBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT |
+ VK_ACCESS_INDEX_READ_BIT |
+ VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT |
+ VK_ACCESS_UNIFORM_READ_BIT |
+ VK_ACCESS_INPUT_ATTACHMENT_READ_BIT |
+ VK_ACCESS_SHADER_READ_BIT |
+ VK_ACCESS_SHADER_WRITE_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+ VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
+ VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+ VK_ACCESS_TRANSFER_READ_BIT |
+ VK_ACCESS_TRANSFER_WRITE_BIT |
+ VK_ACCESS_HOST_READ_BIT |
+ VK_ACCESS_HOST_WRITE_BIT;
+
+ vkCmdPipelineBarrier(
+ (VkCommandBuffer)p_cmd_buffer.id,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ 0, 1u, &memoryBarrier, 0u, nullptr, 0u, nullptr);
+ }
+
+ // We write to a circular buffer. If you're getting barrier sync errors here,
+ // increase the value of BREADCRUMB_BUFFER_ENTRIES.
+ vkCmdFillBuffer((VkCommandBuffer)p_cmd_buffer.id, ((BufferInfo *)breadcrumb_buffer.id)->vk_buffer, breadcrumb_offset, sizeof(uint32_t), breadcrumb_id++);
+ vkCmdFillBuffer((VkCommandBuffer)p_cmd_buffer.id, ((BufferInfo *)breadcrumb_buffer.id)->vk_buffer, breadcrumb_offset + sizeof(uint32_t), sizeof(uint32_t), p_data);
+ breadcrumb_offset += sizeof(uint32_t) * 2u;
+ if (breadcrumb_offset >= BREADCRUMB_BUFFER_ENTRIES * sizeof(uint32_t) * 2u) {
+ breadcrumb_offset = 0u;
+ }
+#endif
}
void RenderingDeviceDriverVulkan::on_device_lost() const {
@@ -5058,64 +5149,121 @@ void RenderingDeviceDriverVulkan::on_device_lost() const {
void RenderingDeviceDriverVulkan::print_lost_device_info() {
#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
- void *breadcrumb_ptr;
- vmaFlushAllocation(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, 0, sizeof(uint32_t));
- vmaInvalidateAllocation(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, 0, sizeof(uint32_t));
-
- vmaMapMemory(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, &breadcrumb_ptr);
- uint32_t last_breadcrumb = *(uint32_t *)breadcrumb_ptr;
- vmaUnmapMemory(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle);
- uint32_t phase = last_breadcrumb & uint32_t(~((1 << 16) - 1));
- uint32_t user_data = last_breadcrumb & ((1 << 16) - 1);
- String error_msg = "Last known breadcrumb: ";
-
- switch (phase) {
- case BreadcrumbMarker::ALPHA_PASS:
- error_msg += "ALPHA_PASS";
- break;
- case BreadcrumbMarker::BLIT_PASS:
- error_msg += "BLIT_PASS";
- break;
- case BreadcrumbMarker::DEBUG_PASS:
- error_msg += "DEBUG_PASS";
- break;
- case BreadcrumbMarker::LIGHTMAPPER_PASS:
- error_msg += "LIGHTMAPPER_PASS";
- break;
- case BreadcrumbMarker::OPAQUE_PASS:
- error_msg += "OPAQUE_PASS";
- break;
- case BreadcrumbMarker::POST_PROCESSING_PASS:
- error_msg += "POST_PROCESSING_PASS";
- break;
- case BreadcrumbMarker::REFLECTION_PROBES:
- error_msg += "REFLECTION_PROBES";
- break;
- case BreadcrumbMarker::SHADOW_PASS_CUBE:
- error_msg += "SHADOW_PASS_CUBE";
- break;
- case BreadcrumbMarker::SHADOW_PASS_DIRECTIONAL:
- error_msg += "SHADOW_PASS_DIRECTIONAL";
- break;
- case BreadcrumbMarker::SKY_PASS:
- error_msg += "SKY_PASS";
- break;
- case BreadcrumbMarker::TRANSPARENT_PASS:
- error_msg += "TRANSPARENT_PASS";
- break;
- case BreadcrumbMarker::UI_PASS:
- error_msg += "UI_PASS";
- break;
- default:
- error_msg += "UNKNOWN_BREADCRUMB(" + itos((uint32_t)phase) + ')';
- break;
+ {
+ String error_msg = "Printing last known breadcrumbs in reverse order (last executed first).";
+ if (!Engine::get_singleton()->is_accurate_breadcrumbs_enabled()) {
+ error_msg += "\nSome of them might be inaccurate. Try running with --accurate-breadcrumbs for precise information.";
+ }
+ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, error_msg);
}
- if (user_data != 0) {
- error_msg += " | User data: " + itos(user_data);
- }
+ uint8_t *breadcrumb_ptr = nullptr;
+ VkResult map_result = VK_SUCCESS;
- _err_print_error(FUNCTION_STR, __FILE__, __LINE__, error_msg);
+ vmaFlushAllocation(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, 0, BREADCRUMB_BUFFER_ENTRIES * sizeof(uint32_t) * 2u);
+ vmaInvalidateAllocation(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, 0, BREADCRUMB_BUFFER_ENTRIES * sizeof(uint32_t) * 2u);
+ {
+ void *ptr = nullptr;
+ map_result = vmaMapMemory(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle, &ptr);
+ breadcrumb_ptr = reinterpret_cast<uint8_t *>(ptr);
+ }
+
+ if (breadcrumb_ptr && map_result == VK_SUCCESS) {
+ uint32_t last_breadcrumb_offset = 0;
+ {
+ _err_print_error_asap("Searching last breadcrumb. We've sent up to ID: " + itos(breadcrumb_id - 1u));
+
+ // Scan the whole buffer to find the offset with the highest ID.
+ // That means that was the last one to be written.
+ //
+ // We use "breadcrumb_id - id" to account for wraparound.
+ // e.g. breadcrumb_id = 2 and id = 4294967294; then 2 - 4294967294 = 4.
+ // The one with the smallest difference is the closest to breadcrumb_id, which means it's
+ // the last written command.
+ uint32_t biggest_id = 0u;
+ uint32_t smallest_id_diff = std::numeric_limits<uint32_t>::max();
+ const uint32_t *breadcrumb_ptr32 = reinterpret_cast<const uint32_t *>(breadcrumb_ptr);
+ for (size_t i = 0u; i < BREADCRUMB_BUFFER_ENTRIES; ++i) {
+ const uint32_t id = breadcrumb_ptr32[i * 2u];
+ const uint32_t id_diff = breadcrumb_id - id;
+ if (id_diff < smallest_id_diff) {
+ biggest_id = i;
+ smallest_id_diff = id_diff;
+ }
+ }
+
+ _err_print_error_asap("Last breadcrumb ID found: " + itos(breadcrumb_ptr32[biggest_id * 2u]));
+
+ last_breadcrumb_offset = biggest_id * sizeof(uint32_t) * 2u;
+ }
+
+ const size_t entries_to_print = 8u; // Note: The value is arbitrary.
+ for (size_t i = 0u; i < entries_to_print; ++i) {
+ const uint32_t last_breadcrumb = *reinterpret_cast<uint32_t *>(breadcrumb_ptr + last_breadcrumb_offset + sizeof(uint32_t));
+ const uint32_t phase = last_breadcrumb & uint32_t(~((1 << 16) - 1));
+ const uint32_t user_data = last_breadcrumb & ((1 << 16) - 1);
+ String error_msg = "Last known breadcrumb: ";
+
+ switch (phase) {
+ case BreadcrumbMarker::ALPHA_PASS:
+ error_msg += "ALPHA_PASS";
+ break;
+ case BreadcrumbMarker::BLIT_PASS:
+ error_msg += "BLIT_PASS";
+ break;
+ case BreadcrumbMarker::DEBUG_PASS:
+ error_msg += "DEBUG_PASS";
+ break;
+ case BreadcrumbMarker::LIGHTMAPPER_PASS:
+ error_msg += "LIGHTMAPPER_PASS";
+ break;
+ case BreadcrumbMarker::OPAQUE_PASS:
+ error_msg += "OPAQUE_PASS";
+ break;
+ case BreadcrumbMarker::POST_PROCESSING_PASS:
+ error_msg += "POST_PROCESSING_PASS";
+ break;
+ case BreadcrumbMarker::REFLECTION_PROBES:
+ error_msg += "REFLECTION_PROBES";
+ break;
+ case BreadcrumbMarker::SHADOW_PASS_CUBE:
+ error_msg += "SHADOW_PASS_CUBE";
+ break;
+ case BreadcrumbMarker::SHADOW_PASS_DIRECTIONAL:
+ error_msg += "SHADOW_PASS_DIRECTIONAL";
+ break;
+ case BreadcrumbMarker::SKY_PASS:
+ error_msg += "SKY_PASS";
+ break;
+ case BreadcrumbMarker::TRANSPARENT_PASS:
+ error_msg += "TRANSPARENT_PASS";
+ break;
+ case BreadcrumbMarker::UI_PASS:
+ error_msg += "UI_PASS";
+ break;
+ default:
+ error_msg += "UNKNOWN_BREADCRUMB(" + itos((uint32_t)phase) + ')';
+ break;
+ }
+
+ if (user_data != 0) {
+ error_msg += " | User data: " + itos(user_data);
+ }
+
+ _err_print_error_asap(error_msg);
+
+ if (last_breadcrumb_offset == 0u) {
+ // Decrement last_breadcrumb_idx, wrapping underflow.
+ last_breadcrumb_offset = BREADCRUMB_BUFFER_ENTRIES * sizeof(uint32_t) * 2u;
+ }
+ last_breadcrumb_offset -= sizeof(uint32_t) * 2u;
+ }
+
+ vmaUnmapMemory(allocator, ((BufferInfo *)breadcrumb_buffer.id)->allocation.handle);
+ breadcrumb_ptr = nullptr;
+ } else {
+ _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Couldn't map breadcrumb buffer. VkResult = " + itos(map_result));
+ }
#endif
on_device_lost();
}
@@ -5386,7 +5534,9 @@ RenderingDeviceDriverVulkan::RenderingDeviceDriverVulkan(RenderingContextDriverV
}
RenderingDeviceDriverVulkan::~RenderingDeviceDriverVulkan() {
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
buffer_free(breadcrumb_buffer);
+#endif
while (small_allocs_pools.size()) {
HashMap<uint32_t, VmaPool>::Iterator E = small_allocs_pools.begin();
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index 58f7a97ec0..a7ec22e37b 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -172,7 +172,12 @@ private:
VmaPool _find_or_create_small_allocs_pool(uint32_t p_mem_type_index);
private:
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ // It's a circular buffer.
BufferID breadcrumb_buffer;
+ uint32_t breadcrumb_offset = 0u;
+ uint32_t breadcrumb_id = 0u;
+#endif
public:
/*****************/
@@ -366,6 +371,15 @@ public:
/**** FRAMEBUFFER ****/
/*********************/
+ struct Framebuffer {
+ VkFramebuffer vk_framebuffer = VK_NULL_HANDLE;
+
+ // Only filled in by a framebuffer created by a swap chain. Unused otherwise.
+ VkImage swap_chain_image = VK_NULL_HANDLE;
+ VkImageSubresourceRange swap_chain_image_subresource_range = {};
+ bool swap_chain_acquired = false;
+ };
+
virtual FramebufferID framebuffer_create(RenderPassID p_render_pass, VectorView<TextureID> p_attachments, uint32_t p_width, uint32_t p_height) override final;
virtual void framebuffer_free(FramebufferID p_framebuffer) override final;
diff --git a/editor/add_metadata_dialog.cpp b/editor/add_metadata_dialog.cpp
index 0a070e37b6..66a7b820f5 100644
--- a/editor/add_metadata_dialog.cpp
+++ b/editor/add_metadata_dialog.cpp
@@ -64,7 +64,6 @@ AddMetadataDialog::AddMetadataDialog() {
}
void AddMetadataDialog::_complete_init(const StringName &p_title) {
- add_meta_name->grab_focus();
add_meta_name->set_text("");
validation_panel->update();
@@ -90,6 +89,7 @@ void AddMetadataDialog::open(const StringName p_title, List<StringName> &p_exist
this->_existing_metas = p_existing_metas;
_complete_init(p_title);
popup_centered();
+ add_meta_name->grab_focus();
}
StringName AddMetadataDialog::get_meta_name() {
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 25e3925653..741d127ea2 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -8763,7 +8763,7 @@ void AnimationMarkerEdit::_move_selection_commit() {
void AnimationMarkerEdit::_delete_selected_markers() {
if (selection.size()) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Animation Delete Keys"));
+ undo_redo->create_action(TTR("Animation Delete Markers"));
for (const StringName &name : selection) {
double time = animation->get_marker_time(name);
undo_redo->add_do_method(animation.ptr(), "remove_marker", name);
@@ -8967,7 +8967,7 @@ AnimationMarkerEdit::AnimationMarkerEdit() {
add_child(menu);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationMarkerEdit::_menu_selected));
menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/rename_marker", TTR("Rename Marker"), Key::R), MENU_KEY_RENAME);
- menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/delete_selection", TTR("Delete Markers (s)"), Key::KEY_DELETE), MENU_KEY_DELETE);
+ menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/delete_selection", TTR("Delete Marker(s)"), Key::KEY_DELETE), MENU_KEY_DELETE);
menu->add_shortcut(ED_SHORTCUT("animation_marker_edit/toggle_marker_names", TTR("Show All Marker Names"), Key::M), MENU_KEY_TOGGLE_MARKER_NAMES);
marker_insert_confirm = memnew(ConfirmationDialog);
diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp
index d244b6b4cd..8b253f36e4 100644
--- a/editor/debugger/editor_profiler.cpp
+++ b/editor/debugger/editor_profiler.cpp
@@ -30,6 +30,7 @@
#include "editor_profiler.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp
index d4859fbe4d..b949df4518 100644
--- a/editor/debugger/editor_visual_profiler.cpp
+++ b/editor/debugger/editor_visual_profiler.cpp
@@ -30,6 +30,7 @@
#include "editor_visual_profiler.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -437,11 +438,7 @@ void EditorVisualProfiler::_notification(int p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
- if (is_layout_rtl()) {
- activate->set_icon(get_editor_theme_icon(SNAME("PlayBackwards")));
- } else {
- activate->set_icon(get_editor_theme_icon(SNAME("Play")));
- }
+ activate->set_icon(get_editor_theme_icon(SNAME("Play")));
clear_button->set_icon(get_editor_theme_icon(SNAME("Clear")));
} break;
}
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index cbe7910518..73c59707d2 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -827,7 +827,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
bool parsed = EditorDebuggerNode::get_singleton()->plugins_capture(this, p_msg, p_data);
if (!parsed) {
- WARN_PRINT("unknown message " + p_msg);
+ WARN_PRINT("Unknown message: " + p_msg);
}
}
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index ee16c61c89..bb02172b1a 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -547,6 +547,7 @@ Variant EditorData::instantiate_custom_type(const String &p_type, const String &
if (n) {
n->set_name(p_type);
}
+ n->set_meta(SceneStringName(_custom_type_script), script);
((Object *)ob)->set_script(script);
return ob;
}
@@ -1008,6 +1009,7 @@ Variant EditorData::script_class_instance(const String &p_class) {
// Store in a variant to initialize the refcount if needed.
Variant obj = ClassDB::instantiate(script->get_instance_base_type());
if (obj) {
+ Object::cast_to<Object>(obj)->set_meta(SceneStringName(_custom_type_script), script);
obj.operator Object *()->set_script(script);
}
return obj;
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 2d1a914120..50595ec7a6 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -248,11 +248,16 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Scanning file structure..."), 0, true);
nb_files_total = _scan_new_dir(first_scan_root_dir, d);
+ // Preloading GDExtensions file extensions to prevent looping on all the resource loaders
+ // for each files in _first_scan_process_scripts.
+ List<String> gdextension_extensions;
+ ResourceLoader::get_recognized_extensions_for_type("GDExtension", &gdextension_extensions);
+
// This loads the global class names from the scripts and ensures that even if the
// global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer.
// At the same time, to prevent looping multiple times in all files, it looks for extensions.
ep.step(TTR("Loading global class names..."), 1, true);
- _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions);
+ _first_scan_process_scripts(first_scan_root_dir, gdextension_extensions, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
@@ -276,16 +281,16 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Starting file scan..."), 5, true);
}
-void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
- _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions);
+ _first_scan_process_scripts(scan_sub_dir, p_gdextension_extensions, p_existing_class_names, p_extensions);
}
for (const String &scan_file : p_scan_dir->files) {
// Optimization to skip the ResourceLoader::get_resource_type for files
// that are not scripts. Some loader get_resource_type methods read the file
// which can be very slow on large projects.
- String ext = scan_file.get_extension().to_lower();
+ const String ext = scan_file.get_extension().to_lower();
bool is_script = false;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (ScriptServer::get_language(i)->get_extension() == ext) {
@@ -293,24 +298,29 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
break;
}
}
- if (!is_script) {
- continue; // Not a script.
- }
+ if (is_script) {
+ const String path = p_scan_dir->full_path.path_join(scan_file);
+ const String type = ResourceLoader::get_resource_type(path);
- String path = p_scan_dir->full_path.path_join(scan_file);
- String type = ResourceLoader::get_resource_type(path);
+ if (ClassDB::is_parent_class(type, SNAME("Script"))) {
+ String script_class_extends;
+ String script_class_icon_path;
+ String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
+ _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
- if (ClassDB::is_parent_class(type, SNAME("Script"))) {
- String script_class_extends;
- String script_class_icon_path;
- String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
- _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
+ if (!script_class_name.is_empty()) {
+ p_existing_class_names.insert(script_class_name);
+ }
+ }
+ }
- if (!script_class_name.is_empty()) {
- p_existing_class_names.insert(script_class_name);
+ // Check for GDExtensions.
+ if (p_gdextension_extensions.find(ext)) {
+ const String path = p_scan_dir->full_path.path_join(scan_file);
+ const String type = ResourceLoader::get_resource_type(path);
+ if (type == SNAME("GDExtension")) {
+ p_extensions.insert(path);
}
- } else if (type == SNAME("GDExtension")) {
- p_extensions.insert(path);
}
}
}
@@ -1017,7 +1027,9 @@ void EditorFileSystem::scan() {
void EditorFileSystem::ScanProgress::increment() {
current++;
float ratio = current / MAX(hi, 1.0f);
- progress->step(ratio * 1000.0f);
+ if (progress) {
+ progress->step(ratio * 1000.0f);
+ }
EditorFileSystem::singleton->scan_total = ratio;
}
@@ -1283,7 +1295,7 @@ void EditorFileSystem::_process_removed_files(const HashSet<String> &p_processed
}
}
-void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) {
+void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, bool p_recursive) {
uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path());
bool updated_dir = false;
@@ -1477,7 +1489,9 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
scan_actions.push_back(ia);
continue;
}
- _scan_fs_changes(p_dir->get_subdir(i), p_progress);
+ if (p_recursive) {
+ _scan_fs_changes(p_dir->get_subdir(i), p_progress);
+ }
}
nb_files_total = MAX(nb_files_total + diff_nb_files, 0);
@@ -2902,6 +2916,96 @@ void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file
emit_signal(SNAME("resources_reimported"), reloads);
}
+Error EditorFileSystem::_copy_file(const String &p_from, const String &p_to) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ if (FileAccess::exists(p_from + ".import")) {
+ Error err = da->copy(p_from, p_to);
+ if (err != OK) {
+ return err;
+ }
+
+ // Remove uid from .import file to avoid conflict.
+ Ref<ConfigFile> cfg;
+ cfg.instantiate();
+ cfg->load(p_from + ".import");
+ cfg->erase_section_key("remap", "uid");
+ err = cfg->save(p_to + ".import");
+ if (err != OK) {
+ return err;
+ }
+ } else if (ResourceLoader::get_resource_uid(p_from) == ResourceUID::INVALID_ID) {
+ // Files which do not use an uid can just be copied.
+ Error err = da->copy(p_from, p_to);
+ if (err != OK) {
+ return err;
+ }
+ } else {
+ // Load the resource and save it again in the new location (this generates a new UID).
+ Error err;
+ Ref<Resource> res = ResourceLoader::load(p_from, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
+ if (err == OK && res.is_valid()) {
+ err = ResourceSaver::save(res, p_to, ResourceSaver::FLAG_COMPRESS);
+ if (err != OK) {
+ return err;
+ }
+ } else if (err != OK) {
+ // When loading files like text files the error is OK but the resource is still null.
+ // We can ignore such files.
+ return err;
+ }
+ }
+ return OK;
+}
+
+bool EditorFileSystem::_copy_directory(const String &p_from, const String &p_to, List<CopiedFile> *p_files) {
+ Ref<DirAccess> old_dir = DirAccess::open(p_from);
+ ERR_FAIL_COND_V(old_dir.is_null(), false);
+
+ Error err = make_dir_recursive(p_to);
+ if (err != OK && err != ERR_ALREADY_EXISTS) {
+ return false;
+ }
+
+ bool success = true;
+ old_dir->set_include_navigational(false);
+ old_dir->list_dir_begin();
+
+ for (String F = old_dir->_get_next(); !F.is_empty(); F = old_dir->_get_next()) {
+ if (old_dir->current_is_dir()) {
+ success = _copy_directory(p_from.path_join(F), p_to.path_join(F), p_files) && success;
+ } else if (F.get_extension() != "import") {
+ CopiedFile copy;
+ copy.from = p_from.path_join(F);
+ copy.to = p_to.path_join(F);
+ p_files->push_back(copy);
+ }
+ }
+ return success;
+}
+
+void EditorFileSystem::_queue_refresh_filesystem() {
+ if (refresh_queued) {
+ return;
+ }
+ refresh_queued = true;
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &EditorFileSystem::_refresh_filesystem), CONNECT_ONE_SHOT);
+}
+
+void EditorFileSystem::_refresh_filesystem() {
+ for (const ObjectID &id : folders_to_sort) {
+ EditorFileSystemDirectory *dir = Object::cast_to<EditorFileSystemDirectory>(ObjectDB::get_instance(id));
+ if (dir) {
+ dir->subdirs.sort_custom<DirectoryComparator>();
+ }
+ }
+ folders_to_sort.clear();
+
+ _update_scan_actions();
+
+ emit_signal(SNAME("filesystem_changed"));
+ refresh_queued = false;
+}
+
void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) {
int current_max = p_import_data->reimport_from + int(p_index);
p_import_data->max_index.exchange_if_greater(current_max);
@@ -3225,10 +3329,9 @@ Error EditorFileSystem::make_dir_recursive(const String &p_path, const String &p
const String path = da->get_current_dir();
EditorFileSystemDirectory *parent = get_filesystem_path(path);
ERR_FAIL_NULL_V(parent, ERR_FILE_NOT_FOUND);
+ folders_to_sort.insert(parent->get_instance_id());
const PackedStringArray folders = p_path.trim_prefix(path).trim_suffix("/").split("/");
- bool first = true;
-
for (const String &folder : folders) {
const int current = parent->find_dir_index(folder);
if (current > -1) {
@@ -3240,18 +3343,59 @@ Error EditorFileSystem::make_dir_recursive(const String &p_path, const String &p
efd->parent = parent;
efd->name = folder;
parent->subdirs.push_back(efd);
-
- if (first) {
- parent->subdirs.sort_custom<DirectoryComparator>();
- first = false;
- }
parent = efd;
}
- emit_signal(SNAME("filesystem_changed"));
+ _queue_refresh_filesystem();
+ return OK;
+}
+
+Error EditorFileSystem::copy_file(const String &p_from, const String &p_to) {
+ _copy_file(p_from, p_to);
+
+ EditorFileSystemDirectory *parent = get_filesystem_path(p_to.get_base_dir());
+ ERR_FAIL_NULL_V(parent, ERR_FILE_NOT_FOUND);
+
+ ScanProgress sp;
+ _scan_fs_changes(parent, sp, false);
+
+ _queue_refresh_filesystem();
return OK;
}
+Error EditorFileSystem::copy_directory(const String &p_from, const String &p_to) {
+ List<CopiedFile> files;
+ bool success = _copy_directory(p_from, p_to, &files);
+
+ EditorProgress *ep = nullptr;
+ if (files.size() > 10) {
+ ep = memnew(EditorProgress("_copy_files", TTR("Copying files..."), files.size()));
+ }
+
+ int i = 0;
+ for (const CopiedFile &F : files) {
+ if (_copy_file(F.from, F.to) != OK) {
+ success = false;
+ }
+ if (ep) {
+ ep->step(F.from.get_file(), i++, false);
+ }
+ }
+ memdelete_notnull(ep);
+
+ EditorFileSystemDirectory *efd = get_filesystem_path(p_to);
+ ERR_FAIL_NULL_V(efd, FAILED);
+ ERR_FAIL_NULL_V(efd->get_parent(), FAILED);
+
+ folders_to_sort.insert(efd->get_parent()->get_instance_id());
+
+ ScanProgress sp;
+ _scan_fs_changes(efd, sp);
+
+ _queue_refresh_filesystem();
+ return success ? OK : FAILED;
+}
+
ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate) {
if (!p_path.is_resource_file() || p_path.begins_with(ProjectSettings::get_singleton()->get_project_data_path())) {
// Saved externally (configuration file) or internal file, do not assign an ID.
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 7aa0137f4e..1479c0e49e 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -191,7 +191,7 @@ class EditorFileSystem : public Node {
void _scan_filesystem();
void _first_scan_filesystem();
- void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
HashSet<String> late_update_files;
@@ -239,7 +239,7 @@ class EditorFileSystem : public Node {
bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const;
- void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
+ void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, bool p_recursive = true);
void _delete_internal_files(const String &p_file);
int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
@@ -324,6 +324,19 @@ class EditorFileSystem : public Node {
HashSet<String> group_file_cache;
HashMap<String, String> file_icon_cache;
+ struct CopiedFile {
+ String from;
+ String to;
+ };
+
+ bool refresh_queued = false;
+ HashSet<ObjectID> folders_to_sort;
+
+ Error _copy_file(const String &p_from, const String &p_to);
+ bool _copy_directory(const String &p_from, const String &p_to, List<CopiedFile> *p_files);
+ void _queue_refresh_filesystem();
+ void _refresh_filesystem();
+
struct ImportThreadData {
const ImportFile *reimport_files;
int reimport_from;
@@ -378,6 +391,8 @@ public:
void move_group_file(const String &p_path, const String &p_new_path);
Error make_dir_recursive(const String &p_path, const String &p_base_path = String());
+ Error copy_file(const String &p_from, const String &p_to);
+ Error copy_directory(const String &p_from, const String &p_to);
static bool _should_skip_directory(const String &p_path);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index dd6c88ef25..c69f443a22 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -35,6 +35,7 @@
#include "core/input/input.h"
#include "core/io/config_file.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/class_db.h"
@@ -3378,6 +3379,8 @@ void EditorNode::unload_editor_addons() {
remove_editor_plugin(E.value, false);
memdelete(E.value);
}
+
+ addon_name_to_plugin.clear();
}
void EditorNode::_discard_changes(const String &p_str) {
@@ -4672,6 +4675,11 @@ void EditorNode::stop_child_process(OS::ProcessID p_pid) {
Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const {
ERR_FAIL_NULL_V(p_object, nullptr);
+ const Node *node = Object::cast_to<const Node>(p_object);
+ if (node && node->has_meta(SceneStringName(_custom_type_script))) {
+ return node->get_meta(SceneStringName(_custom_type_script));
+ }
+
Ref<Script> scr = p_object->get_script();
if (scr.is_valid()) {
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index c5a35e466c..158ea4b8a7 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -2648,7 +2648,7 @@ Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool
return Variant();
}
-void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
+void EditorPropertyNodePath::_node_selected(const NodePath &p_path, bool p_absolute) {
NodePath path = p_path;
Node *base_node = get_base_node();
@@ -2658,7 +2658,7 @@ void EditorPropertyNodePath::_node_selected(const NodePath &p_path) {
path = get_tree()->get_edited_scene_root()->get_path_to(to_node);
}
- if (base_node) { // for AnimationTrackKeyEdit
+ if (p_absolute && base_node) { // for AnimationTrackKeyEdit
path = base_node->get_path().rel_path_to(p_path);
}
@@ -2680,7 +2680,7 @@ void EditorPropertyNodePath::_node_assign() {
scene_tree->get_scene_tree()->set_show_enabled_subscene(true);
scene_tree->set_valid_types(valid_types);
add_child(scene_tree);
- scene_tree->connect("selected", callable_mp(this, &EditorPropertyNodePath::_node_selected));
+ scene_tree->connect("selected", callable_mp(this, &EditorPropertyNodePath::_node_selected).bind(true));
}
Variant val = get_edited_property_value();
@@ -2748,7 +2748,7 @@ void EditorPropertyNodePath::_accept_text() {
void EditorPropertyNodePath::_text_submitted(const String &p_text) {
NodePath np = p_text;
- emit_changed(get_edited_property(), np);
+ _node_selected(np, false);
edit->hide();
assign->show();
menu->show();
@@ -3221,6 +3221,7 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const
}
resource_picker->set_base_type(p_base_type);
+ resource_picker->set_resource_owner(p_object);
resource_picker->set_editable(true);
resource_picker->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(resource_picker);
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 004630da3e..9cc72cd5c5 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -628,7 +628,7 @@ class EditorPropertyNodePath : public EditorProperty {
bool editing_node = false;
Vector<StringName> valid_types;
- void _node_selected(const NodePath &p_path);
+ void _node_selected(const NodePath &p_path, bool p_absolute = true);
void _node_assign();
Node *get_base_node();
void _update_menu();
diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp
index e38ab456cb..ca8854f797 100644
--- a/editor/editor_property_name_processor.cpp
+++ b/editor/editor_property_name_processor.cpp
@@ -198,6 +198,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["gi"] = "GI";
capitalize_string_remaps["gl"] = "GL";
capitalize_string_remaps["glb"] = "GLB";
+ capitalize_string_remaps["gles"] = "GLES";
capitalize_string_remaps["gles2"] = "GLES2";
capitalize_string_remaps["gles3"] = "GLES3";
capitalize_string_remaps["gltf"] = "glTF";
@@ -231,6 +232,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["kb"] = "(KB)"; // Unit.
capitalize_string_remaps["lcd"] = "LCD";
capitalize_string_remaps["ldr"] = "LDR";
+ capitalize_string_remaps["linuxbsd"] = "Linux/*BSD";
capitalize_string_remaps["lod"] = "LOD";
capitalize_string_remaps["lods"] = "LODs";
capitalize_string_remaps["lowpass"] = "Low-pass";
@@ -248,6 +250,7 @@ EditorPropertyNameProcessor::EditorPropertyNameProcessor() {
capitalize_string_remaps["oidn"] = "OIDN";
capitalize_string_remaps["ok"] = "OK";
capitalize_string_remaps["opengl"] = "OpenGL";
+ capitalize_string_remaps["opengl3"] = "OpenGL 3";
capitalize_string_remaps["opentype"] = "OpenType";
capitalize_string_remaps["openxr"] = "OpenXR";
capitalize_string_remaps["osslsigncode"] = "osslsigncode";
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 0f0287718c..025d019f45 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -224,7 +224,9 @@ void EditorResourcePicker::_update_menu_items() {
}
if (is_editable()) {
- edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
+ if (!_is_custom_type_script()) {
+ edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
+ }
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
// Check whether the resource has subresources.
@@ -694,6 +696,16 @@ bool EditorResourcePicker::_is_type_valid(const String &p_type_name, const HashS
return false;
}
+bool EditorResourcePicker::_is_custom_type_script() const {
+ Ref<Script> resource_as_script = edited_resource;
+
+ if (resource_as_script.is_valid() && resource_owner && resource_owner->has_meta(SceneStringName(_custom_type_script))) {
+ return true;
+ }
+
+ return false;
+}
+
Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
if (edited_resource.is_valid()) {
Dictionary drag_data = EditorNode::get_singleton()->drag_resource(edited_resource, p_from);
@@ -953,6 +965,10 @@ bool EditorResourcePicker::is_toggle_pressed() const {
return assign_button->is_pressed();
}
+void EditorResourcePicker::set_resource_owner(Object *p_object) {
+ resource_owner = p_object;
+}
+
void EditorResourcePicker::set_editable(bool p_editable) {
editable = p_editable;
assign_button->set_disabled(!editable && !edited_resource.is_valid());
@@ -1098,7 +1114,10 @@ void EditorScriptPicker::set_create_options(Object *p_menu_node) {
return;
}
- menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
+ if (!(script_owner && script_owner->has_meta(SceneStringName(_custom_type_script)))) {
+ menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
+ }
+
if (script_owner) {
Ref<Script> scr = script_owner->get_script();
if (scr.is_valid()) {
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 0a32dea3ed..8fb774a2cb 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -81,6 +81,8 @@ class EditorResourcePicker : public HBoxContainer {
CONVERT_BASE_ID = 1000,
};
+ Object *resource_owner = nullptr;
+
PopupMenu *edit_menu = nullptr;
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
@@ -102,6 +104,7 @@ class EditorResourcePicker : public HBoxContainer {
void _ensure_allowed_types() const;
bool _is_drop_valid(const Dictionary &p_drag_data) const;
bool _is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const;
+ bool _is_custom_type_script() const;
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
@@ -137,6 +140,8 @@ public:
void set_toggle_pressed(bool p_pressed);
bool is_toggle_pressed() const;
+ void set_resource_owner(Object *p_object);
+
void set_editable(bool p_editable);
bool is_editable() const;
diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp
index a6b30233fc..8fde728027 100644
--- a/editor/event_listener_line_edit.cpp
+++ b/editor/event_listener_line_edit.cpp
@@ -121,7 +121,7 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo
}
String EventListenerLineEdit::get_device_string(int p_device) {
- if (p_device == InputMap::ALL_DEVICES) {
+ if (p_device == InputEvent::DEVICE_ID_ALL_DEVICES) {
return TTR("All Devices");
}
return TTR("Device") + " " + itos(p_device);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 58737c53ed..50fa49dc52 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -326,11 +326,7 @@ Error EditorExportPlatform::_save_zip_patch_file(void *p_userdata, const String
Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
- if (EditorNode::get_singleton()->get_gui_base()->is_layout_rtl()) {
- return theme->get_icon(SNAME("PlayBackwards"), EditorStringName(EditorIcons));
- } else {
- return theme->get_icon(SNAME("Play"), EditorStringName(EditorIcons));
- }
+ return theme->get_icon(SNAME("Play"), EditorStringName(EditorIcons));
}
String EditorExportPlatform::find_export_template(const String &template_file_name, String *err) const {
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 53982b37b9..2df0b2edaa 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1491,76 +1491,22 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin
EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n");
return;
}
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (p_item.is_file) {
print_verbose("Duplicating " + old_path + " -> " + new_path);
// Create the directory structure.
- da->make_dir_recursive(new_path.get_base_dir());
-
- if (FileAccess::exists(old_path + ".import")) {
- Error err = da->copy(old_path, new_path);
- if (err != OK) {
- EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n");
- return;
- }
-
- // Remove uid from .import file to avoid conflict.
- Ref<ConfigFile> cfg;
- cfg.instantiate();
- cfg->load(old_path + ".import");
- cfg->erase_section_key("remap", "uid");
- err = cfg->save(new_path + ".import");
- if (err != OK) {
- EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ".import: " + error_names[err] + "\n");
- return;
- }
- } else {
- // Files which do not use an uid can just be copied.
- if (ResourceLoader::get_resource_uid(old_path) == ResourceUID::INVALID_ID) {
- Error err = da->copy(old_path, new_path);
- if (err != OK) {
- EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n");
- }
- return;
- }
+ EditorFileSystem::get_singleton()->make_dir_recursive(p_new_path.get_base_dir());
- // Load the resource and save it again in the new location (this generates a new UID).
- Error err;
- Ref<Resource> res = ResourceLoader::load(old_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err);
- if (err == OK && res.is_valid()) {
- err = ResourceSaver::save(res, new_path, ResourceSaver::FLAG_COMPRESS);
- if (err != OK) {
- EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to save resource at %s: %s"), new_path, error_names[err]));
- }
- } else if (err != OK) {
- // When loading files like text files the error is OK but the resource is still null.
- // We can ignore such files.
- EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to load resource at %s: %s"), new_path, error_names[err]));
- }
+ Error err = EditorFileSystem::get_singleton()->copy_file(old_path, new_path);
+ if (err != OK) {
+ EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n");
}
} else {
- da->make_dir(new_path);
-
- // Recursively duplicate all files inside the folder.
- Ref<DirAccess> old_dir = DirAccess::open(old_path);
- ERR_FAIL_COND(old_dir.is_null());
-
- Ref<FileAccess> file_access = FileAccess::create(FileAccess::ACCESS_RESOURCES);
- old_dir->set_include_navigational(false);
- old_dir->list_dir_begin();
- for (String f = old_dir->_get_next(); !f.is_empty(); f = old_dir->_get_next()) {
- if (f.get_extension() == "import") {
- continue;
- }
- if (file_access->file_exists(old_path + f)) {
- _try_duplicate_item(FileOrFolder(old_path + f, true), new_path + f);
- } else if (da->dir_exists(old_path + f)) {
- _try_duplicate_item(FileOrFolder(old_path + f, false), new_path + f);
- }
+ Error err = EditorFileSystem::get_singleton()->copy_directory(old_path, new_path);
+ if (err != OK) {
+ EditorNode::get_singleton()->add_io_error(TTR("Error duplicating directory:") + "\n" + old_path + "\n");
}
- old_dir->list_dir_end();
}
}
@@ -1866,21 +1812,15 @@ void FileSystemDock::_rename_operation_confirm() {
}
void FileSystemDock::_duplicate_operation_confirm(const String &p_path) {
- String base_dir = p_path.trim_suffix("/").get_base_dir();
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- if (!da->dir_exists(base_dir)) {
- Error err = da->make_dir_recursive(base_dir);
-
+ const String base_dir = p_path.trim_suffix("/").get_base_dir();
+ if (!DirAccess::dir_exists_absolute(base_dir)) {
+ Error err = EditorFileSystem::get_singleton()->make_dir_recursive(base_dir);
if (err != OK) {
EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create base directory: %s"), error_names[err]));
return;
}
}
_try_duplicate_item(to_duplicate, p_path);
-
- // Rescan everything.
- print_verbose("FileSystem: calling rescan.");
- _rescan();
}
void FileSystemDock::_overwrite_dialog_action(bool p_overwrite) {
@@ -3431,7 +3371,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
const bool is_directory = fpath.ends_with("/");
p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("Terminal")), ED_GET_SHORTCUT("filesystem_dock/open_in_terminal"), FILE_OPEN_IN_TERMINAL);
- p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Containing Folder in Terminal"));
+ p_popup->set_item_text(p_popup->get_item_index(FILE_OPEN_IN_TERMINAL), is_directory ? TTR("Open in Terminal") : TTR("Open Folder in Terminal"));
if (!is_directory) {
p_popup->add_icon_shortcut(get_editor_theme_icon(SNAME("ExternalLink")), ED_GET_SHORTCUT("filesystem_dock/open_in_external_program"), FILE_OPEN_EXTERNAL);
@@ -4060,17 +4000,17 @@ FileSystemDock::FileSystemDock() {
// `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts.
ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);
- ED_SHORTCUT("filesystem_dock/copy_absolute_path", TTR("Copy Absolute Path"));
- ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID"));
+ ED_SHORTCUT("filesystem_dock/copy_absolute_path", TTR("Copy Absolute Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::C);
+ ED_SHORTCUT("filesystem_dock/copy_uid", TTR("Copy UID"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::C);
ED_SHORTCUT("filesystem_dock/duplicate", TTR("Duplicate..."), KeyModifierMask::CMD_OR_CTRL | Key::D);
ED_SHORTCUT("filesystem_dock/delete", TTR("Delete"), Key::KEY_DELETE);
ED_SHORTCUT("filesystem_dock/rename", TTR("Rename..."), Key::F2);
ED_SHORTCUT_OVERRIDE("filesystem_dock/rename", "macos", Key::ENTER);
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
// Opening the system file manager or opening in an external program is not supported on the Android and web editors.
- ED_SHORTCUT("filesystem_dock/show_in_explorer", TTR("Open in File Manager"));
- ED_SHORTCUT("filesystem_dock/open_in_external_program", TTR("Open in External Program"));
- ED_SHORTCUT("filesystem_dock/open_in_terminal", TTR("Open in Terminal"));
+ ED_SHORTCUT("filesystem_dock/show_in_explorer", TTR("Open in File Manager"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::R);
+ ED_SHORTCUT("filesystem_dock/open_in_external_program", TTR("Open in External Program"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::E);
+ ED_SHORTCUT("filesystem_dock/open_in_terminal", TTR("Open in Terminal"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T);
#endif
// Properly translating color names would require a separate HashMap, so for simplicity they are provided as comments.
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index 77d0ba7a60..7600748685 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -1355,6 +1355,13 @@ EditorFileDialog::Access EditorFileDialog::get_access() const {
void EditorFileDialog::_make_dir_confirm() {
const String stripped_dirname = makedirname->get_text().strip_edges();
+ if (stripped_dirname.is_empty()) {
+ error_dialog->set_text(TTR("The path specified is invalid."));
+ error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
+ makedirname->set_text(""); // Reset label.
+ return;
+ }
+
if (dir_access->dir_exists(stripped_dirname)) {
error_dialog->set_text(TTR("Could not create folder. File with that name already exists."));
error_dialog->popup_centered(Size2(250, 50) * EDSCALE);
@@ -1638,6 +1645,7 @@ void EditorFileDialog::_update_favorites() {
for (int i = 0; i < favorited_paths.size(); i++) {
favorites->add_item(favorited_names[i], theme_cache.folder);
+ favorites->set_item_tooltip(-1, favorited_paths[i]);
favorites->set_item_metadata(-1, favorited_paths[i]);
favorites->set_item_icon_modulate(-1, get_dir_icon_color(favorited_paths[i]));
@@ -1719,6 +1727,7 @@ void EditorFileDialog::_update_recent() {
for (int i = 0; i < recentd_paths.size(); i++) {
recent->add_item(recentd_names[i], theme_cache.folder);
+ recent->set_item_tooltip(-1, recentd_paths[i]);
recent->set_item_metadata(-1, recentd_paths[i]);
recent->set_item_icon_modulate(-1, get_dir_icon_color(recentd_paths[i]));
}
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index a073a2338b..27b6bbafb7 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -37,10 +37,6 @@
#include "editor/themes/editor_scale.h"
#include "scene/theme/theme_db.h"
-bool EditorSpinSlider::is_text_field() const {
- return true;
-}
-
String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const {
if (!read_only && grabber->is_visible()) {
Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h
index 2476c2f71b..dfc50878dd 100644
--- a/editor/gui/editor_spin_slider.h
+++ b/editor/gui/editor_spin_slider.h
@@ -101,8 +101,6 @@ protected:
void _focus_entered();
public:
- virtual bool is_text_field() const override;
-
String get_tooltip(const Point2 &p_pos) const override;
String get_text_value() const;
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index a579224ecd..5cec366d69 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -202,12 +202,12 @@ static Error _parse_material_library(const String &p_path, HashMap<String, Ref<S
return OK;
}
-static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, List<String> *r_missing_deps) {
+static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes, bool p_single_mesh, bool p_generate_tangents, bool p_generate_lods, bool p_generate_shadow_mesh, bool p_generate_lightmap_uv2, float p_generate_lightmap_uv2_texel_size, const PackedByteArray &p_src_lightmap_cache, Vector3 p_scale_mesh, Vector3 p_offset_mesh, bool p_disable_compression, Vector<Vector<uint8_t>> &r_lightmap_caches, List<String> *r_missing_deps) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Couldn't open OBJ file '%s', it may not exist or not be readable.", p_path));
- // Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree)
- // This should only match, if it's indeed a COFF file header
+ // Avoid trying to load/interpret potential build artifacts from Visual Studio (e.g. when compiling native plugins inside the project tree).
+ // This should only match if it's indeed a COFF file header.
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
const int first_bytes = f->get_16();
static const Vector<int> coff_header_machines{
@@ -445,6 +445,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, array, TypedArray<Array>(), Dictionary(), material, name, mesh_flags);
+
print_verbose("OBJ: Added surface :" + mesh->get_surface_name(mesh->get_surface_count() - 1));
if (!current_material.is_empty()) {
@@ -508,6 +509,43 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
}
+ if (p_generate_lightmap_uv2) {
+ Vector<uint8_t> lightmap_cache;
+ mesh->lightmap_unwrap_cached(Transform3D(), p_generate_lightmap_uv2_texel_size, p_src_lightmap_cache, lightmap_cache);
+
+ if (!lightmap_cache.is_empty()) {
+ if (r_lightmap_caches.is_empty()) {
+ r_lightmap_caches.push_back(lightmap_cache);
+ } else {
+ // MD5 is stored at the beginning of the cache data.
+ const String new_md5 = String::md5(lightmap_cache.ptr());
+
+ for (int i = 0; i < r_lightmap_caches.size(); i++) {
+ const String md5 = String::md5(r_lightmap_caches[i].ptr());
+ if (new_md5 < md5) {
+ r_lightmap_caches.insert(i, lightmap_cache);
+ break;
+ }
+
+ if (new_md5 == md5) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ mesh->optimize_indices_for_cache();
+
+ if (p_generate_lods) {
+ // Use normal merge/split angles that match the defaults used for 3D scene importing.
+ mesh->generate_lods(60.0f, 25.0f, {});
+ }
+
+ if (p_generate_shadow_mesh) {
+ mesh->create_shadow_mesh();
+ }
+
if (p_single_mesh && mesh->get_surface_count() > 0) {
r_meshes.push_back(mesh);
}
@@ -518,7 +556,10 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
Node *EditorOBJImporter::import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) {
List<Ref<ImporterMesh>> meshes;
- Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, r_missing_deps);
+ // LOD, shadow mesh and lightmap UV2 generation are handled by ResourceImporterScene in this case,
+ // so disable it within the OBJ mesh import.
+ Vector<Vector<uint8_t>> mesh_lightmap_caches;
+ Error err = _parse_obj(p_path, meshes, false, p_flags & IMPORT_GENERATE_TANGENT_ARRAYS, false, false, false, 0.2, PackedByteArray(), Vector3(1, 1, 1), Vector3(0, 0, 0), p_flags & IMPORT_FORCE_DISABLE_MESH_COMPRESSION, mesh_lightmap_caches, r_missing_deps);
if (err != OK) {
if (r_err) {
@@ -587,19 +628,51 @@ String ResourceImporterOBJ::get_preset_name(int p_idx) const {
void ResourceImporterOBJ::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_tangents"), true));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_lods"), true));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_shadow_mesh"), true));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate_lightmap_uv2", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "generate_lightmap_uv2_texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2));
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "scale_mesh"), Vector3(1, 1, 1)));
r_options->push_back(ImportOption(PropertyInfo(Variant::VECTOR3, "offset_mesh"), Vector3(0, 0, 0)));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_disable_mesh_compression"), false));
}
bool ResourceImporterOBJ::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+ if (p_option == "generate_lightmap_uv2_texel_size" && !p_options["generate_lightmap_uv2"]) {
+ // Only display the lightmap texel size import option when lightmap UV2 generation is enabled.
+ return false;
+ }
+
return true;
}
Error ResourceImporterOBJ::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
List<Ref<ImporterMesh>> meshes;
- Error err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], nullptr);
+ Vector<uint8_t> src_lightmap_cache;
+ Vector<Vector<uint8_t>> mesh_lightmap_caches;
+
+ Error err;
+ {
+ src_lightmap_cache = FileAccess::get_file_as_bytes(p_source_file + ".unwrap_cache", &err);
+ if (err != OK) {
+ src_lightmap_cache.clear();
+ }
+ }
+
+ err = _parse_obj(p_source_file, meshes, true, p_options["generate_tangents"], p_options["generate_lods"], p_options["generate_shadow_mesh"], p_options["generate_lightmap_uv2"], p_options["generate_lightmap_uv2_texel_size"], src_lightmap_cache, p_options["scale_mesh"], p_options["offset_mesh"], p_options["force_disable_mesh_compression"], mesh_lightmap_caches, nullptr);
+
+ if (mesh_lightmap_caches.size()) {
+ Ref<FileAccess> f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);
+ if (f.is_valid()) {
+ f->store_32(mesh_lightmap_caches.size());
+ for (int i = 0; i < mesh_lightmap_caches.size(); i++) {
+ String md5 = String::md5(mesh_lightmap_caches[i].ptr());
+ f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());
+ }
+ }
+ }
+ err = OK;
ERR_FAIL_COND_V(err != OK, err);
ERR_FAIL_COND_V(meshes.size() != 1, ERR_BUG);
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 7a6f39906c..339e7921b3 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -112,7 +112,15 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
}
/* GET FILESIZE */
- file->get_32(); // filesize
+
+ // The file size in header is 8 bytes less than the actual size.
+ // See https://docs.fileformat.com/audio/wav/
+ const int FILE_SIZE_HEADER_OFFSET = 8;
+ uint32_t file_size_header = file->get_32() + FILE_SIZE_HEADER_OFFSET;
+ uint64_t file_size = file->get_length();
+ if (file_size != file_size_header) {
+ WARN_PRINT(vformat("File size %d is %s than the expected size %d. (%s)", file_size, file_size > file_size_header ? "larger" : "smaller", file_size_header, p_source_file));
+ }
/* CHECK WAVE */
@@ -198,7 +206,12 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
break;
}
+ uint64_t remaining_bytes = file_size - file_pos;
frames = chunksize;
+ if (remaining_bytes < chunksize) {
+ WARN_PRINT(vformat("Data chunk size is smaller than expected. Proceeding with actual data size. (%s)", p_source_file));
+ frames = remaining_bytes;
+ }
if (format_channels == 0) {
ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index 14065abf73..6c22a965ae 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -790,23 +790,14 @@ ImportDock::ImportDock() {
import->set_text(TTR("Reimport"));
import->set_disabled(true);
import->connect(SceneStringName(pressed), callable_mp(this, &ImportDock::_reimport_pressed));
- if (!DisplayServer::get_singleton()->get_swap_cancel_ok()) {
- advanced_spacer = hb->add_spacer();
- advanced = memnew(Button);
- advanced->set_text(TTR("Advanced..."));
- hb->add_child(advanced);
- }
+ advanced_spacer = hb->add_spacer();
+ advanced = memnew(Button);
+ advanced->set_text(TTR("Advanced..."));
+ hb->add_child(advanced);
hb->add_spacer();
hb->add_child(import);
hb->add_spacer();
- if (DisplayServer::get_singleton()->get_swap_cancel_ok()) {
- advanced = memnew(Button);
- advanced->set_text(TTR("Advanced..."));
- hb->add_child(advanced);
- advanced_spacer = hb->add_spacer();
- }
-
advanced->hide();
advanced_spacer->hide();
advanced->connect(SceneStringName(pressed), callable_mp(this, &ImportDock::_advanced_options));
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index c60197b96b..a2aeeb11bd 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -551,18 +551,18 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
}
void InputEventConfigurationDialog::_device_selection_changed(int p_option_button_index) {
- // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1)
- // and option index 1 corresponds to device 0, etc...
- event->set_device(p_option_button_index - 1);
+ // Option index 0 corresponds to "All Devices" (value of -3).
+ // Otherwise subtract 1 as option index 1 corresponds to device 0, etc...
+ event->set_device(p_option_button_index == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : p_option_button_index - 1);
event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true));
}
void InputEventConfigurationDialog::_set_current_device(int p_device) {
- device_id_option->select(p_device + 1);
+ device_id_option->select(p_device == InputEvent::DEVICE_ID_ALL_DEVICES ? 0 : p_device + 1);
}
int InputEventConfigurationDialog::_get_current_device() const {
- return device_id_option->get_selected() - 1;
+ return device_id_option->get_selected() == 0 ? InputEvent::DEVICE_ID_ALL_DEVICES : device_id_option->get_selected() - 1;
}
void InputEventConfigurationDialog::_notification(int p_what) {
@@ -705,11 +705,12 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
device_id_option = memnew(OptionButton);
device_id_option->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- for (int i = -1; i < 8; i++) {
+ device_id_option->add_item(EventListenerLineEdit::get_device_string(InputEvent::DEVICE_ID_ALL_DEVICES));
+ for (int i = 0; i < 8; i++) {
device_id_option->add_item(EventListenerLineEdit::get_device_string(i));
}
device_id_option->connect(SceneStringName(item_selected), callable_mp(this, &InputEventConfigurationDialog::_device_selection_changed));
- _set_current_device(InputMap::ALL_DEVICES);
+ _set_current_device(InputEvent::DEVICE_ID_ALL_DEVICES);
device_container->add_child(device_id_option);
device_container->hide();
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index e6afc85e9e..1581e7cc66 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -2006,30 +2006,34 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
HBoxContainer *hb = memnew(HBoxContainer);
add_child(hb);
+ HBoxContainer *playback_container = memnew(HBoxContainer);
+ playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);
+ hb->add_child(playback_container);
+
play_bw_from = memnew(Button);
play_bw_from->set_theme_type_variation("FlatButton");
play_bw_from->set_tooltip_text(TTR("Play Animation Backwards"));
- hb->add_child(play_bw_from);
+ playback_container->add_child(play_bw_from);
play_bw = memnew(Button);
play_bw->set_theme_type_variation("FlatButton");
play_bw->set_tooltip_text(TTR("Play Animation Backwards from End"));
- hb->add_child(play_bw);
+ playback_container->add_child(play_bw);
stop = memnew(Button);
stop->set_theme_type_variation("FlatButton");
stop->set_tooltip_text(TTR("Pause/Stop Animation"));
- hb->add_child(stop);
+ playback_container->add_child(stop);
play = memnew(Button);
play->set_theme_type_variation("FlatButton");
play->set_tooltip_text(TTR("Play Animation from Start"));
- hb->add_child(play);
+ playback_container->add_child(play);
play_from = memnew(Button);
play_from->set_theme_type_variation("FlatButton");
play_from->set_tooltip_text(TTR("Play Animation"));
- hb->add_child(play_from);
+ playback_container->add_child(play_from);
frame = memnew(SpinBox);
hb->add_child(frame);
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index cd13deb3e9..24943af60f 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -536,7 +536,6 @@ ControlEditorPopupButton::ControlEditorPopupButton() {
set_focus_mode(FOCUS_NONE);
popup_panel = memnew(PopupPanel);
- popup_panel->set_theme_type_variation("ControlEditorPopupPanel");
add_child(popup_panel);
popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true));
popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false));
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 9a53f07a3f..3618c0e6d3 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/file_access_memory.h"
+#include "core/io/image.h"
#include "core/io/resource_loader.h"
#include "core/object/script_language.h"
#include "core/os/os.h"
diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
index 01492c1dd0..4a784be064 100644
--- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
@@ -93,7 +93,7 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g
}
if (Object::cast_to<CylinderShape3D>(*s)) {
- return p_id == 0 ? "Radius" : "Height";
+ return helper->cylinder_get_handle_name(p_id);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
@@ -219,25 +219,15 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i
}
if (Object::cast_to<CylinderShape3D>(*s)) {
- Vector3 axis;
- axis[p_id == 0 ? 0 : 1] = 1.0;
Ref<CylinderShape3D> cs2 = s;
- Vector3 ra, rb;
- Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
- float d = axis.dot(ra);
- if (Node3DEditor::get_singleton()->is_snap_enabled()) {
- d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
- }
- if (d < 0.001) {
- d = 0.001;
- }
-
- if (p_id == 0) {
- cs2->set_radius(d);
- } else if (p_id == 1) {
- cs2->set_height(d * 2.0);
- }
+ real_t height = cs2->get_height();
+ real_t radius = cs2->get_radius();
+ Vector3 position;
+ helper->cylinder_set_handle(sg, p_id, height, radius, position);
+ cs2->set_height(height);
+ cs2->set_radius(radius);
+ cs->set_global_position(position);
}
}
@@ -293,31 +283,7 @@ void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo
if (Object::cast_to<CylinderShape3D>(*s)) {
Ref<CylinderShape3D> ss = s;
- if (p_cancel) {
- if (p_id == 0) {
- ss->set_radius(p_restore);
- } else {
- ss->set_height(p_restore);
- }
- return;
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- if (p_id == 0) {
- ur->create_action(TTR("Change Cylinder Shape Radius"));
- ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
- ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
- } else {
- ur->create_action(
- ///
-
- ////////
- TTR("Change Cylinder Shape Height"));
- ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
- ur->add_undo_method(ss.ptr(), "set_height", p_restore);
- }
-
- ur->commit_action();
+ helper->cylinder_commit_handle(p_id, TTR("Change Cylinder Shape Radius"), TTR("Change Cylinder Shape Height"), p_cancel, cs, *ss, *ss);
}
if (Object::cast_to<SeparationRayShape3D>(*s)) {
@@ -534,10 +500,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_collision_segments(collision_segments);
- Vector<Vector3> handles = {
- Vector3(cs2->get_radius(), 0, 0),
- Vector3(0, cs2->get_height() * 0.5, 0)
- };
+ Vector<Vector3> handles = helper->cylinder_get_handles(cs2->get_height(), cs2->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
diff --git a/editor/plugins/gizmos/gizmo_3d_helper.cpp b/editor/plugins/gizmos/gizmo_3d_helper.cpp
index 1226be90cb..ff1b67aa4a 100644
--- a/editor/plugins/gizmos/gizmo_3d_helper.cpp
+++ b/editor/plugins/gizmos/gizmo_3d_helper.cpp
@@ -139,3 +139,98 @@ void Gizmo3DHelper::box_commit_handle(const String &p_action_name, bool p_cancel
ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
ur->commit_action();
}
+
+Vector<Vector3> Gizmo3DHelper::cylinder_get_handles(real_t p_height, real_t p_radius) {
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(p_radius, 0, 0));
+ handles.push_back(Vector3(0, p_height * 0.5, 0));
+ handles.push_back(Vector3(0, p_height * -0.5, 0));
+ return handles;
+}
+
+String Gizmo3DHelper::cylinder_get_handle_name(int p_id) const {
+ if (p_id == 0) {
+ return "Radius";
+ } else {
+ return "Height";
+ }
+}
+
+void Gizmo3DHelper::cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position) {
+ int sign = p_id == 2 ? -1 : 1;
+ int axis = p_id == 0 ? 0 : 1;
+
+ Vector3 axis_vector;
+ axis_vector[axis] = sign;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(axis_vector * -4096, axis_vector * 4096, p_segment[0], p_segment[1], ra, rb);
+ float d = axis_vector.dot(ra);
+
+ // Snap to grid.
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (p_id == 0) {
+ // Adjust radius.
+ if (d < 0.001) {
+ d = 0.001;
+ }
+ r_radius = d;
+ r_cylinder_position = initial_transform.get_origin();
+ } else if (p_id == 1 || p_id == 2) {
+ real_t initial_height = initial_value;
+
+ // Adjust height.
+ if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
+ r_height = d * 2.0;
+ } else {
+ r_height = (initial_height * 0.5) + d;
+ }
+
+ if (r_height < 0.001) {
+ r_height = 0.001;
+ }
+
+ // Adjust position.
+ if (Input::get_singleton()->is_key_pressed(Key::ALT)) {
+ r_cylinder_position = initial_transform.get_origin();
+ } else {
+ Vector3 offset;
+ offset[axis] = (r_height - initial_height) * 0.5 * sign;
+ r_cylinder_position = initial_transform.xform(offset);
+ }
+ }
+}
+
+void Gizmo3DHelper::cylinder_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object, Object *p_radius_object, const StringName &p_position_property, const StringName &p_height_property, const StringName &p_radius_property) {
+ if (!p_height_object) {
+ p_height_object = p_position_object;
+ }
+ if (!p_radius_object) {
+ p_radius_object = p_position_object;
+ }
+
+ if (p_cancel) {
+ if (p_id == 0) {
+ p_radius_object->set(p_radius_property, initial_value);
+ } else {
+ p_height_object->set(p_height_property, initial_value);
+ }
+ p_position_object->set(p_position_property, initial_transform.get_origin());
+ return;
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(p_id == 0 ? p_radius_action_name : p_height_action_name);
+ if (p_id == 0) {
+ ur->add_do_property(p_radius_object, p_radius_property, p_radius_object->get(p_radius_property));
+ ur->add_undo_property(p_radius_object, p_radius_property, initial_value);
+ } else {
+ ur->add_do_property(p_height_object, p_height_property, p_height_object->get(p_height_property));
+ ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property));
+ ur->add_undo_property(p_height_object, p_height_property, initial_value);
+ ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin());
+ }
+ ur->commit_action();
+}
diff --git a/editor/plugins/gizmos/gizmo_3d_helper.h b/editor/plugins/gizmos/gizmo_3d_helper.h
index 387ea020b8..6d27e54770 100644
--- a/editor/plugins/gizmos/gizmo_3d_helper.h
+++ b/editor/plugins/gizmos/gizmo_3d_helper.h
@@ -50,6 +50,11 @@ public:
String box_get_handle_name(int p_id) const;
void box_set_handle(const Vector3 p_segment[2], int p_id, Vector3 &r_box_size, Vector3 &r_box_position);
void box_commit_handle(const String &p_action_name, bool p_cancel, Object *p_position_object, Object *p_size_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_size_property = "size");
+
+ Vector<Vector3> cylinder_get_handles(real_t p_height, real_t p_radius);
+ String cylinder_get_handle_name(int p_id) const;
+ void cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position);
+ void cylinder_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object = nullptr, Object *p_radius_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_height_property = "height", const StringName &p_radius_property = "radius");
};
#endif // GIZMO_3D_HELPER_H
diff --git a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
index 748f770d4d..007cc0636a 100644
--- a/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/lightmap_gi_gizmo_plugin.cpp
@@ -44,7 +44,10 @@ LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+ // Fade out probes when camera gets too close to them.
+ mat->set_distance_fade(StandardMaterial3D::DISTANCE_FADE_PIXEL_DITHER);
+ mat->set_distance_fade_min_distance(0.5);
+ mat->set_distance_fade_max_distance(1.5);
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 7e0331d15c..96a9dd7a80 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -261,6 +261,52 @@ Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const {
return syntax_highlighter;
}
+////
+
+void EditorMarkdownSyntaxHighlighter::_update_cache() {
+ highlighter->set_text_edit(text_edit);
+ highlighter->clear_keyword_colors();
+ highlighter->clear_member_keyword_colors();
+ highlighter->clear_color_regions();
+
+ // Disable automatic symbolic highlights, as these don't make sense for prose.
+ highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_member_variable_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+ highlighter->set_function_color(EDITOR_GET("text_editor/theme/highlighting/text_color"));
+
+ // Headings (any level).
+ const Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color");
+ highlighter->add_color_region("#", "", function_color);
+
+ // Bold.
+ highlighter->add_color_region("**", "**", function_color);
+ // `__bold__` syntax is not supported as color regions must begin with a symbol,
+ // not a character that is valid in an identifier.
+
+ // Code (both inline code and triple-backticks code blocks).
+ const Color code_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color");
+ highlighter->add_color_region("`", "`", code_color);
+
+ // Link (both references and inline links with URLs). The URL is not highlighted.
+ const Color link_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
+ highlighter->add_color_region("[", "]", link_color);
+
+ // Quote.
+ const Color quote_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ highlighter->add_color_region(">", "", quote_color, true);
+
+ // HTML comment, which is also supported in Markdown.
+ const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color");
+ highlighter->add_color_region("<!--", "-->", comment_color);
+}
+
+Ref<EditorSyntaxHighlighter> EditorMarkdownSyntaxHighlighter::_create() const {
+ Ref<EditorMarkdownSyntaxHighlighter> syntax_highlighter;
+ syntax_highlighter.instantiate();
+ return syntax_highlighter;
+}
+
////////////////////////////////////////////////////////////////////////////////
/*** SCRIPT EDITOR ****/
@@ -4414,6 +4460,10 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
json_syntax_highlighter.instantiate();
register_syntax_highlighter(json_syntax_highlighter);
+ Ref<EditorMarkdownSyntaxHighlighter> markdown_syntax_highlighter;
+ markdown_syntax_highlighter.instantiate();
+ register_syntax_highlighter(markdown_syntax_highlighter);
+
_update_online_doc();
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 8e82d60605..5de0aaa1e9 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -120,6 +120,24 @@ public:
EditorJSONSyntaxHighlighter() { highlighter.instantiate(); }
};
+class EditorMarkdownSyntaxHighlighter : public EditorSyntaxHighlighter {
+ GDCLASS(EditorMarkdownSyntaxHighlighter, EditorSyntaxHighlighter)
+
+private:
+ Ref<CodeHighlighter> highlighter;
+
+public:
+ virtual void _update_cache() override;
+ virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); }
+
+ virtual PackedStringArray _get_supported_languages() const override { return PackedStringArray{ "md", "markdown" }; }
+ virtual String _get_name() const override { return TTR("Markdown"); }
+
+ virtual Ref<EditorSyntaxHighlighter> _create() const override;
+
+ EditorMarkdownSyntaxHighlighter() { highlighter.instantiate(); }
+};
+
///////////////////////////////////////////////////////////////////////////////
class ScriptEditorQuickOpen : public ConfirmationDialog {
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 6b00a2c9c5..5166619f90 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -396,6 +396,7 @@ void ShaderEditorPlugin::_setup_popup_menu(PopupMenuType p_type, PopupMenu *p_me
if (p_type == FILE) {
p_menu->add_separator();
p_menu->add_item(TTR("Open File in Inspector"), FILE_INSPECT);
+ p_menu->add_item(TTR("Inspect Native Shader Code..."), FILE_INSPECT_NATIVE_SHADER_CODE);
p_menu->add_separator();
p_menu->add_shortcut(ED_SHORTCUT("shader_editor/close_file", TTR("Close File"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE);
} else {
@@ -554,6 +555,12 @@ void ShaderEditorPlugin::_menu_item_pressed(int p_index) {
EditorNode::get_singleton()->push_item(edited_shaders[index].shader_inc.ptr());
}
} break;
+ case FILE_INSPECT_NATIVE_SHADER_CODE: {
+ int index = shader_tabs->get_current_tab();
+ if (edited_shaders[index].shader.is_valid()) {
+ edited_shaders[index].shader->inspect_native_shader_code();
+ }
+ } break;
case FILE_CLOSE: {
_close_shader(shader_tabs->get_current_tab());
} break;
@@ -754,6 +761,7 @@ void ShaderEditorPlugin::_set_file_specific_items_disabled(bool p_disabled) {
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE_AS), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_INSPECT), p_disabled);
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_INSPECT_NATIVE_SHADER_CODE), p_disabled);
file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_CLOSE), p_disabled);
}
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index 43e6af79fa..19e43921c3 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -69,6 +69,7 @@ class ShaderEditorPlugin : public EditorPlugin {
FILE_SAVE,
FILE_SAVE_AS,
FILE_INSPECT,
+ FILE_INSPECT_NATIVE_SHADER_CODE,
FILE_CLOSE,
CLOSE_ALL,
CLOSE_OTHER_TABS,
diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp
index c7db243662..5333e10b56 100644
--- a/editor/plugins/sprite_2d_editor_plugin.cpp
+++ b/editor/plugins/sprite_2d_editor_plugin.cpp
@@ -593,12 +593,12 @@ Sprite2DEditor::Sprite2DEditor() {
add_child(err_dialog);
debug_uv_dialog = memnew(ConfirmationDialog);
+ debug_uv_dialog->set_size(Size2(960, 540) * EDSCALE);
VBoxContainer *vb = memnew(VBoxContainer);
debug_uv_dialog->add_child(vb);
debug_uv = memnew(Panel);
debug_uv->connect(SceneStringName(gui_input), callable_mp(this, &Sprite2DEditor::_debug_uv_input));
debug_uv->connect(SceneStringName(draw), callable_mp(this, &Sprite2DEditor::_debug_uv_draw));
- debug_uv->set_custom_minimum_size(Size2(800, 500) * EDSCALE);
debug_uv->set_clip_contents(true);
vb->add_margin_child(TTR("Preview:"), debug_uv, true);
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 37d5b787eb..168a3b3ac2 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -1986,6 +1986,7 @@ SpriteFramesEditor::SpriteFramesEditor() {
sub_vb->add_child(hfc);
playback_container = memnew(HBoxContainer);
+ playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);
hfc->add_child(playback_container);
play_bw_from = memnew(Button);
@@ -2013,7 +2014,7 @@ SpriteFramesEditor::SpriteFramesEditor() {
play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
playback_container->add_child(play_from);
- playback_container->add_child(memnew(VSeparator));
+ hfc->add_child(memnew(VSeparator));
autoplay->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_autoplay_pressed));
autoplay->set_toggle_mode(true);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index ede8351e41..a5df9edcf0 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -2128,12 +2128,11 @@ void VisualShaderEditor::_update_nodes() {
}
}
- Array keys = added.keys();
- keys.sort();
-
- for (int i = 0; i < keys.size(); i++) {
- const Variant &key = keys.get(i);
+ List<Variant> keys;
+ added.get_key_list(&keys);
+ keys.sort_custom<StringLikeVariantOrder>();
+ for (const Variant &key : keys) {
const Dictionary &value = (Dictionary)added[key];
add_custom_type(value["name"], value["type"], value["script"], value["description"], value["return_icon_type"], value["category"], value["highend"]);
diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp
index 541ab01e62..39c1a78c4a 100644
--- a/editor/project_manager/project_list.cpp
+++ b/editor/project_manager/project_list.cpp
@@ -88,7 +88,7 @@ void ProjectListItemControl::_notification(int p_what) {
draw_style_box(get_theme_stylebox(SNAME("selected"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
if (is_hovering) {
- draw_style_box(get_theme_stylebox(SNAME("hover"), SNAME("Tree")), Rect2(Point2(), get_size()));
+ draw_style_box(get_theme_stylebox(SNAME("hovered"), SNAME("Tree")), Rect2(Point2(), get_size()));
}
draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree")));
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index bcab0c2883..0f359a3af8 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -156,11 +156,12 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
}
if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) {
- // Prevent renaming if a button is focused
- // to avoid conflict with Enter shortcut on macOS
- if (!focus_owner || !Object::cast_to<BaseButton>(focus_owner)) {
- _tool_selected(TOOL_RENAME);
+ // Prevent renaming if a button or a range is focused
+ // to avoid conflict with Enter shortcut on macOS.
+ if (focus_owner && (Object::cast_to<BaseButton>(focus_owner) || Object::cast_to<Range>(focus_owner))) {
+ return;
}
+ _tool_selected(TOOL_RENAME);
#ifdef MODULE_REGEX_ENABLED
} else if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) {
_tool_selected(TOOL_BATCH_RENAME);
@@ -1667,6 +1668,7 @@ void SceneTreeDock::_notification(int p_what) {
button_instance->set_icon(get_editor_theme_icon(SNAME("Instance")));
button_create_script->set_icon(get_editor_theme_icon(SNAME("ScriptCreate")));
button_detach_script->set_icon(get_editor_theme_icon(SNAME("ScriptRemove")));
+ button_extend_script->set_icon(get_editor_theme_icon(SNAME("ScriptExtend")));
button_tree_menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
@@ -2784,33 +2786,49 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
void SceneTreeDock::_update_script_button() {
- if (!profile_allow_script_editing) {
- button_create_script->hide();
- button_detach_script->hide();
- } else if (editor_selection->get_selection().size() == 0) {
- button_create_script->hide();
- button_detach_script->hide();
- } else if (editor_selection->get_selection().size() == 1) {
- Node *n = editor_selection->get_selected_node_list().front()->get();
- if (n->get_script().is_null()) {
- button_create_script->show();
- button_detach_script->hide();
- } else {
- button_create_script->hide();
- button_detach_script->show();
- }
- } else {
- button_create_script->hide();
+ bool can_create_script = false;
+ bool can_detach_script = false;
+ bool can_extend_script = false;
+
+ if (profile_allow_script_editing) {
Array selection = editor_selection->get_selected_nodes();
+
for (int i = 0; i < selection.size(); i++) {
Node *n = Object::cast_to<Node>(selection[i]);
- if (!n->get_script().is_null()) {
- button_detach_script->show();
- return;
+ Ref<Script> s = n->get_script();
+ Ref<Script> cts;
+
+ if (n->has_meta(SceneStringName(_custom_type_script))) {
+ cts = n->get_meta(SceneStringName(_custom_type_script));
+ }
+
+ if (selection.size() == 1) {
+ if (s.is_valid()) {
+ if (cts.is_valid() && s == cts) {
+ can_extend_script = true;
+ }
+ } else {
+ can_create_script = true;
+ }
+ }
+
+ if (s.is_valid()) {
+ if (cts.is_valid()) {
+ if (s != cts) {
+ can_detach_script = true;
+ break;
+ }
+ } else {
+ can_detach_script = true;
+ break;
+ }
}
}
- button_detach_script->hide();
}
+
+ button_create_script->set_visible(can_create_script);
+ button_detach_script->set_visible(can_detach_script);
+ button_extend_script->set_visible(can_extend_script);
}
void SceneTreeDock::_selection_changed() {
@@ -3057,7 +3075,28 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro
Node *newnode = p_by_node;
if (p_keep_properties) {
- Node *default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(oldnode->get_class()));
+ Node *default_oldnode = nullptr;
+
+ // If we're dealing with a custom node type, we need to create a default instance of the custom type instead of the native type for property comparison.
+ if (oldnode->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> cts = oldnode->get_meta(SceneStringName(_custom_type_script));
+ default_oldnode = Object::cast_to<Node>(get_editor_data()->script_class_instance(cts->get_global_name()));
+ if (default_oldnode) {
+ default_oldnode->set_name(cts->get_global_name());
+ get_editor_data()->instantiate_object_properties(default_oldnode);
+ } else {
+ // Legacy custom type, registered with "add_custom_type()".
+ // TODO: Should probably be deprecated in 4.x.
+ const EditorData::CustomType *custom_type = get_editor_data()->get_custom_type_by_path(cts->get_path());
+ if (custom_type) {
+ default_oldnode = Object::cast_to<Node>(get_editor_data()->instantiate_custom_type(custom_type->name, cts->get_instance_base_type()));
+ }
+ }
+ }
+
+ if (!default_oldnode) {
+ default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(oldnode->get_class()));
+ }
List<PropertyInfo> pinfo;
oldnode->get_property_list(&pinfo);
@@ -3542,6 +3581,27 @@ void SceneTreeDock::_script_dropped(const String &p_file, NodePath p_to) {
undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(n)).path_join(new_node->get_name())));
undo_redo->commit_action();
} else {
+ // Check if dropped script is compatible.
+ if (n->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> ct_scr = n->get_meta(SceneStringName(_custom_type_script));
+ if (!scr->inherits_script(ct_scr)) {
+ String custom_type_name = ct_scr->get_global_name();
+
+ // Legacy custom type, registered with "add_custom_type()".
+ if (custom_type_name.is_empty()) {
+ const EditorData::CustomType *custom_type = get_editor_data()->get_custom_type_by_path(ct_scr->get_path());
+ if (custom_type) {
+ custom_type_name = custom_type->name;
+ } else {
+ custom_type_name = TTR("<unknown>");
+ }
+ }
+
+ WARN_PRINT_ED(vformat("Script does not extend type: '%s'.", custom_type_name));
+ return;
+ }
+ }
+
undo_redo->create_action(TTR("Attach Script"), UndoRedo::MERGE_DISABLE, n);
undo_redo->add_do_method(InspectorDock::get_singleton(), "store_script_properties", n);
undo_redo->add_undo_method(InspectorDock::get_singleton(), "store_script_properties", n);
@@ -3649,6 +3709,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
Ref<Script> existing_script;
bool existing_script_removable = true;
+ bool allow_attach_new_script = true;
if (selection.size() == 1) {
Node *selected = selection.front()->get();
@@ -3672,6 +3733,10 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script) {
existing_script_removable = false;
}
+
+ if (selected->has_meta(SceneStringName(_custom_type_script))) {
+ allow_attach_new_script = false;
+ }
}
if (profile_allow_editing) {
@@ -3692,7 +3757,10 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (full_selection.size() == 1) {
add_separator = true;
- menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptCreate")), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
+ if (allow_attach_new_script) {
+ menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptCreate")), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
+ }
+
if (existing_script.is_valid()) {
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ScriptExtend")), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT);
}
@@ -4601,6 +4669,14 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
filter_hbc->add_child(button_detach_script);
button_detach_script->hide();
+ button_extend_script = memnew(Button);
+ button_extend_script->set_flat(true);
+ button_extend_script->connect(SceneStringName(pressed), callable_mp(this, &SceneTreeDock::_tool_selected).bind(TOOL_EXTEND_SCRIPT, false));
+ button_extend_script->set_tooltip_text(TTR("Extend the script of the selected node."));
+ button_extend_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/extend_script"));
+ filter_hbc->add_child(button_extend_script);
+ button_extend_script->hide();
+
button_tree_menu = memnew(MenuButton);
button_tree_menu->set_flat(false);
button_tree_menu->set_theme_type_variation("FlatMenuButton");
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 05ad0f36e4..8cee2870f6 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -115,6 +115,7 @@ class SceneTreeDock : public VBoxContainer {
Button *button_instance = nullptr;
Button *button_create_script = nullptr;
Button *button_detach_script = nullptr;
+ Button *button_extend_script = nullptr;
MenuButton *button_tree_menu = nullptr;
Button *node_shortcuts_toggle = nullptr;
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 17bcbacfc2..4db43f0703 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -633,6 +633,16 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme,
// in 4.0, and even if it was, it may not always work in practice (e.g. running with compositing disabled).
p_config.popup_style->set_corner_radius_all(0);
+ p_config.popup_border_style = p_config.popup_style->duplicate();
+ p_config.popup_border_style->set_content_margin_all(MAX(Math::round(EDSCALE), p_config.border_width) + 2 + (p_config.base_margin * 1.5) * EDSCALE);
+ // Always display a border for popups like PopupMenus so they can be distinguished from their background.
+ p_config.popup_border_style->set_border_width_all(MAX(Math::round(EDSCALE), p_config.border_width));
+ if (p_config.draw_extra_borders) {
+ p_config.popup_border_style->set_border_color(p_config.extra_border_color_2);
+ } else {
+ p_config.popup_border_style->set_border_color(p_config.dark_color_2);
+ }
+
p_config.window_style = p_config.popup_style->duplicate();
p_config.window_style->set_border_color(p_config.base_color);
p_config.window_style->set_border_width(SIDE_TOP, 24 * EDSCALE);
@@ -707,7 +717,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
}
// PopupPanel
- p_theme->set_stylebox(SceneStringName(panel), "PopupPanel", p_config.popup_style);
+ p_theme->set_stylebox(SceneStringName(panel), "PopupPanel", p_config.popup_border_style);
}
// Buttons.
@@ -945,6 +955,8 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
p_theme->set_color("custom_button_font_highlight", "Tree", p_config.font_hover_color);
p_theme->set_color(SceneStringName(font_color), "Tree", p_config.font_color);
+ p_theme->set_color("font_hovered_color", "Tree", p_config.mono_color);
+ p_theme->set_color("font_hovered_dimmed_color", "Tree", p_config.font_color);
p_theme->set_color("font_selected_color", "Tree", p_config.mono_color);
p_theme->set_color("font_disabled_color", "Tree", p_config.font_disabled_color);
p_theme->set_color("font_outline_color", "Tree", p_config.font_outline_color);
@@ -997,7 +1009,13 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
Ref<StyleBoxFlat> style_tree_hover = p_config.base_style->duplicate();
style_tree_hover->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.4));
style_tree_hover->set_border_width_all(0);
- p_theme->set_stylebox("hover", "Tree", style_tree_hover);
+ p_theme->set_stylebox("hovered", "Tree", style_tree_hover);
+ p_theme->set_stylebox("button_hover", "Tree", style_tree_hover);
+
+ Ref<StyleBoxFlat> style_tree_hover_dimmed = p_config.base_style->duplicate();
+ style_tree_hover_dimmed->set_bg_color(p_config.highlight_color * Color(1, 1, 1, 0.2));
+ style_tree_hover_dimmed->set_border_width_all(0);
+ p_theme->set_stylebox("hovered_dimmed", "Tree", style_tree_hover_dimmed);
p_theme->set_stylebox("selected_focus", "Tree", style_tree_focus);
p_theme->set_stylebox("selected", "Tree", style_tree_selected);
@@ -1306,18 +1324,11 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// PopupMenu.
{
- Ref<StyleBoxFlat> style_popup_menu = p_config.popup_style->duplicate();
+ Ref<StyleBoxFlat> style_popup_menu = p_config.popup_border_style->duplicate();
// Use 1 pixel for the sides, since if 0 is used, the highlight of hovered items is drawn
// on top of the popup border. This causes a 'gap' in the panel border when an item is highlighted,
// and it looks weird. 1px solves this.
- style_popup_menu->set_content_margin_individual(EDSCALE, 2 * EDSCALE, EDSCALE, 2 * EDSCALE);
- // Always display a border for PopupMenus so they can be distinguished from their background.
- style_popup_menu->set_border_width_all(EDSCALE);
- if (p_config.draw_extra_borders) {
- style_popup_menu->set_border_color(p_config.extra_border_color_2);
- } else {
- style_popup_menu->set_border_color(p_config.dark_color_2);
- }
+ style_popup_menu->set_content_margin_individual(Math::round(EDSCALE), 2 * EDSCALE, Math::round(EDSCALE), 2 * EDSCALE);
p_theme->set_stylebox(SceneStringName(panel), "PopupMenu", style_popup_menu);
Ref<StyleBoxFlat> style_menu_hover = p_config.button_style_hover->duplicate();
@@ -1327,17 +1338,17 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
Ref<StyleBoxLine> style_popup_separator(memnew(StyleBoxLine));
style_popup_separator->set_color(p_config.separator_color);
- style_popup_separator->set_grow_begin(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
- style_popup_separator->set_grow_end(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_separator->set_grow_begin(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_separator->set_grow_end(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_separator->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
Ref<StyleBoxLine> style_popup_labeled_separator_left(memnew(StyleBoxLine));
- style_popup_labeled_separator_left->set_grow_begin(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_labeled_separator_left->set_grow_begin(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_labeled_separator_left->set_color(p_config.separator_color);
style_popup_labeled_separator_left->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
Ref<StyleBoxLine> style_popup_labeled_separator_right(memnew(StyleBoxLine));
- style_popup_labeled_separator_right->set_grow_end(p_config.popup_margin - MAX(Math::round(EDSCALE), p_config.border_width));
+ style_popup_labeled_separator_right->set_grow_end(Math::round(EDSCALE) - MAX(Math::round(EDSCALE), p_config.border_width));
style_popup_labeled_separator_right->set_color(p_config.separator_color);
style_popup_labeled_separator_right->set_thickness(MAX(Math::round(EDSCALE), p_config.border_width));
@@ -2115,21 +2126,6 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
// EditorValidationPanel.
p_theme->set_stylebox(SceneStringName(panel), "EditorValidationPanel", p_config.tree_panel_style);
-
- // ControlEditor.
- {
- p_theme->set_type_variation("ControlEditorPopupPanel", "PopupPanel");
-
- Ref<StyleBoxFlat> control_editor_popup_style = p_config.popup_style->duplicate();
- control_editor_popup_style->set_shadow_size(0);
- control_editor_popup_style->set_content_margin(SIDE_LEFT, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_TOP, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_RIGHT, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_content_margin(SIDE_BOTTOM, p_config.base_margin * EDSCALE);
- control_editor_popup_style->set_border_width_all(0);
-
- p_theme->set_stylebox(SceneStringName(panel), "ControlEditorPopupPanel", control_editor_popup_style);
- }
}
// Editor inspector.
diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h
index 5e7bd00083..ca5e1a4e2d 100644
--- a/editor/themes/editor_theme_manager.h
+++ b/editor/themes/editor_theme_manager.h
@@ -135,6 +135,7 @@ class EditorThemeManager {
Ref<StyleBoxFlat> button_style_hover;
Ref<StyleBoxFlat> popup_style;
+ Ref<StyleBoxFlat> popup_border_style;
Ref<StyleBoxFlat> window_style;
Ref<StyleBoxFlat> dialog_style;
Ref<StyleBoxFlat> panel_container_style;
diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp
index 9496ba016c..7f61623980 100644
--- a/editor/window_wrapper.cpp
+++ b/editor/window_wrapper.cpp
@@ -391,7 +391,6 @@ void ScreenSelect::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
set_icon(get_editor_theme_icon("MakeFloating"));
- popup_background->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("PanelForeground", EditorStringName(EditorStyles)));
const real_t popup_height = real_t(get_theme_font_size(SceneStringName(font_size))) * 2.0;
popup->set_min_size(Size2(0, popup_height * 3));
@@ -454,14 +453,10 @@ ScreenSelect::ScreenSelect() {
// Create the popup.
const Size2 borders = Size2(4, 4) * EDSCALE;
- popup = memnew(Popup);
+ popup = memnew(PopupPanel);
popup->connect("popup_hide", callable_mp(static_cast<BaseButton *>(this), &ScreenSelect::set_pressed).bind(false));
add_child(popup);
- popup_background = memnew(Panel);
- popup_background->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
- popup->add_child(popup_background);
-
MarginContainer *popup_root = memnew(MarginContainer);
popup_root->add_theme_constant_override("margin_right", borders.width);
popup_root->add_theme_constant_override("margin_top", borders.height);
diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h
index a07e95f09e..3597276de9 100644
--- a/editor/window_wrapper.h
+++ b/editor/window_wrapper.h
@@ -88,7 +88,6 @@ class ScreenSelect : public Button {
GDCLASS(ScreenSelect, Button);
Popup *popup = nullptr;
- Panel *popup_background = nullptr;
HBoxContainer *screen_list = nullptr;
void _build_advanced_menu();
diff --git a/main/main.cpp b/main/main.cpp
index 5206e9b84c..e8086db9d3 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -42,6 +42,7 @@
#include "core/io/dir_access.h"
#include "core/io/file_access_pack.h"
#include "core/io/file_access_zip.h"
+#include "core/io/image.h"
#include "core/io/image_loader.h"
#include "core/io/ip.h"
#include "core/io/resource_loader.h"
@@ -629,6 +630,7 @@ void Main::print_help(const char *p_binary) {
print_help_option("--generate-spirv-debug-info", "Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n");
#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
print_help_option("--extra-gpu-memory-tracking", "Enables additional memory tracking (see class reference for `RenderingDevice.get_driver_and_device_memory_report()` and linked methods). Currently only implemented for Vulkan. Enabling this feature may cause crashes on some systems due to buggy drivers or bugs in the Vulkan Loader. See https://github.com/godotengine/godot/issues/95967\n");
+ print_help_option("--accurate-breadcrumbs", "Force barriers between breadcrumbs. Useful for narrowing down a command causing GPU resets. Currently only implemented for Vulkan.\n");
#endif
print_help_option("--remote-debug <uri>", "Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n");
print_help_option("--single-threaded-scene", "Force scene tree to run in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n");
@@ -1235,8 +1237,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
#endif
} else if (arg == "--generate-spirv-debug-info") {
Engine::singleton->generate_spirv_debug_info = true;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
} else if (arg == "--extra-gpu-memory-tracking") {
Engine::singleton->extra_gpu_memory_tracking = true;
+ } else if (arg == "--accurate-breadcrumbs") {
+ Engine::singleton->accurate_breadcrumbs = true;
+#endif
} else if (arg == "--tablet-driver") {
if (N) {
tablet_driver = N->get();
@@ -2412,6 +2418,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (bool(GLOBAL_GET("display/window/size/no_focus"))) {
window_flags |= DisplayServer::WINDOW_FLAG_NO_FOCUS_BIT;
}
+ if (bool(GLOBAL_GET("display/window/size/sharp_corners"))) {
+ window_flags |= DisplayServer::WINDOW_FLAG_SHARP_CORNERS_BIT;
+ }
window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int());
int initial_position_type = GLOBAL_GET("display/window/size/initial_position_type").operator int();
if (initial_position_type == 0) { // Absolute.
@@ -2495,7 +2504,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_RST_NOVAL("audio/driver/driver", AudioDriverManager::get_driver(0)->get_name());
if (audio_driver.is_empty()) { // Specified in project.godot.
- audio_driver = GLOBAL_GET("audio/driver/driver");
+ if (project_manager) {
+ // The project manager doesn't need to play sound (TTS audio output is not emitted by Godot, but by the system itself).
+ // Disable audio output so it doesn't appear in the list of applications outputting sound in the OS.
+ // On macOS, this also prevents the project manager from inhibiting suspend.
+ audio_driver = "Dummy";
+ } else {
+ audio_driver = GLOBAL_GET("audio/driver/driver");
+ }
}
// Make sure that dummy is the last one, which it is assumed to be by design.
@@ -3197,6 +3213,10 @@ Error Main::setup2(bool p_show_boot_logo) {
}
id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true)));
+
+ if (editor) {
+ id->set_emulate_mouse_from_touch(true);
+ }
}
OS::get_singleton()->benchmark_end_measure("Startup", "Setup Window and Boot");
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index d48ea363a7..8ca5dba225 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -30,6 +30,7 @@
#include "image_compress_basisu.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "servers/rendering_server.h"
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 6bfe01f65c..1ad9bed721 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -37,11 +37,16 @@
#include "bc1.glsl.gen.h"
#include "bc4.glsl.gen.h"
#include "bc6h.glsl.gen.h"
+#include "servers/display_server.h"
static Mutex betsy_mutex;
static BetsyCompressor *betsy = nullptr;
void BetsyCompressor::_init() {
+ if (!DisplayServer::can_create_rendering_device()) {
+ return;
+ }
+
// Create local RD.
RenderingContextDriver *rcd = nullptr;
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
@@ -182,6 +187,11 @@ static String get_shader_name(BetsyFormat p_format) {
Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+ // Return an error so that the compression can fall back to cpu compression
+ if (compress_rd == nullptr) {
+ return ERR_CANT_CREATE;
+ }
+
if (r_img->is_compressed()) {
return ERR_INVALID_DATA;
}
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 95ffeed6c3..e5d33dd179 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -278,24 +278,13 @@ void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_i
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- Vector3 axis;
- axis[p_id == 0 ? 0 : 1] = 1.0;
- Vector3 ra, rb;
- Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
- float d = axis.dot(ra);
- if (Node3DEditor::get_singleton()->is_snap_enabled()) {
- d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
- }
-
- if (d < 0.001) {
- d = 0.001;
- }
-
- if (p_id == 0) {
- s->set_radius(d);
- } else if (p_id == 1) {
- s->set_height(d * 2.0);
- }
+ real_t height = s->get_height();
+ real_t radius = s->get_radius();
+ Vector3 position;
+ helper->cylinder_set_handle(sg, p_id, height, radius, position);
+ s->set_height(height);
+ s->set_radius(radius);
+ s->set_global_position(position);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
@@ -340,32 +329,11 @@ void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int
}
if (Object::cast_to<CSGBox3D>(cs)) {
- helper->box_commit_handle(TTR("Change Box Shape Size"), p_cancel, cs);
+ helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
}
if (Object::cast_to<CSGCylinder3D>(cs)) {
- CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- if (p_cancel) {
- if (p_id == 0) {
- s->set_radius(p_restore);
- } else {
- s->set_height(p_restore);
- }
- return;
- }
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- if (p_id == 0) {
- ur->create_action(TTR("Change Cylinder Radius"));
- ur->add_do_method(s, "set_radius", s->get_radius());
- ur->add_undo_method(s, "set_radius", p_restore);
- } else {
- ur->create_action(TTR("Change Cylinder Height"));
- ur->add_do_method(s, "set_height", s->get_height());
- ur->add_undo_method(s, "set_height", p_restore);
- }
-
- ur->commit_action();
+ helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs);
}
if (Object::cast_to<CSGTorus3D>(cs)) {
@@ -506,9 +474,7 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<CSGCylinder3D>(cs)) {
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
- Vector<Vector3> handles;
- handles.push_back(Vector3(s->get_radius(), 0, 0));
- handles.push_back(Vector3(0, s->get_height() * 0.5, 0));
+ Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
p_gizmo->add_handles(handles, handles_material);
}
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 5fe47d69df..ede4ce6617 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -4,8 +4,8 @@
Built-in GDScript constants, functions, and annotations.
</brief_description>
<description>
- A list of GDScript-specific utility functions and annotations accessible from any script.
- For the list of the global functions and constants see [@GlobalScope].
+ A list of utility functions and annotations accessible from any script written in GDScript.
+ For the list of global functions and constants that can be accessed in any scripting language, see [@GlobalScope].
</description>
<tutorials>
<link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link>
@@ -61,7 +61,7 @@
<method name="convert" deprecated="Use [method @GlobalScope.type_convert] instead.">
<return type="Variant" />
<param index="0" name="what" type="Variant" />
- <param index="1" name="type" type="int" />
+ <param index="1" name="type" type="int" enum="Variant.Type" />
<description>
Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values.
[codeblock]
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp
index 32ef429b0d..758887a723 100644
--- a/modules/gdscript/editor/gdscript_docgen.cpp
+++ b/modules/gdscript/editor/gdscript_docgen.cpp
@@ -217,7 +217,7 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
List<Variant> keys;
dict.get_key_list(&keys);
- keys.sort();
+ keys.sort_custom<StringLikeVariantOrder>();
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index d765cfa1ea..0b12f2ff76 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -701,7 +701,9 @@ void GDScriptSyntaxHighlighter::_update_cache() {
List<StringName> types;
ClassDB::get_class_list(&types);
for (const StringName &E : types) {
- class_names[E] = types_color;
+ if (ClassDB::is_class_exposed(E)) {
+ class_names[E] = types_color;
+ }
}
/* User types. */
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7b9aa70686..aab9a5acf1 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -76,9 +76,16 @@ bool GDScriptNativeClass::_get(const StringName &p_name, Variant &r_ret) const {
if (ok) {
r_ret = v;
return true;
- } else {
- return false;
}
+
+ MethodBind *method = ClassDB::get_method(name, p_name);
+ if (method && method->is_static()) {
+ // Native static method.
+ r_ret = Callable(this, p_name);
+ return true;
+ }
+
+ return false;
}
void GDScriptNativeClass::_bind_methods() {
@@ -693,10 +700,16 @@ void GDScript::_static_default_init() {
continue;
}
if (type.builtin_type == Variant::ARRAY && type.has_container_element_type(0)) {
+ const GDScriptDataType element_type = type.get_container_element_type(0);
Array default_value;
- const GDScriptDataType &element_type = type.get_container_element_type(0);
default_value.set_typed(element_type.builtin_type, element_type.native_type, element_type.script_type);
static_variables.write[E.value.index] = default_value;
+ } else if (type.builtin_type == Variant::DICTIONARY && type.has_container_element_types()) {
+ const GDScriptDataType key_type = type.get_container_element_type_or_variant(0);
+ const GDScriptDataType value_type = type.get_container_element_type_or_variant(1);
+ Dictionary default_value;
+ default_value.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
+ static_variables.write[E.value.index] = default_value;
} else {
Variant default_value;
Callable::CallError err;
@@ -1062,6 +1075,26 @@ void GDScript::_bind_methods() {
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
}
+void GDScript::set_path_cache(const String &p_path) {
+ if (ResourceCache::has(p_path)) {
+ set_path(p_path, true);
+ return;
+ }
+
+ if (is_root_script()) {
+ Script::set_path_cache(p_path);
+ }
+
+ String old_path = path;
+ path = p_path;
+ path_valid = true;
+ GDScriptCache::move_script(old_path, p_path);
+
+ for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) {
+ kv.value->set_path_cache(p_path);
+ }
+}
+
void GDScript::set_path(const String &p_path, bool p_take_over) {
if (is_root_script()) {
Script::set_path(p_path, p_take_over);
@@ -2540,11 +2573,11 @@ void GDScriptLanguage::reload_all_scripts() {
}
}
}
-#endif
+#endif // TOOLS_ENABLED
}
reload_scripts(scripts, true);
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) {
@@ -2614,7 +2647,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
}
}
-#endif
+#endif // TOOLS_ENABLED
for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : scr->pending_reload_state) {
map[F.key] = F.value; //pending to reload, use this one instead
@@ -2682,7 +2715,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
//if instance states were saved, set them!
}
-#endif
+#endif // DEBUG_ENABLED
}
void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 9bb39aac0f..006a09debb 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -300,6 +300,7 @@ public:
virtual Error reload(bool p_keep_state = false) override;
+ virtual void set_path_cache(const String &p_path) override;
virtual void set_path(const String &p_path, bool p_take_over = false) override;
String get_script_path() const;
Error load_source_code(const String &p_path);
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 4a3a3a4b61..93d4a512a9 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -940,8 +940,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
Finally finally([&]() {
ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source);
GDScriptParser::DataType member_type = member.get_datatype();
- if (member_type.has_container_element_type(0)) {
- ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source);
+ for (int i = 0; i < member_type.get_container_element_type_count(); ++i) {
+ ensure_cached_external_parser_for_class(member_type.get_container_element_type(i).class_type, p_class, "Trying to resolve datatype of class member", p_source);
}
});
@@ -3816,6 +3816,12 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
}
Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) {
+ // Delicate piece of code that intentionally doesn't use the GDScript cache or `get_depended_parser_for`.
+ // Search dependencies for the parser that owns `p_class` and make a cache entry for it.
+ // Required for how we store pointers to classes owned by other parser trees and need to call `resolve_class_member` and such on the same parser tree.
+ // Since https://github.com/godotengine/godot/pull/94871 there can technically be multiple parsers for the same script in the same parser tree.
+ // Even if unlikely, getting the wrong parser could lead to strange undefined behavior without errors.
+
if (p_class == nullptr) {
return nullptr;
}
@@ -3832,8 +3838,6 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class
p_from_class = parser->head;
}
- String script_path = p_class->get_datatype().script_path;
-
Ref<GDScriptParserRef> parser_ref;
for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) {
if (parser->has_class(look_class)) {
@@ -5867,7 +5871,7 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
parent = ClassDB::get_parent_class(parent);
}
}
-#endif
+#endif // DEBUG_ENABLED
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source) {
// Unary version.
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index 3c022412bd..fa22798edf 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -312,7 +312,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
Ref<GDScript> script;
script.instantiate();
- script->set_path(p_path, true);
+ script->set_path_cache(p_path);
if (remapped_path.get_extension().to_lower() == "gdc") {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
@@ -360,6 +360,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
return script;
}
}
+ script->set_path(p_path, true);
const String remapped_path = ResourceLoader::path_remap(p_path);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3de1decc18..951ae6ce99 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3164,7 +3164,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
HashMap<String, ScriptLanguage::CodeCompletionOption> options;
GDScriptParser::CompletionContext completion_context = parser.get_completion_context();
- completion_context.base = p_owner;
+ if (completion_context.current_class != nullptr && completion_context.current_class->outer == nullptr) {
+ completion_context.base = p_owner;
+ }
bool is_function = false;
switch (completion_context.type) {
@@ -3534,13 +3536,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return OK;
}
-#else
+#else // !TOOLS_ENABLED
Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
-#endif
+#endif // TOOLS_ENABLED
//////// END COMPLETION //////////
@@ -4125,4 +4127,4 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-#endif
+#endif // TOOLS_ENABLED
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 111a39d730..ee8d53639c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -244,7 +244,7 @@ void GDScriptParser::apply_pending_warnings() {
pending_warnings.clear();
}
-#endif
+#endif // DEBUG_ENABLED
void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
if (!for_completion) {
@@ -1624,15 +1624,17 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- annotation->info = &valid_annotations[annotation->name];
+ if (valid) {
+ annotation->info = &valid_annotations[annotation->name];
- if (!annotation->applies_to(p_valid_targets)) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
- } else {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (!annotation->applies_to(p_valid_targets)) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
+ valid = false;
}
- valid = false;
}
if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 7f64ae902b..2ec33831a2 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -165,6 +165,10 @@ public:
container_element_types.write[p_index] = DataType(p_type);
}
+ _FORCE_INLINE_ int get_container_element_type_count() const {
+ return container_element_types.size();
+ }
+
_FORCE_INLINE_ DataType get_container_element_type(int p_index) const {
ERR_FAIL_INDEX_V(p_index, container_element_types.size(), get_variant_type());
return container_element_types[p_index];
diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp
index 59dd983ed2..8246069696 100644
--- a/modules/gdscript/gdscript_utility_functions.cpp
+++ b/modules/gdscript/gdscript_utility_functions.cpp
@@ -34,7 +34,6 @@
#include "core/io/resource_loader.h"
#include "core/object/class_db.h"
-#include "core/object/method_bind.h"
#include "core/object/object.h"
#include "core/templates/oa_hash_map.h"
#include "core/templates/vector.h"
@@ -42,101 +41,105 @@
#ifdef DEBUG_ENABLED
-#define VALIDATE_ARG_COUNT(m_count) \
- if (p_arg_count < m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
- r_error.expected = m_count; \
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count) \
+ if (unlikely(p_arg_count < m_min_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \
+ r_error.expected = m_min_count; \
return; \
} \
- if (p_arg_count > m_count) { \
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
- r_error.expected = m_count; \
+ if (unlikely(p_arg_count > m_max_count)) { \
*r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \
+ r_error.expected = m_max_count; \
return; \
}
-#define VALIDATE_ARG_INT(m_arg) \
- if (p_args[m_arg]->get_type() != Variant::INT) { \
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type) \
+ if (unlikely(!Variant::can_convert_strict(p_args[m_arg]->get_type(), m_type))) { \
+ *r_ret = Variant(); \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
+ r_error.argument = m_arg; \
+ r_error.expected = m_type; \
+ return; \
+ }
+
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::INT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#define VALIDATE_ARG_NUM(m_arg) \
- if (!p_args[m_arg]->is_num()) { \
+#else // !DEBUG_ENABLED
+
+#define DEBUG_VALIDATE_ARG_COUNT(m_min_count, m_max_count)
+#define DEBUG_VALIDATE_ARG_TYPE(m_arg, m_type)
+#define DEBUG_VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg)
+
+#endif // DEBUG_ENABLED
+
+#define VALIDATE_ARG_CUSTOM(m_arg, m_type, m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; \
r_error.argument = m_arg; \
- r_error.expected = Variant::FLOAT; \
- *r_ret = Variant(); \
+ r_error.expected = m_type; \
return; \
}
-#else
-
-#define VALIDATE_ARG_COUNT(m_count)
-#define VALIDATE_ARG_INT(m_arg)
-#define VALIDATE_ARG_NUM(m_arg)
-
-#endif
+#define GDFUNC_FAIL_COND_MSG(m_cond, m_msg) \
+ if (unlikely(m_cond)) { \
+ *r_ret = m_msg; \
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; \
+ return; \
+ }
struct GDScriptUtilityFunctionsDefinitions {
#ifndef DISABLE_DEPRECATED
static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
- VALIDATE_ARG_INT(1);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+
int type = *p_args[1];
- if (type < 0 || type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument to convert(), use TYPE_* constants.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::INT;
- return;
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::INT, type < 0 || type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument to convert(), use TYPE_* constants."));
- } else {
- Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
- if (r_error.error != Callable::CallError::CALL_OK) {
- *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type)));
- }
- }
+ Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error);
}
#endif // DISABLE_DEPRECATED
static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING_NAME);
*r_ret = ClassDB::class_exists(*p_args[0]);
}
static inline void _char(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- VALIDATE_ARG_INT(0);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
char32_t result[2] = { *p_args[0], 0 };
*r_ret = String(result);
}
static inline void range(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
+ DEBUG_VALIDATE_ARG_COUNT(1, 3);
switch (p_arg_count) {
- case 0: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 1;
- *r_ret = Variant();
- } break;
case 1: {
- VALIDATE_ARG_NUM(0);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+
int count = *p_args[0];
+
Array arr;
if (count <= 0) {
*r_ret = arr;
return;
}
+
Error err = arr.resize(count);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
for (int i = 0; i < count; i++) {
arr[i] = i;
@@ -145,8 +148,8 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
case 2: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
@@ -156,30 +159,26 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
return;
}
+
Error err = arr.resize(to - from);
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
+
for (int i = from; i < to; i++) {
arr[i - from] = i;
}
+
*r_ret = arr;
} break;
case 3: {
- VALIDATE_ARG_NUM(0);
- VALIDATE_ARG_NUM(1);
- VALIDATE_ARG_NUM(2);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
int from = *p_args[0];
int to = *p_args[1];
int incr = *p_args[2];
- if (incr == 0) {
- *r_ret = RTR("Step argument is zero!");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+
+ VALIDATE_ARG_CUSTOM(2, Variant::INT, incr == 0, RTR("Step argument is zero!"));
Array arr;
if (from >= to && incr > 0) {
@@ -200,12 +199,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
Error err = arr.resize(count);
-
- if (err != OK) {
- *r_ret = RTR("Cannot resize array.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return;
- }
+ GDFUNC_FAIL_COND_MSG(err != OK, RTR("Cannot resize array."));
if (incr > 0) {
int idx = 0;
@@ -221,138 +215,79 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = arr;
} break;
- default: {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
-
- } break;
}
}
static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
- if (!p_args[0]->is_string()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::STRING;
- *r_ret = Variant();
- } else {
- *r_ret = ResourceLoader::load(*p_args[0]);
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::STRING);
+ *r_ret = ResourceLoader::load(*p_args[0]);
}
static inline void inst_to_dict(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::OBJECT);
if (p_args[0]->get_type() == Variant::NIL) {
*r_ret = Variant();
- } else if (p_args[0]->get_type() != Variant::OBJECT) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
+ return;
+ }
+
+ Object *obj = *p_args[0];
+ if (!obj) {
*r_ret = Variant();
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- *r_ret = Variant();
+ return;
+ }
- } else if (!obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not a script with an instance");
- return;
- } else {
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(obj->get_script_instance());
- Ref<GDScript> base = ins->get_script();
- if (base.is_null()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = RTR("Not based on a script");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT,
+ !obj->get_script_instance() || obj->get_script_instance()->get_language() != GDScriptLanguage::get_singleton(),
+ RTR("Not a script with an instance."));
- GDScript *p = base.ptr();
- String path = p->get_script_path();
- Vector<StringName> sname;
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(obj->get_script_instance());
- while (p->_owner) {
- sname.push_back(p->local_name);
- p = p->_owner;
- }
- sname.reverse();
+ Ref<GDScript> base = inst->get_script();
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, base.is_null(), RTR("Not based on a script."));
- if (!path.is_resource_file()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
+ GDScript *p = base.ptr();
+ String path = p->get_script_path();
+ Vector<StringName> sname;
- *r_ret = RTR("Not based on a resource file");
+ while (p->_owner) {
+ sname.push_back(p->local_name);
+ p = p->_owner;
+ }
+ sname.reverse();
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::OBJECT, !path.is_resource_file(), RTR("Not based on a resource file."));
- NodePath cp(sname, Vector<StringName>(), false);
+ NodePath cp(sname, Vector<StringName>(), false);
- Dictionary d;
- d["@subpath"] = cp;
- d["@path"] = path;
+ Dictionary d;
+ d["@subpath"] = cp;
+ d["@path"] = path;
- for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
- if (!d.has(E.key)) {
- d[E.key] = ins->members[E.value.index];
- }
- }
- *r_ret = d;
+ for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) {
+ if (!d.has(E.key)) {
+ d[E.key] = inst->members[E.value.index];
}
}
+
+ *r_ret = d;
}
static inline void dict_to_inst(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
-
- if (p_args[0]->get_type() != Variant::DICTIONARY) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::DICTIONARY;
- *r_ret = Variant();
-
- return;
- }
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::DICTIONARY);
Dictionary d = *p_args[0];
- if (!d.has("@path")) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (missing @path)");
-
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !d.has("@path"), RTR("Invalid instance dictionary format (missing @path)."));
Ref<Script> scr = ResourceLoader::load(d["@path"]);
- if (!scr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = RTR("Invalid instance dictionary format (can't load script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !scr.is_valid(), RTR("Invalid instance dictionary format (can't load script at @path)."));
Ref<GDScript> gdscr = scr;
-
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary format (invalid script at @path)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary format (invalid script at @path)."));
NodePath sub;
if (d.has("@subpath")) {
@@ -361,54 +296,35 @@ struct GDScriptUtilityFunctionsDefinitions {
for (int i = 0; i < sub.get_name_count(); i++) {
gdscr = gdscr->subclasses[sub.get_name(i)];
- if (!gdscr.is_valid()) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::OBJECT;
- *r_ret = Variant();
- *r_ret = RTR("Invalid instance dictionary (invalid subclasses)");
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::DICTIONARY, !gdscr.is_valid(), RTR("Invalid instance dictionary (invalid subclasses)."));
}
- *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error);
+ *r_ret = gdscr->_new(nullptr, -1 /* skip initializer */, r_error);
if (r_error.error != Callable::CallError::CALL_OK) {
*r_ret = RTR("Cannot instantiate GDScript class.");
return;
}
- GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
- Ref<GDScript> gd_ref = ins->get_script();
+ GDScriptInstance *inst = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance());
+ Ref<GDScript> gd_ref = inst->get_script();
for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) {
if (d.has(E.key)) {
- ins->members.write[E.value.index] = d[E.key];
+ inst->members.write[E.value.index] = d[E.key];
}
}
}
static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- if (p_arg_count < 3) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.expected = 3;
- *r_ret = Variant();
- return;
- }
- if (p_arg_count > 4) {
- r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
- r_error.expected = 4;
- *r_ret = Variant();
- return;
- }
-
- VALIDATE_ARG_INT(0);
- VALIDATE_ARG_INT(1);
- VALIDATE_ARG_INT(2);
+ DEBUG_VALIDATE_ARG_COUNT(3, 4);
+ DEBUG_VALIDATE_ARG_TYPE(0, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(1, Variant::INT);
+ DEBUG_VALIDATE_ARG_TYPE(2, Variant::INT);
Color color((int64_t)*p_args[0] / 255.0f, (int64_t)*p_args[1] / 255.0f, (int64_t)*p_args[2] / 255.0f);
if (p_arg_count == 4) {
- VALIDATE_ARG_INT(3);
+ DEBUG_VALIDATE_ARG_TYPE(3, Variant::INT);
color.a = (int64_t)*p_args[3] / 255.0f;
}
@@ -435,7 +351,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()));
return;
@@ -449,7 +366,8 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(0);
+ DEBUG_VALIDATE_ARG_COUNT(0, 0);
+
if (Thread::get_caller_id() != Thread::get_main_id()) {
*r_ret = TypedArray<Dictionary>();
return;
@@ -468,7 +386,7 @@ struct GDScriptUtilityFunctionsDefinitions {
}
static inline void len(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(1);
+ DEBUG_VALIDATE_ARG_COUNT(1, 1);
switch (p_args[0]->get_type()) {
case Variant::STRING:
case Variant::STRING_NAME: {
@@ -524,56 +442,34 @@ struct GDScriptUtilityFunctionsDefinitions {
*r_ret = d.size();
} break;
default: {
+ *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::NIL;
- *r_ret = vformat(RTR("Value of type '%s' can't provide a length."), Variant::get_type_name(p_args[0]->get_type()));
- }
+ } break;
}
}
static inline void is_instance_of(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
- VALIDATE_ARG_COUNT(2);
+ DEBUG_VALIDATE_ARG_COUNT(2, 2);
if (p_args[1]->get_type() == Variant::INT) {
int builtin_type = *p_args[1];
- if (builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ DEBUG_VALIDATE_ARG_CUSTOM(1, Variant::NIL, builtin_type < 0 || builtin_type >= Variant::VARIANT_MAX,
+ RTR("Invalid type argument for is_instance_of(), use TYPE_* constants for built-in types."));
*r_ret = p_args[0]->get_type() == builtin_type;
return;
}
bool was_type_freed = false;
Object *type_object = p_args[1]->get_validated_object_with_check(was_type_freed);
- if (was_type_freed) {
- *r_ret = RTR("Type argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
- if (!type_object) {
- *r_ret = RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 1;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, was_type_freed, RTR("Type argument is a previously freed instance."));
+ VALIDATE_ARG_CUSTOM(1, Variant::NIL, !type_object,
+ RTR("Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script."));
bool was_value_freed = false;
Object *value_object = p_args[0]->get_validated_object_with_check(was_value_freed);
- if (was_value_freed) {
- *r_ret = RTR("Value argument is a previously freed instance.");
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = 0;
- r_error.expected = Variant::NIL;
- return;
- }
+ VALIDATE_ARG_CUSTOM(0, Variant::NIL, was_value_freed, RTR("Value argument is a previously freed instance."));
if (!value_object) {
*r_ret = false;
return;
@@ -618,113 +514,77 @@ struct GDScriptUtilityFunctionInfo {
static OAHashMap<StringName, GDScriptUtilityFunctionInfo> utility_function_table;
static List<StringName> utility_function_name_table;
-static void _register_function(const String &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
- StringName sname(p_name);
-
- ERR_FAIL_COND(utility_function_table.has(sname));
+static void _register_function(const StringName &p_name, const MethodInfo &p_method_info, GDScriptUtilityFunctions::FunctionPtr p_function, bool p_is_const) {
+ ERR_FAIL_COND(utility_function_table.has(p_name));
GDScriptUtilityFunctionInfo function;
function.function = p_function;
function.info = p_method_info;
function.is_constant = p_is_const;
- utility_function_table.insert(sname, function);
- utility_function_name_table.push_back(sname);
+ utility_function_table.insert(p_name, function);
+ utility_function_name_table.push_back(p_name);
}
-#define REGISTER_FUNC(m_func, m_is_const, m_return_type, ...) \
+#define REGISTER_FUNC(m_func, m_is_const, m_return, m_args, m_is_vararg, m_default_args) \
{ \
String name(#m_func); \
if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ name = name.substr(1); \
} \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
-
-#define REGISTER_FUNC_NO_ARGS(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
+ MethodInfo info = m_args; \
+ info.name = name; \
+ info.return_val = m_return; \
+ info.default_arguments = m_default_args; \
+ if (m_is_vararg) { \
+ info.flags |= METHOD_FLAG_VARARG; \
} \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
_register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
}
-#define REGISTER_VARARG_FUNC(m_func, m_is_const, m_return_type) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name); \
- info.return_val.type = m_return_type; \
- info.flags |= METHOD_FLAG_VARARG; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RET(m_type) \
+ PropertyInfo(Variant::m_type, "")
-#define REGISTER_VARIANT_FUNC(m_func, m_is_const, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::NIL; \
- info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETVAR \
+ PropertyInfo(Variant::NIL, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define REGISTER_CLASS_FUNC(m_func, m_is_const, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = Variant::OBJECT; \
- info.return_val.hint = PROPERTY_HINT_RESOURCE_TYPE; \
- info.return_val.class_name = m_return_type; \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define RETCLS(m_class) \
+ PropertyInfo(Variant::OBJECT, "", PROPERTY_HINT_RESOURCE_TYPE, m_class)
-#define REGISTER_FUNC_DEF(m_func, m_is_const, m_default, m_return_type, ...) \
- { \
- String name(#m_func); \
- if (name.begins_with("_")) { \
- name = name.substr(1, name.length() - 1); \
- } \
- MethodInfo info = MethodInfo(name, __VA_ARGS__); \
- info.return_val.type = m_return_type; \
- info.default_arguments.push_back(m_default); \
- _register_function(name, info, GDScriptUtilityFunctionsDefinitions::m_func, m_is_const); \
- }
+#define NOARGS \
+ MethodInfo()
+
+#define ARGS(...) \
+ MethodInfo("", __VA_ARGS__)
#define ARG(m_name, m_type) \
- PropertyInfo(m_type, m_name)
+ PropertyInfo(Variant::m_type, m_name)
+
+#define ARGVAR(m_name) \
+ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)
-#define VARARG(m_name) \
- PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT)
+#define ARGTYPE(m_name) \
+ PropertyInfo(Variant::INT, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "Variant.Type")
void GDScriptUtilityFunctions::register_functions() {
+ /* clang-format off */
#ifndef DISABLE_DEPRECATED
- REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT));
+ REGISTER_FUNC( convert, true, RETVAR, ARGS( ARGVAR("what"), ARGTYPE("type") ), false, varray( ));
#endif // DISABLE_DEPRECATED
- REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME));
- REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT));
- REGISTER_VARARG_FUNC(range, false, Variant::ARRAY);
- REGISTER_CLASS_FUNC(load, false, "Resource", ARG("path", Variant::STRING));
- REGISTER_FUNC(inst_to_dict, false, Variant::DICTIONARY, ARG("instance", Variant::OBJECT));
- REGISTER_FUNC(dict_to_inst, false, Variant::OBJECT, ARG("dictionary", Variant::DICTIONARY));
- REGISTER_FUNC_DEF(Color8, true, 255, Variant::COLOR, ARG("r8", Variant::INT), ARG("g8", Variant::INT), ARG("b8", Variant::INT), ARG("a8", Variant::INT));
- REGISTER_VARARG_FUNC(print_debug, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(print_stack, false, Variant::NIL);
- REGISTER_FUNC_NO_ARGS(get_stack, false, Variant::ARRAY);
- REGISTER_FUNC(len, true, Variant::INT, VARARG("var"));
- REGISTER_FUNC(is_instance_of, true, Variant::BOOL, VARARG("value"), VARARG("type"));
+ REGISTER_FUNC( type_exists, true, RET(BOOL), ARGS( ARG("type", STRING_NAME) ), false, varray( ));
+ REGISTER_FUNC( _char, true, RET(STRING), ARGS( ARG("char", INT) ), false, varray( ));
+ REGISTER_FUNC( range, false, RET(ARRAY), NOARGS, true, varray( ));
+ REGISTER_FUNC( load, false, RETCLS("Resource"), ARGS( ARG("path", STRING) ), false, varray( ));
+ REGISTER_FUNC( inst_to_dict, false, RET(DICTIONARY), ARGS( ARG("instance", OBJECT) ), false, varray( ));
+ REGISTER_FUNC( dict_to_inst, false, RET(OBJECT), ARGS( ARG("dictionary", DICTIONARY) ), false, varray( ));
+ REGISTER_FUNC( Color8, true, RET(COLOR), ARGS( ARG("r8", INT), ARG("g8", INT),
+ ARG("b8", INT), ARG("a8", INT) ), false, varray( 255 ));
+ REGISTER_FUNC( print_debug, false, RET(NIL), NOARGS, true, varray( ));
+ REGISTER_FUNC( print_stack, false, RET(NIL), NOARGS, false, varray( ));
+ REGISTER_FUNC( get_stack, false, RET(ARRAY), NOARGS, false, varray( ));
+ REGISTER_FUNC( len, true, RET(INT), ARGS( ARGVAR("var") ), false, varray( ));
+ REGISTER_FUNC( is_instance_of, true, RET(BOOL), ARGS( ARGVAR("value"), ARGVAR("type") ), false, varray( ));
+ /* clang-format on */
}
void GDScriptUtilityFunctions::unregister_functions() {
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index d8139d913a..26c5cfe23c 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -397,32 +397,36 @@ void (*type_init_function_table[])(Variant *) = {
#define OPCODES_OUT \
OPSOUT:
#define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test];
+
#ifdef DEBUG_ENABLED
#define DISPATCH_OPCODE \
last_opcode = _code_ptr[ip]; \
goto *switch_table_ops[last_opcode]
-#else
+#else // !DEBUG_ENABLED
#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]]
-#endif
+#endif // DEBUG_ENABLED
+
#define OPCODE_BREAK goto OPSEXIT
#define OPCODE_OUT goto OPSOUT
-#else
+#else // !(defined(__GNUC__) || defined(__clang__))
#define OPCODES_TABLE
#define OPCODE(m_op) case m_op:
#define OPCODE_WHILE(m_test) while (m_test)
#define OPCODES_END
#define OPCODES_OUT
#define DISPATCH_OPCODE continue
+
#ifdef _MSC_VER
#define OPCODE_SWITCH(m_test) \
__assume(m_test <= OPCODE_END); \
switch (m_test)
-#else
+#else // !_MSC_VER
#define OPCODE_SWITCH(m_test) switch (m_test)
-#endif
+#endif // _MSC_VER
+
#define OPCODE_BREAK break
#define OPCODE_OUT break
-#endif
+#endif // defined(__GNUC__) || defined(__clang__)
// Helpers for VariantInternal methods in macros.
#define OP_GET_BOOL get_bool
@@ -663,7 +667,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#else
+#else // !DEBUG_ENABLED
#define GD_ERR_BREAK(m_cond)
#define CHECK_SPACE(m_space)
@@ -676,7 +680,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE_BREAK; \
}
-#endif
+#endif // DEBUG_ENABLED
#define LOAD_INSTRUCTION_ARGS \
int instr_arg_count = _code_ptr[ip + 1]; \
@@ -1965,7 +1969,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
-#endif
+#endif // DEBUG_ENABLED
ip += 3;
}
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
new file mode 100644
index 0000000000..cfacb6a010
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.gd
@@ -0,0 +1,3 @@
+@export
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
new file mode 100644
index 0000000000..ed677cd39a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_inapplicable.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Annotation "@export" cannot be applied to a function.
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
new file mode 100644
index 0000000000..a6b171a428
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.gd
@@ -0,0 +1,3 @@
+@hello_world
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
new file mode 100644
index 0000000000..540e66f15b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/annotation_unrecognized.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unrecognized annotation: "@hello_world".
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
new file mode 100644
index 0000000000..59bdb6eceb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.gd
@@ -0,0 +1,12 @@
+func test():
+ const COLOR = Color8(255, 0.0, false)
+ var false_value := false
+ @warning_ignore("narrowing_conversion")
+ var color = Color8(255, 0.0, false_value)
+ print(var_to_str(COLOR))
+ print(var_to_str(color))
+
+ var string := "Node"
+ var string_name := &"Node"
+ print(type_exists(string))
+ print(type_exists(string_name))
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
new file mode 100644
index 0000000000..00913faa49
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript_utility_implicit_conversion.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+Color(1, 0, 0, 1)
+Color(1, 0, 0, 1)
+true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
new file mode 100644
index 0000000000..63d5935d1e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.gd
@@ -0,0 +1,8 @@
+func get_parse_string(t: Variant):
+ return t.parse_string
+
+func test():
+ var a: Callable = JSON.parse_string
+ var b: Callable = get_parse_string(JSON)
+ prints(a.call("{\"test\": \"a\"}"), a.is_valid())
+ prints(b.call("{\"test\": \"b\"}"), b.is_valid())
diff --git a/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
new file mode 100644
index 0000000000..a2cb4b9a07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/native_static_method_as_callable.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+{ "test": "a" } false
+{ "test": "b" } false
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
index 188972e6fe..f54058b0d9 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs
@@ -32,7 +32,7 @@ partial class EventSignals
add => backing_MySignal += value;
remove => backing_MySignal -= value;
}
- protected void OnMySignal(string str, int num)
+ protected void EmitSignalMySignal(string str, int num)
{
EmitSignal(SignalName.MySignal, str, num);
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
index c734dc7be1..ffde135930 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs
@@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
index 0de840aa34..94d447f61a 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs
@@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
- properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
+ properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)23, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index 0f86b3b91c..fc67e4f592 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -791,7 +791,7 @@ namespace Godot.SourceGenerators
}
}
- hint = PropertyHint.DictionaryType;
+ hint = PropertyHint.TypeString;
hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
return hintString != null;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
index 0dda43ab4c..702c50d461 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -282,7 +282,7 @@ namespace Godot.SourceGenerators
.Append(" -= value;\n")
.Append("}\n");
- // Generate On{EventName} method to raise the event
+ // Generate EmitSignal{EventName} method to raise the event
var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
int paramCount = invokeMethodSymbol.Parameters.Length;
@@ -291,7 +291,7 @@ namespace Godot.SourceGenerators
"private" :
"protected";
- source.Append($" {raiseMethodModifiers} void On{signalName}(");
+ source.Append($" {raiseMethodModifiers} void EmitSignal{signalName}(");
for (int i = 0; i < paramCount; i++)
{
var paramSymbol = invokeMethodSymbol.Parameters[i];
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 946e997c1b..e97229c621 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3275,10 +3275,10 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
p_output.append(CLOSE_BLOCK_L1);
- // Generate On{EventName} method to raise the event.
+ // Generate EmitSignal{EventName} method to raise the event.
if (!p_itype.is_singleton) {
p_output.append(MEMBER_BEGIN "protected void ");
- p_output << "On" << p_isignal.proxy_name;
+ p_output << "EmitSignal" << p_isignal.proxy_name;
if (is_parameterless) {
p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index dd77e81b45..8df1db533d 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -480,6 +480,8 @@ void NavMap::sync() {
// connection, integration and path finding.
_new_pm_edge_free_count = free_edges.size();
+ real_t sqr_edge_connection_margin = edge_connection_margin * edge_connection_margin;
+
for (int i = 0; i < free_edges.size(); i++) {
const gd::Edge::Connection &free_edge = free_edges[i];
Vector3 edge_p1 = free_edge.polygon->points[free_edge.edge].pos;
@@ -510,7 +512,7 @@ void NavMap::sync() {
} else {
other1 = other_edge_p1.lerp(other_edge_p2, (1.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other1.distance_to(self1) > edge_connection_margin) {
+ if (other1.distance_squared_to(self1) > sqr_edge_connection_margin) {
continue;
}
@@ -521,7 +523,7 @@ void NavMap::sync() {
} else {
other2 = other_edge_p1.lerp(other_edge_p2, (0.0 - projected_p1_ratio) / (projected_p2_ratio - projected_p1_ratio));
}
- if (other2.distance_to(self2) > edge_connection_margin) {
+ if (other2.distance_squared_to(self2) > sqr_edge_connection_margin) {
continue;
}
@@ -549,11 +551,11 @@ void NavMap::sync() {
const Vector3 end = link->get_end_position();
gd::Polygon *closest_start_polygon = nullptr;
- real_t closest_start_distance = link_connection_radius;
+ real_t closest_start_sqr_dist = link_connection_radius * link_connection_radius;
Vector3 closest_start_point;
gd::Polygon *closest_end_polygon = nullptr;
- real_t closest_end_distance = link_connection_radius;
+ real_t closest_end_sqr_dist = link_connection_radius * link_connection_radius;
Vector3 closest_end_point;
// Create link to any polygons within the search radius of the start point.
@@ -564,11 +566,11 @@ void NavMap::sync() {
for (uint32_t start_point_id = 2; start_point_id < start_poly.points.size(); start_point_id += 1) {
const Face3 start_face(start_poly.points[0].pos, start_poly.points[start_point_id - 1].pos, start_poly.points[start_point_id].pos);
const Vector3 start_point = start_face.get_closest_point_to(start);
- const real_t start_distance = start_point.distance_to(start);
+ const real_t sqr_dist = start_point.distance_squared_to(start);
// Pick the polygon that is within our radius and is closer than anything we've seen yet.
- if (start_distance <= link_connection_radius && start_distance < closest_start_distance) {
- closest_start_distance = start_distance;
+ if (sqr_dist < closest_start_sqr_dist) {
+ closest_start_sqr_dist = sqr_dist;
closest_start_point = start_point;
closest_start_polygon = &start_poly;
}
@@ -581,11 +583,11 @@ void NavMap::sync() {
for (uint32_t end_point_id = 2; end_point_id < end_poly.points.size(); end_point_id += 1) {
const Face3 end_face(end_poly.points[0].pos, end_poly.points[end_point_id - 1].pos, end_poly.points[end_point_id].pos);
const Vector3 end_point = end_face.get_closest_point_to(end);
- const real_t end_distance = end_point.distance_to(end);
+ const real_t sqr_dist = end_point.distance_squared_to(end);
// Pick the polygon that is within our radius and is closer than anything we've seen yet.
- if (end_distance <= link_connection_radius && end_distance < closest_end_distance) {
- closest_end_distance = end_distance;
+ if (sqr_dist < closest_end_sqr_dist) {
+ closest_end_sqr_dist = sqr_dist;
closest_end_point = end_point;
closest_end_polygon = &end_poly;
}
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index dd6a921440..48c87bcd59 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -26,7 +26,7 @@ elif env["platform"] == "linuxbsd":
env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_XLIB"])
if env["wayland"]:
- env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_WAYLAND"])
+ env_openxr.AppendUnique(CPPDEFINES=["XR_USE_PLATFORM_EGL"])
# FIXME: Review what needs to be set for Android and macOS.
env_openxr.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"])
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 5430a41d6d..f924386ecf 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -576,20 +576,15 @@ PackedStringArray OpenXRActionMap::get_top_level_paths(const Ref<OpenXRAction> p
const OpenXRInteractionProfileMetadata::InteractionProfile *profile = OpenXRInteractionProfileMetadata::get_singleton()->get_profile(ip->get_interaction_profile_path());
if (profile != nullptr) {
- for (int j = 0; j < ip->get_binding_count(); j++) {
- Ref<OpenXRIPBinding> binding = ip->get_binding(j);
- if (binding->get_action() == p_action) {
- PackedStringArray paths = binding->get_paths();
-
- for (int k = 0; k < paths.size(); k++) {
- const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(paths[k]);
- if (io_path != nullptr) {
- String top_path = io_path->top_level_path;
-
- if (!arr.has(top_path)) {
- arr.push_back(top_path);
- }
- }
+ Vector<Ref<OpenXRIPBinding>> bindings = ip->get_bindings_for_action(p_action);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ String binding_path = binding->get_binding_path();
+ const OpenXRInteractionProfileMetadata::IOPath *io_path = profile->get_io_path(binding_path);
+ if (io_path != nullptr) {
+ String top_path = io_path->top_level_path;
+
+ if (!arr.has(top_path)) {
+ arr.push_back(top_path);
}
}
}
diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp
index 1266457113..2aab55f6ec 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.cpp
+++ b/modules/openxr/action_map/openxr_interaction_profile.cpp
@@ -35,23 +35,30 @@ void OpenXRIPBinding::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action");
- ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
+ ClassDB::bind_method(D_METHOD("set_binding_path", "binding_path"), &OpenXRIPBinding::set_binding_path);
+ ClassDB::bind_method(D_METHOD("get_binding_path"), &OpenXRIPBinding::get_binding_path);
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "binding_path"), "set_binding_path", "get_binding_path");
+
+ // Deprecated
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths);
ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"), "set_paths", "get_paths");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_paths", "get_paths");
+ ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count);
ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path);
ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path);
ClassDB::bind_method(D_METHOD("remove_path", "path"), &OpenXRIPBinding::remove_path);
+#endif // DISABLE_DEPRECATED
}
-Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) {
// This is a helper function to help build our default action sets
Ref<OpenXRIPBinding> binding;
binding.instantiate();
binding->set_action(p_action);
- binding->parse_paths(String(p_paths));
+ binding->set_binding_path(p_binding_path);
return binding;
}
@@ -65,42 +72,68 @@ Ref<OpenXRAction> OpenXRIPBinding::get_action() const {
return action;
}
-int OpenXRIPBinding::get_path_count() const {
- return paths.size();
+void OpenXRIPBinding::set_binding_path(const String &path) {
+ binding_path = path;
+ emit_changed();
}
-void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) {
- paths = p_paths;
- emit_changed();
+String OpenXRIPBinding::get_binding_path() const {
+ return binding_path;
}
-PackedStringArray OpenXRIPBinding::get_paths() const {
+#ifndef DISABLE_DEPRECATED
+
+void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { // Deprecated, but needed for loading old action maps.
+ // Fallback logic, this should ONLY be called when loading older action maps.
+ // We'll parse this momentarily and extract individual bindings.
+ binding_path = "";
+ for (const String &path : p_paths) {
+ if (!binding_path.is_empty()) {
+ binding_path += ",";
+ }
+ binding_path += path;
+ }
+}
+
+PackedStringArray OpenXRIPBinding::get_paths() const { // Deprecated, but needed for converting old action maps.
+ // Fallback logic, return an array.
+ // If we just loaded an old action map from disc, this will be a comma separated list of actions.
+ // Once parsed there should be only one path in our array.
+ PackedStringArray paths = binding_path.split(",", false);
+
return paths;
}
-void OpenXRIPBinding::parse_paths(const String p_paths) {
- paths = p_paths.split(",", false);
- emit_changed();
+int OpenXRIPBinding::get_path_count() const { // Deprecated.
+ // Fallback logic, we only have one entry.
+ return binding_path.is_empty() ? 0 : 1;
}
-bool OpenXRIPBinding::has_path(const String p_path) const {
- return paths.has(p_path);
+bool OpenXRIPBinding::has_path(const String p_path) const { // Deprecated.
+ // Fallback logic, return true if this is our path.
+ return binding_path == p_path;
}
-void OpenXRIPBinding::add_path(const String p_path) {
- if (!paths.has(p_path)) {
- paths.push_back(p_path);
+void OpenXRIPBinding::add_path(const String p_path) { // Deprecated.
+ // Fallback logic, only assign first time this is called.
+ if (binding_path != p_path) {
+ ERR_FAIL_COND_MSG(!binding_path.is_empty(), "Method add_path has been deprecated. A binding path was already set, create separate binding resources for each path and use set_binding_path instead.");
+
+ binding_path = p_path;
emit_changed();
}
}
-void OpenXRIPBinding::remove_path(const String p_path) {
- if (paths.has(p_path)) {
- paths.erase(p_path);
- emit_changed();
- }
+void OpenXRIPBinding::remove_path(const String p_path) { // Deprecated.
+ ERR_FAIL_COND_MSG(binding_path != p_path, "Method remove_path has been deprecated. Attempt at removing a different binding path, remove the correct binding record from the interaction profile instead.");
+
+ // Fallback logic, clear if this is our path.
+ binding_path = p_path;
+ emit_changed();
}
+#endif // DISABLE_DEPRECATED
+
OpenXRIPBinding::~OpenXRIPBinding() {
action.unref();
}
@@ -151,9 +184,18 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding(int p_index) const {
}
void OpenXRInteractionProfile::set_bindings(Array p_bindings) {
- // TODO add check here that our bindings don't contain duplicate actions
+ bindings.clear();
+
+ for (Ref<OpenXRIPBinding> binding : p_bindings) {
+ String binding_path = binding->get_binding_path();
+ if (binding_path.find_char(',') >= 0) {
+ // Convert old binding approach to new...
+ add_new_binding(binding->get_action(), binding_path);
+ } else {
+ add_binding(binding);
+ }
+ }
- bindings = p_bindings;
emit_changed();
}
@@ -161,10 +203,9 @@ Array OpenXRInteractionProfile::get_bindings() const {
return bindings;
}
-Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<OpenXRAction> p_action) const {
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->get_action() == p_action) {
+Ref<OpenXRIPBinding> OpenXRInteractionProfile::find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action && binding->get_binding_path() == p_binding_path) {
return binding;
}
}
@@ -172,11 +213,23 @@ Ref<OpenXRIPBinding> OpenXRInteractionProfile::get_binding_for_action(const Ref<
return Ref<OpenXRIPBinding>();
}
+Vector<Ref<OpenXRIPBinding>> OpenXRInteractionProfile::get_bindings_for_action(const Ref<OpenXRAction> p_action) const {
+ Vector<Ref<OpenXRIPBinding>> ret_bindings;
+
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_action() == p_action) {
+ ret_bindings.push_back(binding);
+ }
+ }
+
+ return ret_bindings;
+}
+
void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) {
ERR_FAIL_COND(p_binding.is_null());
if (!bindings.has(p_binding)) {
- ERR_FAIL_COND_MSG(get_binding_for_action(p_binding->get_action()).is_valid(), "There is already a binding for this action in this interaction profile");
+ ERR_FAIL_COND_MSG(find_binding(p_binding->get_action(), p_binding->get_binding_path()).is_valid(), "There is already a binding for this action and binding path in this interaction profile.");
bindings.push_back(p_binding);
emit_changed();
@@ -191,11 +244,15 @@ void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) {
}
}
-void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) {
+void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths) {
// This is a helper function to help build our default action sets
- Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths);
- add_binding(binding);
+ PackedStringArray paths = p_paths.split(",", false);
+
+ for (const String &path : paths) {
+ Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, path);
+ add_binding(binding);
+ }
}
void OpenXRInteractionProfile::remove_binding_for_action(const Ref<OpenXRAction> p_action) {
diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h
index 479cc3c527..952f87a09d 100644
--- a/modules/openxr/action_map/openxr_interaction_profile.h
+++ b/modules/openxr/action_map/openxr_interaction_profile.h
@@ -41,26 +41,29 @@ class OpenXRIPBinding : public Resource {
private:
Ref<OpenXRAction> action;
- PackedStringArray paths;
+ String binding_path;
protected:
static void _bind_methods();
public:
- static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Helper function for adding a new binding
+ static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path); // Helper function for adding a new binding.
- void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding
- Ref<OpenXRAction> get_action() const; // Get the action for this binding
+ void set_action(const Ref<OpenXRAction> p_action); // Set the action for this binding.
+ Ref<OpenXRAction> get_action() const; // Get the action for this binding.
- int get_path_count() const; // Get the number of io paths
- void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource)
- PackedStringArray get_paths() const; // Get our paths (for saving to resource)
+ void set_binding_path(const String &path);
+ String get_binding_path() const;
- void parse_paths(const String p_paths); // Parse a comma separated string of io paths.
-
- bool has_path(const String p_path) const; // Has this io path
- void add_path(const String p_path); // Add an io path
- void remove_path(const String p_path); // Remove an io path
+ // Deprecated.
+#ifndef DISABLE_DEPRECATED
+ void set_paths(const PackedStringArray p_paths); // Set our paths (for loading from resource), needed for loading old action maps.
+ PackedStringArray get_paths() const; // Get our paths (for saving to resource), needed for converted old action maps.
+ int get_path_count() const; // Get the number of io paths.
+ bool has_path(const String p_path) const; // Has this io path.
+ void add_path(const String p_path); // Add an io path.
+ void remove_path(const String p_path); // Remove an io path.
+#endif // DISABLE_DEPRECATED
// TODO add validation that we can display in the interface that checks if no two paths belong to the same top level path
@@ -88,11 +91,12 @@ public:
void set_bindings(Array p_bindings); // Set the bindings (for loading from a resource)
Array get_bindings() const; // Get the bindings (for saving to a resource)
- Ref<OpenXRIPBinding> get_binding_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
+ Ref<OpenXRIPBinding> find_binding(const Ref<OpenXRAction> p_action, const String &p_binding_path) const; // Get our binding record
+ Vector<Ref<OpenXRIPBinding>> get_bindings_for_action(const Ref<OpenXRAction> p_action) const; // Get our binding record for a given action
void add_binding(Ref<OpenXRIPBinding> p_binding); // Add a binding object
void remove_binding(Ref<OpenXRIPBinding> p_binding); // Remove a binding object
- void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); // Create a new binding for this profile
+ void add_new_binding(const Ref<OpenXRAction> p_action, const String &p_paths); // Create a new binding for this profile
void remove_binding_for_action(const Ref<OpenXRAction> p_action); // Remove all bindings for this action
bool has_binding_for_action(const Ref<OpenXRAction> p_action); // Returns true if we have a binding for this action
diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml
index f274f0868e..ddd6fbe268 100644
--- a/modules/openxr/doc_classes/OpenXRIPBinding.xml
+++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml
@@ -4,32 +4,32 @@
Defines a binding between an [OpenXRAction] and an XR input or output.
</brief_description>
<description>
- This binding resource binds an [OpenXRAction] to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger".
+ This binding resource binds an [OpenXRAction] to an input or output. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". This would require two binding entries.
</description>
<tutorials>
</tutorials>
<methods>
- <method name="add_path">
+ <method name="add_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
Add an input/output path to this binding.
</description>
</method>
- <method name="get_path_count" qualifiers="const">
+ <method name="get_path_count" qualifiers="const" deprecated="Binding is for a single path.">
<return type="int" />
<description>
Get the number of input/output paths in this binding.
</description>
</method>
- <method name="has_path" qualifiers="const">
+ <method name="has_path" qualifiers="const" deprecated="Binding is for a single path.">
<return type="bool" />
<param index="0" name="path" type="String" />
<description>
Returns [code]true[/code] if this input/output path is part of this binding.
</description>
</method>
- <method name="remove_path">
+ <method name="remove_path" deprecated="Binding is for a single path.">
<return type="void" />
<param index="0" name="path" type="String" />
<description>
@@ -39,9 +39,13 @@
</methods>
<members>
<member name="action" type="OpenXRAction" setter="set_action" getter="get_action">
- [OpenXRAction] that is bound to these paths.
+ [OpenXRAction] that is bound to [member binding_path].
</member>
- <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()">
+ <member name="binding_path" type="String" setter="set_binding_path" getter="get_binding_path" default="&quot;&quot;">
+ Binding path that defines the input or output bound to [member action].
+ [b]Note:[/b] Binding paths are suggestions, an XR runtime may choose to bind the action to a different input or output emulating this input or output.
+ </member>
+ <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" deprecated="Use [member binding_path] instead.">
Paths that define the inputs or outputs bound on the device.
</member>
</members>
diff --git a/modules/openxr/editor/openxr_interaction_profile_editor.cpp b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
index 651171358c..09a9a990ed 100644
--- a/modules/openxr/editor/openxr_interaction_profile_editor.cpp
+++ b/modules/openxr/editor/openxr_interaction_profile_editor.cpp
@@ -73,17 +73,19 @@ void OpenXRInteractionProfileEditorBase::_add_binding(const String p_action, con
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_null()) {
// create a new binding
binding.instantiate();
binding->set_action(action);
+ binding->set_binding_path(p_path);
+
+ // add it to our interaction profile
interaction_profile->add_binding(binding);
interaction_profile->set_edited(true);
- }
- binding->add_path(p_path);
- binding->set_edited(true);
+ binding->set_edited(true);
+ }
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -98,15 +100,10 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
Ref<OpenXRAction> action = action_map->get_action(p_action);
ERR_FAIL_COND(action.is_null());
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(action);
+ Ref<OpenXRIPBinding> binding = interaction_profile->find_binding(action, p_path);
if (binding.is_valid()) {
- binding->remove_path(p_path);
- binding->set_edited(true);
-
- if (binding->get_path_count() == 0) {
- interaction_profile->remove_binding(binding);
- interaction_profile->set_edited(true);
- }
+ interaction_profile->remove_binding(binding);
+ interaction_profile->set_edited(true);
// Update our toplevel paths
action->set_toplevel_paths(action_map->get_top_level_paths(action));
@@ -116,21 +113,22 @@ void OpenXRInteractionProfileEditorBase::_remove_binding(const String p_action,
}
void OpenXRInteractionProfileEditorBase::remove_all_bindings_for_action(Ref<OpenXRAction> p_action) {
- Ref<OpenXRIPBinding> binding = interaction_profile->get_binding_for_action(p_action);
- if (binding.is_valid()) {
+ Vector<Ref<OpenXRIPBinding>> bindings = interaction_profile->get_bindings_for_action(p_action);
+ if (bindings.size() > 0) {
String action_name = p_action->get_name_with_set();
// for our undo/redo we process all paths
undo_redo->create_action(TTR("Remove action from interaction profile"));
- PackedStringArray paths = binding->get_paths();
- for (const String &path : paths) {
- undo_redo->add_do_method(this, "_remove_binding", action_name, path);
- undo_redo->add_undo_method(this, "_add_binding", action_name, path);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ undo_redo->add_do_method(this, "_remove_binding", action_name, binding->get_binding_path());
+ undo_redo->add_undo_method(this, "_add_binding", action_name, binding->get_binding_path());
}
undo_redo->commit_action(false);
// but we take a shortcut here :)
- interaction_profile->remove_binding(binding);
+ for (const Ref<OpenXRIPBinding> &binding : bindings) {
+ interaction_profile->remove_binding(binding);
+ }
interaction_profile->set_edited(true);
// Update our toplevel paths
@@ -228,9 +226,8 @@ void OpenXRInteractionProfileEditor::_add_io_path(VBoxContainer *p_container, co
if (interaction_profile.is_valid()) {
String io_path = String(p_io_path->openxr_path);
Array bindings = interaction_profile->get_bindings();
- for (int i = 0; i < bindings.size(); i++) {
- Ref<OpenXRIPBinding> binding = bindings[i];
- if (binding->has_path(io_path)) {
+ for (Ref<OpenXRIPBinding> binding : bindings) {
+ if (binding->get_binding_path() == io_path) {
Ref<OpenXRAction> action = binding->get_action();
HBoxContainer *action_hb = memnew(HBoxContainer);
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
index caded14ca7..07e26298bf 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp
@@ -64,6 +64,9 @@ HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() {
#else
request_extensions[XR_KHR_OPENGL_ENABLE_EXTENSION_NAME] = nullptr;
#endif
+#if defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED)
+ request_extensions[XR_MNDX_EGL_ENABLE_EXTENSION_NAME] = &egl_extension_enabled;
+#endif
return request_extensions;
}
@@ -128,9 +131,14 @@ bool OpenXROpenGLExtension::check_graphics_api_support(XrVersion p_desired_versi
XrGraphicsBindingOpenGLWin32KHR OpenXROpenGLExtension::graphics_binding_gl;
#elif defined(ANDROID_ENABLED)
XrGraphicsBindingOpenGLESAndroidKHR OpenXROpenGLExtension::graphics_binding_gl;
-#elif defined(X11_ENABLED)
+#elif defined(LINUXBSD_ENABLED)
+#ifdef X11_ENABLED
XrGraphicsBindingOpenGLXlibKHR OpenXROpenGLExtension::graphics_binding_gl;
#endif
+#ifdef EGL_ENABLED
+XrGraphicsBindingEGLMNDX OpenXROpenGLExtension::graphics_binding_egl;
+#endif
+#endif
void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) {
XrVersion desired_version = XR_MAKE_VERSION(3, 3, 0);
@@ -142,10 +150,6 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
DisplayServer *display_server = DisplayServer::get_singleton();
-#ifdef WAYLAND_ENABLED
- ERR_FAIL_COND_V_MSG(display_server->get_name() == "Wayland", p_next_pointer, "OpenXR is not yet supported on OpenGL Wayland.");
-#endif
-
#ifdef WIN32
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR,
graphics_binding_gl.next = p_next_pointer;
@@ -159,7 +163,23 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
graphics_binding_gl.display = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE);
graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122
graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
-#elif defined(X11_ENABLED)
+#else
+#if defined(EGL_ENABLED) && defined(WAYLAND_ENABLED)
+ if (display_server->get_name() == "Wayland") {
+ ERR_FAIL_COND_V_MSG(!egl_extension_enabled, p_next_pointer, "OpenXR cannot initialize on Wayland without the XR_MNDX_egl_enable extension.");
+
+ graphics_binding_egl.type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX;
+ graphics_binding_egl.next = p_next_pointer;
+
+ graphics_binding_egl.getProcAddress = eglGetProcAddress;
+ graphics_binding_egl.display = (void *)display_server->window_get_native_handle(DisplayServer::EGL_DISPLAY);
+ graphics_binding_egl.config = (void *)display_server->window_get_native_handle(DisplayServer::EGL_CONFIG);
+ graphics_binding_egl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT);
+
+ return &graphics_binding_egl;
+ }
+#endif
+#if defined(X11_ENABLED)
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
graphics_binding_gl.next = p_next_pointer;
@@ -175,8 +195,13 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
graphics_binding_gl.visualid = 0;
graphics_binding_gl.glxFBConfig = 0;
#endif
+#endif
+#if defined(WIN32) || defined(ANDROID_ENABLED) || defined(X11_ENABLED)
return &graphics_binding_gl;
+#else
+ return p_next_pointer;
+#endif
}
void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) {
diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h
index 8da3ca48f4..beb68e47e3 100644
--- a/modules/openxr/extensions/platform/openxr_opengl_extension.h
+++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h
@@ -64,9 +64,18 @@ private:
static XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl;
#elif defined(ANDROID_ENABLED)
static XrGraphicsBindingOpenGLESAndroidKHR graphics_binding_gl;
-#else // Linux/X11
+#elif defined(LINUXBSD_ENABLED)
+#ifdef X11_ENABLED
static XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl;
#endif
+#ifdef EGL_ENABLED
+ static XrGraphicsBindingEGLMNDX graphics_binding_egl;
+
+ bool egl_extension_enabled = false;
+#endif
+#else
+#error "OpenXR with OpenGL isn't supported on this platform"
+#endif
struct SwapchainGraphicsData {
bool is_multiview;
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 73ac529537..500a58acc3 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -300,10 +300,7 @@ void OpenXRInterface::_load_action_map() {
continue;
}
- PackedStringArray paths = xr_binding->get_paths();
- for (int k = 0; k < paths.size(); k++) {
- openxr_api->interaction_profile_add_binding(ip, action->action_rid, paths[k]);
- }
+ openxr_api->interaction_profile_add_binding(ip, action->action_rid, xr_binding->get_binding_path());
}
// Now submit our suggestions
diff --git a/modules/openxr/openxr_platform_inc.h b/modules/openxr/openxr_platform_inc.h
index 957a87cbb2..09bc0c89a2 100644
--- a/modules/openxr/openxr_platform_inc.h
+++ b/modules/openxr/openxr_platform_inc.h
@@ -49,6 +49,13 @@
#else
#define XR_USE_GRAPHICS_API_OPENGL
#endif // ANDROID_ENABLED
+#if defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED)
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#else
+#include <EGL/egl.h>
+#endif // GLAD_ENABLED
+#endif // defined(LINUXBSD_ENABLED) && defined(EGL_ENABLED)
#ifdef X11_ENABLED
#define GL_GLEXT_PROTOTYPES 1
#define GL3_PROTOTYPES 1
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index 697369d516..bc429e4632 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -56,6 +56,10 @@ OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_c
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
+ if (openxr_api) {
+ openxr_session_running = openxr_api->is_running();
+ }
+
Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR");
if (openxr_interface.is_valid()) {
openxr_interface->connect("session_begun", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_begun));
diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp
index 94d8b267d1..54dc040583 100644
--- a/modules/raycast/raycast_occlusion_cull.cpp
+++ b/modules/raycast/raycast_occlusion_cull.cpp
@@ -181,17 +181,7 @@ void RaycastOcclusionCull::RaycastHZBuffer::sort_rays(const Vector3 &p_camera_di
}
int k = tile_i * TILE_SIZE + tile_j;
int tile_index = i * tile_grid_size.x + j;
- float d = camera_rays[tile_index].ray.tfar[k];
-
- if (!p_orthogonal) {
- const float &dir_x = camera_rays[tile_index].ray.dir_x[k];
- const float &dir_y = camera_rays[tile_index].ray.dir_y[k];
- const float &dir_z = camera_rays[tile_index].ray.dir_z[k];
- float cos_theta = p_camera_dir.x * dir_x + p_camera_dir.y * dir_y + p_camera_dir.z * dir_z;
- d *= cos_theta;
- }
-
- mips[0][y * buffer_size.x + x] = d;
+ mips[0][y * buffer_size.x + x] = camera_rays[tile_index].ray.tfar[k];
}
}
}
diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp
index d903137195..b9d493b844 100644
--- a/modules/svg/image_loader_svg.cpp
+++ b/modules/svg/image_loader_svg.cpp
@@ -104,51 +104,33 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
picture->size(width, height);
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
- // Note: memalloc here, be sure to memfree before any return.
- uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
+ Vector<uint8_t> buffer;
+ buffer.resize(sizeof(uint32_t) * width * height);
- tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
+ tvg::Result res = sw_canvas->target((uint32_t *)buffer.ptrw(), width, width, height, tvg::SwCanvas::ABGR8888S);
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");
}
res = sw_canvas->push(std::move(picture));
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't insert ThorVG picture on canvas.");
}
res = sw_canvas->draw();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas.");
}
res = sw_canvas->sync();
if (res != tvg::Result::Success) {
- memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't sync ThorVG canvas.");
}
- Vector<uint8_t> image;
- image.resize(width * height * sizeof(uint32_t));
-
- for (uint32_t y = 0; y < height; y++) {
- for (uint32_t x = 0; x < width; x++) {
- uint32_t n = buffer[y * width + x];
- const size_t offset = sizeof(uint32_t) * width * y + sizeof(uint32_t) * x;
- image.write[offset + 0] = (n >> 16) & 0xff;
- image.write[offset + 1] = (n >> 8) & 0xff;
- image.write[offset + 2] = n & 0xff;
- image.write[offset + 3] = (n >> 24) & 0xff;
- }
- }
+ p_image->set_data(width, height, false, Image::FORMAT_RGBA8, buffer);
res = sw_canvas->clear(true);
- memfree(buffer);
- p_image->set_data(width, height, false, Image::FORMAT_RGBA8, image);
return OK;
}
diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp
index e2bb89811c..b205dbbbf2 100644
--- a/modules/tga/image_loader_tga.cpp
+++ b/modules/tga/image_loader_tga.cpp
@@ -32,6 +32,7 @@
#include "core/error/error_macros.h"
#include "core/io/file_access_memory.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
diff --git a/modules/theora/video_stream_theora.cpp b/modules/theora/video_stream_theora.cpp
index d964fd7627..2725a61a82 100644
--- a/modules/theora/video_stream_theora.cpp
+++ b/modules/theora/video_stream_theora.cpp
@@ -31,6 +31,7 @@
#include "video_stream_theora.h"
#include "core/config/project_settings.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "scene/resources/image_texture.h"
diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp
index 31c1e06dee..b0a977f647 100644
--- a/modules/tinyexr/image_saver_tinyexr.cpp
+++ b/modules/tinyexr/image_saver_tinyexr.cpp
@@ -31,6 +31,7 @@
#include "image_saver_tinyexr.h"
#include "core/math/math_funcs.h"
+#include "core/os/os.h"
#include <zlib.h> // Should come before including tinyexr.
diff --git a/modules/tinyexr/image_saver_tinyexr.h b/modules/tinyexr/image_saver_tinyexr.h
index 058eeae58e..014c2f2e19 100644
--- a/modules/tinyexr/image_saver_tinyexr.h
+++ b/modules/tinyexr/image_saver_tinyexr.h
@@ -31,7 +31,7 @@
#ifndef IMAGE_SAVER_TINYEXR_H
#define IMAGE_SAVER_TINYEXR_H
-#include "core/os/os.h"
+#include "core/io/image.h"
Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale);
Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale);
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 0a10754e24..937bdbaa07 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -5,6 +5,7 @@ import sys
from typing import TYPE_CHECKING
from methods import print_error, print_warning
+from platform_methods import validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -98,12 +99,7 @@ def install_ndk_if_needed(env: "SConsEnvironment"):
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for Android. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
if get_min_sdk_version(env["ndk_platform"]) < get_min_target_api():
print_warning(
@@ -171,9 +167,7 @@ def configure(env: "SConsEnvironment"):
env["AS"] = compiler_path + "/clang"
env.Append(
- CCFLAGS=(
- "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
- )
+ CCFLAGS=("-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden".split())
)
if get_min_sdk_version(env["ndk_platform"]) >= 24:
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index fa5b970a96..e3ee1dd631 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -389,6 +389,14 @@ int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type,
}
return 0;
}
+ case EGL_DISPLAY: {
+ // @todo Find a way to get this from the Java side.
+ return 0;
+ }
+ case EGL_CONFIG: {
+ // @todo Find a way to get this from the Java side.
+ return 0;
+ }
#endif
default: {
return 0;
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index 2fe5539e56..8c8bca2b7c 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -577,7 +577,7 @@
Allows an application to write to the user dictionary.
</member>
<member name="screen/immersive_mode" type="bool" setter="" getter="">
- If [code]true[/code], hides navigation and status bar.
+ If [code]true[/code], hides navigation and status bar. See [method DisplayServer.window_set_mode] to toggle it at runtime.
</member>
<member name="screen/support_large" type="bool" setter="" getter="">
Indicates whether the application supports larger screen form-factors.
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 7e1d626486..15e80f824d 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -35,6 +35,7 @@
#include "godot_plugin_config.h"
#endif // DISABLE_DEPRECATED
+#include "core/io/image.h"
#include "core/io/zip_io.h"
#include "core/os/os.h"
#include "editor/export/editor_export_platform.h"
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 4e8e82a70a..c44a6dd472 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -108,7 +108,7 @@ public final class PermissionsUtil {
} else {
PermissionInfo permissionInfo = getPermissionInfo(activity, permission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
- if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
+ if ((protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Requesting permission " + permission);
requestedPermissions.add(permission);
}
@@ -174,7 +174,7 @@ public final class PermissionsUtil {
try {
PermissionInfo permissionInfo = getPermissionInfo(activity, permissionName);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
- if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) {
+ if ((protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[] { permissionName }, REQUEST_SINGLE_PERMISSION_REQ_CODE);
return false;
}
@@ -259,7 +259,7 @@ public final class PermissionsUtil {
} else {
PermissionInfo permissionInfo = getPermissionInfo(context, manifestPermission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
- if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(context, manifestPermission) == PackageManager.PERMISSION_GRANTED) {
+ if ((protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(context, manifestPermission) == PackageManager.PERMISSION_GRANTED) {
grantedPermissions.add(manifestPermission);
}
}
diff --git a/platform/ios/detect.py b/platform/ios/detect.py
index 20a3a996bc..0f7f938852 100644
--- a/platform/ios/detect.py
+++ b/platform/ios/detect.py
@@ -3,6 +3,7 @@ import sys
from typing import TYPE_CHECKING
from methods import detect_darwin_sdk_path, print_error, print_warning
+from platform_methods import validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -60,12 +61,7 @@ def get_flags():
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_64", "arm64"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for iOS. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
## LTO
@@ -134,7 +130,7 @@ def configure(env: "SConsEnvironment"):
elif env["arch"] == "arm64":
env.Append(
CCFLAGS=(
- "-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ "-fobjc-arc -arch arm64 -fmessage-length=0"
" -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
" -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies"
" -isysroot $IOS_SDK_PATH".split()
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index a67434527c..2fd573da75 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -4,7 +4,7 @@ import sys
from typing import TYPE_CHECKING
from methods import get_compiler_version, print_error, print_warning, using_gcc
-from platform_methods import detect_arch
+from platform_methods import detect_arch, validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -74,12 +74,7 @@ def get_flags():
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for Linux / *BSD. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
## Build type
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 71c721ca1d..0a87c4a517 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -627,6 +627,18 @@ int64_t DisplayServerWayland::window_get_native_handle(HandleType p_handle_type,
}
return 0;
} break;
+ case EGL_DISPLAY: {
+ if (egl_manager) {
+ return (int64_t)egl_manager->get_display(p_window);
+ }
+ return 0;
+ }
+ case EGL_CONFIG: {
+ if (egl_manager) {
+ return (int64_t)egl_manager->get_config(p_window);
+ }
+ return 0;
+ }
#endif // GLES3_ENABLED
default: {
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 293623e594..f1be5d83dc 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1861,6 +1861,18 @@ int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, Win
}
return 0;
}
+ case EGL_DISPLAY: {
+ if (gl_manager_egl) {
+ return (int64_t)gl_manager_egl->get_display(p_window);
+ }
+ return 0;
+ }
+ case EGL_CONFIG: {
+ if (gl_manager_egl) {
+ return (int64_t)gl_manager_egl->get_config(p_window);
+ }
+ return 0;
+ }
#endif
default: {
return 0;
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index a8968b592e..cab91fd33c 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -3,7 +3,7 @@ import sys
from typing import TYPE_CHECKING
from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang, print_error, print_warning
-from platform_methods import detect_arch, detect_mvk
+from platform_methods import detect_arch, detect_mvk, validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -68,12 +68,7 @@ def get_flags():
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_64", "arm64"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for macOS. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
## Build type
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 48cc7bbba3..43469d981b 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -2676,6 +2676,18 @@ int64_t DisplayServerMacOS::window_get_native_handle(HandleType p_handle_type, W
}
return 0;
}
+ case EGL_DISPLAY: {
+ if (gl_manager_angle) {
+ return (int64_t)gl_manager_angle->get_display(p_window);
+ }
+ return 0;
+ }
+ case EGL_CONFIG: {
+ if (gl_manager_angle) {
+ return (int64_t)gl_manager_angle->get_config(p_window);
+ }
+ return 0;
+ }
#endif
default: {
return 0;
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index d88d347359..4ded2f3301 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -34,6 +34,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
+#include "core/io/image.h"
#include "core/io/marshalls.h"
#include "core/io/resource_saver.h"
#include "core/os/os.h"
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 735e2eaf4f..26bbbccffa 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -14,6 +14,7 @@ from emscripten_helpers import (
from SCons.Util import WhereIs
from methods import get_compiler_version, print_error, print_warning
+from platform_methods import validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -86,12 +87,7 @@ def get_flags():
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["wasm32"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for Web. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
try:
env["initial_memory"] = int(env["initial_memory"])
diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js
index 00616bc1a5..eeb3978256 100644
--- a/platform/web/js/libs/library_godot_fetch.js
+++ b/platform/web/js/libs/library_godot_fetch.js
@@ -59,7 +59,12 @@ const GodotFetch = {
});
obj.status = response.status;
obj.response = response;
- obj.reader = response.body.getReader();
+ // `body` can be null per spec (for example, in cases where the request method is HEAD).
+ // As of the time of writing, Chromium (127.0.6533.72) does not follow the spec but Firefox (131.0.3) does.
+ // See godotengine/godot#76825 for more information.
+ // See Chromium revert (of the change to follow the spec):
+ // https://chromium.googlesource.com/chromium/src/+/135354b7bdb554cd03c913af7c90aceead03c4d4
+ obj.reader = response.body?.getReader();
obj.chunked = chunked;
},
@@ -121,6 +126,10 @@ const GodotFetch = {
}
obj.reading = true;
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
+ } else if (obj.reader == null && obj.response.body == null) {
+ // Emulate a stream closure to maintain the request lifecycle.
+ obj.reading = true;
+ GodotFetch.onread(id, { value: undefined, done: true });
}
},
},
@@ -159,7 +168,10 @@ const GodotFetch = {
if (!obj.response) {
return 0;
}
- if (obj.reader) {
+ // If the reader is nullish, but there is no body, and the request is not marked as done,
+ // the same status should be returned as though the request is currently being read
+ // so that the proper lifecycle closure can be handled in `read()`.
+ if (obj.reader || (obj.response.body == null && !obj.done)) {
return 1;
}
if (obj.done) {
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 0ee52a09a7..ddcd29adc9 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
import methods
from methods import print_error, print_warning
-from platform_methods import detect_arch
+from platform_methods import detect_arch, validate_arch
if TYPE_CHECKING:
from SCons.Script.SConscript import SConsEnvironment
@@ -483,9 +483,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
else:
print_warning("Missing environment variable: WindowsSdkDir")
- if int(env["target_win_version"], 16) < 0x0601:
- print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
- sys.exit(255)
+ validate_win_version(env)
env.AppendUnique(
CPPDEFINES=[
@@ -549,15 +547,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
LIBS += ["vulkan"]
if env["d3d12"]:
- # Check whether we have d3d12 dependencies installed.
- if not os.path.exists(env["mesa_libs"]):
- print_error(
- "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
- "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
- "See the documentation for more information:\n\t"
- "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
- )
- sys.exit(255)
+ check_d3d12_installed(env)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
LIBS += ["dxgi", "dxguid"]
@@ -820,9 +810,7 @@ def configure_mingw(env: "SConsEnvironment"):
## Compile flags
- if int(env["target_win_version"], 16) < 0x0601:
- print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
- sys.exit(255)
+ validate_win_version(env)
if not env["use_llvm"]:
env.Append(CCFLAGS=["-mwindows"])
@@ -900,15 +888,7 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(LIBS=["vulkan"])
if env["d3d12"]:
- # Check whether we have d3d12 dependencies installed.
- if not os.path.exists(env["mesa_libs"]):
- print_error(
- "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
- "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
- "See the documentation for more information:\n\t"
- "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
- )
- sys.exit(255)
+ check_d3d12_installed(env)
env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"])
env.Append(LIBS=["dxgi", "dxguid"])
@@ -951,12 +931,7 @@ def configure_mingw(env: "SConsEnvironment"):
def configure(env: "SConsEnvironment"):
# Validate arch.
supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
- if env["arch"] not in supported_arches:
- print_error(
- 'Unsupported CPU architecture "%s" for Windows. Supported architectures are: %s.'
- % (env["arch"], ", ".join(supported_arches))
- )
- sys.exit(255)
+ validate_arch(env["arch"], get_name(), supported_arches)
# At this point the env has been set up with basic tools/compilers.
env.Prepend(CPPPATH=["#platform/windows"])
@@ -984,3 +959,20 @@ def configure(env: "SConsEnvironment"):
else: # MinGW
configure_mingw(env)
+
+
+def check_d3d12_installed(env):
+ if not os.path.exists(env["mesa_libs"]):
+ print_error(
+ "The Direct3D 12 rendering driver requires dependencies to be installed.\n"
+ "You can install them by running `python misc\\scripts\\install_d3d12_sdk_windows.py`.\n"
+ "See the documentation for more information:\n\t"
+ "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html"
+ )
+ sys.exit(255)
+
+
+def validate_win_version(env):
+ if int(env["target_win_version"], 16) < 0x0601:
+ print_error("`target_win_version` should be 0x0601 or higher (Windows 7).")
+ sys.exit(255)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index ffa3840181..fc49b63dde 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -67,6 +67,18 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#endif
+#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
+#define DWMWA_WINDOW_CORNER_PREFERENCE 33
+#endif
+
+#ifndef DWMWCP_DEFAULT
+#define DWMWCP_DEFAULT 0
+#endif
+
+#ifndef DWMWCP_DONOTROUND
+#define DWMWCP_DONOTROUND 1
+#endif
+
#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1)
#if defined(__GNUC__)
@@ -1483,6 +1495,9 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.always_on_top = true;
}
+ if (p_flags & WINDOW_FLAG_SHARP_CORNERS_BIT) {
+ wd.sharp_corners = true;
+ }
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
wd.no_focus = true;
}
@@ -1650,6 +1665,18 @@ int64_t DisplayServerWindows::window_get_native_handle(HandleType p_handle_type,
}
return 0;
}
+ case EGL_DISPLAY: {
+ if (gl_manager_angle) {
+ return (int64_t)gl_manager_angle->get_display(p_window);
+ }
+ return 0;
+ }
+ case EGL_CONFIG: {
+ if (gl_manager_angle) {
+ return (int64_t)gl_manager_angle->get_config(p_window);
+ }
+ return 0;
+ }
#endif
default: {
return 0;
@@ -2297,6 +2324,12 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.always_on_top = p_enabled;
_update_window_style(p_window);
} break;
+ case WINDOW_FLAG_SHARP_CORNERS: {
+ wd.sharp_corners = p_enabled;
+ DWORD value = wd.sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(wd.hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ _update_window_style(p_window);
+ } break;
case WINDOW_FLAG_TRANSPARENT: {
if (p_enabled) {
// Enable per-pixel alpha.
@@ -3994,6 +4027,10 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
native_menu->_menu_activate(HMENU(lParam), (int)wParam);
} break;
case WM_CREATE: {
+ {
+ DWORD value = windows[window_id].sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ }
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
@@ -5645,6 +5682,12 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
wd_transient_parent->transient_children.insert(id);
}
+ wd.sharp_corners = p_flags & WINDOW_FLAG_SHARP_CORNERS_BIT;
+ {
+ DWORD value = wd.sharp_corners ? DWMWCP_DONOTROUND : DWMWCP_DEFAULT;
+ ::DwmSetWindowAttribute(wd.hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value));
+ }
+
if (is_dark_mode_supported() && dark_title_available) {
BOOL value = is_dark_mode();
::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
@@ -6369,7 +6412,10 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID);
- ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window.");
+ if (main_window == INVALID_WINDOW_ID) {
+ r_error = ERR_UNAVAILABLE;
+ ERR_FAIL_MSG("Failed to create main window.");
+ }
joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd);
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 7d6a3e96a6..fc72e05b1d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -38,6 +38,7 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "drivers/unix/ip_unix.h"
#include "drivers/wasapi/audio_driver_wasapi.h"
@@ -473,6 +474,7 @@ class DisplayServerWindows : public DisplayServer {
bool exclusive = false;
bool context_created = false;
bool mpass = false;
+ bool sharp_corners = false;
// Used to transfer data between events using timer.
WPARAM saved_wparam;
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
index 235a4b332a..09e4640b40 100644
--- a/platform/windows/native_menu_windows.h
+++ b/platform/windows/native_menu_windows.h
@@ -31,6 +31,7 @@
#ifndef NATIVE_MENU_WINDOWS_H
#define NATIVE_MENU_WINDOWS_H
+#include "core/io/image.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
#include "servers/display/native_menu.h"
diff --git a/platform_methods.py b/platform_methods.py
index 2b157da22b..2c4eb0d1dd 100644
--- a/platform_methods.py
+++ b/platform_methods.py
@@ -1,6 +1,7 @@
import os
import platform
import subprocess
+import sys
import methods
@@ -40,6 +41,15 @@ def detect_arch():
return "x86_64"
+def validate_arch(arch, platform_name, supported_arches):
+ if arch not in supported_arches:
+ methods.print_error(
+ 'Unsupported CPU architecture "%s" for %s. Supported architectures are: %s.'
+ % (arch, platform_name, ", ".join(supported_arches))
+ )
+ sys.exit(255)
+
+
def get_build_version(short):
import version
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 5813ab02e3..7d645ba448 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -167,17 +167,16 @@ void Path2D::_curve_changed() {
return;
}
- if (!Engine::get_singleton()->is_editor_hint() && !get_tree()->is_debugging_paths_hint()) {
- return;
- }
-
- queue_redraw();
for (int i = 0; i < get_child_count(); i++) {
PathFollow2D *follow = Object::cast_to<PathFollow2D>(get_child(i));
if (follow) {
follow->path_changed();
}
}
+
+ if (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_paths_hint()) {
+ queue_redraw();
+ }
}
void Path2D::set_curve(const Ref<Curve2D> &p_curve) {
diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp
index 7b70986adc..2d18e62b10 100644
--- a/scene/3d/light_3d.cpp
+++ b/scene/3d/light_3d.cpp
@@ -146,6 +146,15 @@ bool Light3D::get_shadow_reverse_cull_face() const {
return reverse_cull;
}
+void Light3D::set_shadow_caster_mask(uint32_t p_caster_mask) {
+ shadow_caster_mask = p_caster_mask;
+ RS::get_singleton()->light_set_shadow_caster_mask(light, shadow_caster_mask);
+}
+
+uint32_t Light3D::get_shadow_caster_mask() const {
+ return shadow_caster_mask;
+}
+
AABB Light3D::get_aabb() const {
if (type == RenderingServer::LIGHT_DIRECTIONAL) {
return AABB(Vector3(-1, -1, -1), Vector3(2, 2, 2));
@@ -300,7 +309,7 @@ bool Light3D::is_editor_only() const {
}
void Light3D::_validate_property(PropertyInfo &p_property) const {
- if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow")) {
+ if (!shadow && (p_property.name == "shadow_bias" || p_property.name == "shadow_normal_bias" || p_property.name == "shadow_reverse_cull_face" || p_property.name == "shadow_transmittance_bias" || p_property.name == "shadow_opacity" || p_property.name == "shadow_blur" || p_property.name == "distance_fade_shadow" || p_property.name == "shadow_caster_mask")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
@@ -354,6 +363,9 @@ void Light3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shadow_reverse_cull_face", "enable"), &Light3D::set_shadow_reverse_cull_face);
ClassDB::bind_method(D_METHOD("get_shadow_reverse_cull_face"), &Light3D::get_shadow_reverse_cull_face);
+ ClassDB::bind_method(D_METHOD("set_shadow_caster_mask", "caster_mask"), &Light3D::set_shadow_caster_mask);
+ ClassDB::bind_method(D_METHOD("get_shadow_caster_mask"), &Light3D::get_shadow_caster_mask);
+
ClassDB::bind_method(D_METHOD("set_bake_mode", "bake_mode"), &Light3D::set_bake_mode);
ClassDB::bind_method(D_METHOD("get_bake_mode"), &Light3D::get_bake_mode);
@@ -388,6 +400,7 @@ void Light3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_transmittance_bias", PROPERTY_HINT_RANGE, "-16,16,0.001"), "set_param", "get_param", PARAM_TRANSMITTANCE_BIAS);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_param", "get_param", PARAM_SHADOW_OPACITY);
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "shadow_blur", PROPERTY_HINT_RANGE, "0,10,0.001"), "set_param", "get_param", PARAM_SHADOW_BLUR);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_caster_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_shadow_caster_mask", "get_shadow_caster_mask");
ADD_GROUP("Distance Fade", "distance_fade_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distance_fade_enabled"), "set_enable_distance_fade", "is_distance_fade_enabled");
diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h
index d6eca8d8b6..5f549469c6 100644
--- a/scene/3d/light_3d.h
+++ b/scene/3d/light_3d.h
@@ -75,6 +75,7 @@ private:
bool negative = false;
bool reverse_cull = false;
uint32_t cull_mask = 0;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade_enabled = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -136,6 +137,9 @@ public:
void set_shadow_reverse_cull_face(bool p_enable);
bool get_shadow_reverse_cull_face() const;
+ void set_shadow_caster_mask(uint32_t p_caster_mask);
+ uint32_t get_shadow_caster_mask() const;
+
void set_bake_mode(BakeMode p_mode);
BakeMode get_bake_mode() const;
diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp
index a1f32fccbe..012ef7860d 100644
--- a/scene/3d/lightmap_gi.cpp
+++ b/scene/3d/lightmap_gi.cpp
@@ -1602,19 +1602,16 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
PackedStringArray LightmapGI::get_configuration_warnings() const {
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
-#ifndef MODULE_LIGHTMAPPER_RD_ENABLED
-#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
- warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
-#else
- warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
-#endif
- return warnings;
-#endif
-
+#ifdef MODULE_LIGHTMAPPER_RD_ENABLED
if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
return warnings;
}
+#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
+ warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
+#else
+ warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
+#endif
return warnings;
}
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index e8166802f8..f551cb401c 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -332,8 +332,8 @@ void MeshInstance3D::create_multiple_convex_collisions(const Ref<MeshConvexDecom
void MeshInstance3D::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_READY: {
- callable_mp(this, &MeshInstance3D::_resolve_skeleton_path).call_deferred();
+ case NOTIFICATION_ENTER_TREE: {
+ _resolve_skeleton_path();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
if (mesh.is_valid()) {
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 9e4c9b1832..c1c992588b 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -326,6 +326,8 @@ void Skeleton3D::_notification(int p_what) {
#ifndef DISABLE_DEPRECATED
setup_simulator();
#endif // _DISABLE_DEPRECATED
+ update_flags = UPDATE_FLAG_POSE;
+ _notification(NOTIFICATION_UPDATE_SKELETON);
} break;
case NOTIFICATION_UPDATE_SKELETON: {
// Update bone transforms to apply unprocessed poses.
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index 0db12600c3..ecfe095f1d 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -31,6 +31,7 @@
#ifndef SKELETON_3D_H
#define SKELETON_3D_H
+#include "core/templates/a_hash_map.h"
#include "scene/3d/node_3d.h"
#include "scene/resources/3d/skin.h"
@@ -159,7 +160,7 @@ private:
bool process_order_dirty = false;
Vector<int> parentless_bones;
- HashMap<String, int> name_to_bone_index;
+ AHashMap<String, int> name_to_bone_index;
mutable StringName concatenated_bone_names = StringName();
void _update_bone_names() const;
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index eb8bc8c382..0fa6810d23 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -563,6 +563,7 @@ void AnimationMixer::_clear_caches() {
memdelete(K.value);
}
track_cache.clear();
+ animation_track_num_to_track_cashe.clear();
cache_valid = false;
capture_cache.clear();
@@ -922,6 +923,27 @@ bool AnimationMixer::_update_caches() {
idx++;
}
+ for (KeyValue<Animation::TypeHash, TrackCache *> &K : track_cache) {
+ K.value->blend_idx = track_map[K.value->path];
+ }
+
+ animation_track_num_to_track_cashe.clear();
+ LocalVector<TrackCache *> track_num_to_track_cashe;
+ for (const StringName &E : sname_list) {
+ Ref<Animation> anim = get_animation(E);
+ const Vector<Animation::Track *> tracks = anim->get_tracks();
+ track_num_to_track_cashe.resize(tracks.size());
+ for (int i = 0; i < tracks.size(); i++) {
+ TrackCache **track_ptr = track_cache.getptr(tracks[i]->thash);
+ if (track_ptr == nullptr) {
+ track_num_to_track_cashe[i] = nullptr;
+ } else {
+ track_num_to_track_cashe[i] = *track_ptr;
+ }
+ }
+ animation_track_num_to_track_cashe.insert(anim, track_num_to_track_cashe);
+ }
+
track_count = idx;
cache_valid = true;
@@ -946,7 +968,7 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
clear_animation_instances();
}
-Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
+Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx) {
#ifndef _3D_DISABLED
switch (p_anim->track_get_type(p_track)) {
case Animation::TYPE_POSITION_3D: {
@@ -1033,7 +1055,7 @@ void AnimationMixer::_blend_init() {
}
}
-bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
+bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
return true;
}
@@ -1084,26 +1106,30 @@ void AnimationMixer::_blend_calc_total_weight() {
real_t weight = ai.playback_info.weight;
const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
int track_weights_count = ai.playback_info.track_weights.size();
- static LocalVector<Animation::TypeHash> processed_hashes;
+ ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cashe.has(a), "No animation in cache.");
+ LocalVector<TrackCache *> &track_num_to_track_cashe = animation_track_num_to_track_cashe[a];
+ thread_local HashSet<Animation::TypeHash, HashHasher> processed_hashes;
processed_hashes.clear();
const Vector<Animation::Track *> tracks = a->get_tracks();
- for (const Animation::Track *animation_track : tracks) {
+ Animation::Track *const *tracks_ptr = tracks.ptr();
+ int count = tracks.size();
+ for (int i = 0; i < count; i++) {
+ Animation::Track *animation_track = tracks_ptr[i];
if (!animation_track->enabled) {
continue;
}
Animation::TypeHash thash = animation_track->thash;
- TrackCache **track_ptr = track_cache.getptr(thash);
- if (track_ptr == nullptr || processed_hashes.has(thash)) {
+ TrackCache *track = track_num_to_track_cashe[i];
+ if (track == nullptr || processed_hashes.has(thash)) {
// No path, but avoid error spamming.
// Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly.
continue;
}
- TrackCache *track = *track_ptr;
- int blend_idx = track_map[track->path];
+ int blend_idx = track->blend_idx;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
track->total_weight += blend;
- processed_hashes.push_back(thash);
+ processed_hashes.insert(thash);
}
}
}
@@ -1130,6 +1156,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
#ifndef _3D_DISABLED
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
+ ERR_CONTINUE_EDMSG(!animation_track_num_to_track_cashe.has(a), "No animation in cache.");
+ LocalVector<TrackCache *> &track_num_to_track_cashe = animation_track_num_to_track_cashe[a];
const Vector<Animation::Track *> tracks = a->get_tracks();
Animation::Track *const *tracks_ptr = tracks.ptr();
real_t a_length = a->get_length();
@@ -1139,15 +1167,11 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = animation_track->thash;
- TrackCache **track_ptr = track_cache.getptr(thash);
- if (track_ptr == nullptr) {
+ TrackCache *track = track_num_to_track_cashe[i];
+ if (track == nullptr) {
continue; // No path, but avoid error spamming.
}
- TrackCache *track = *track_ptr;
- int *blend_idx_ptr = track_map.getptr(track->path);
- ERR_CONTINUE(blend_idx_ptr == nullptr);
- int blend_idx = *blend_idx_ptr;
+ int blend_idx = track->blend_idx;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
if (!deterministic) {
@@ -1581,7 +1605,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
track_info.backward = backward;
track_info.use_blend = a->audio_track_is_use_blend(i);
- HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ AHashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
// Main process to fire key is started from here.
if (p_update_only) {
@@ -1850,7 +1874,7 @@ void AnimationMixer::_blend_apply() {
PlayingAudioTrackInfo &track_info = L.value;
float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
LocalVector<int> erase_streams;
- HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ AHashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
PlayingAudioStreamInfo pasi = M.value;
@@ -2134,7 +2158,7 @@ void AnimationMixer::restore(const Ref<AnimatedValuesBackup> &p_backup) {
ERR_FAIL_COND(p_backup.is_null());
track_cache = p_backup->get_data();
_blend_apply();
- track_cache = HashMap<Animation::TypeHash, AnimationMixer::TrackCache *>();
+ track_cache = AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher>();
cache_valid = false;
}
@@ -2370,7 +2394,7 @@ AnimationMixer::AnimationMixer() {
AnimationMixer::~AnimationMixer() {
}
-void AnimatedValuesBackup::set_data(const HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> p_data) {
+void AnimatedValuesBackup::set_data(const AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> p_data) {
clear_data();
for (const KeyValue<Animation::TypeHash, AnimationMixer::TrackCache *> &E : p_data) {
@@ -2383,7 +2407,7 @@ void AnimatedValuesBackup::set_data(const HashMap<Animation::TypeHash, Animation
}
}
-HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> AnimatedValuesBackup::get_data() const {
+AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> AnimatedValuesBackup::get_data() const {
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> ret;
for (const KeyValue<Animation::TypeHash, AnimationMixer::TrackCache *> &E : data) {
AnimationMixer::TrackCache *track = get_cache_copy(E.value);
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index 27c9a00a9c..1906146c56 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -31,6 +31,7 @@
#ifndef ANIMATION_MIXER_H
#define ANIMATION_MIXER_H
+#include "core/templates/a_hash_map.h"
#include "scene/animation/tween.h"
#include "scene/main/node.h"
#include "scene/resources/animation.h"
@@ -102,7 +103,7 @@ public:
protected:
/* ---- Data lists ---- */
LocalVector<AnimationLibraryData> animation_libraries;
- HashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
+ AHashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
TypedArray<StringName> _get_animation_library_list() const;
Vector<String> _get_animation_list() const {
@@ -148,6 +149,7 @@ protected:
uint64_t setup_pass = 0;
Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION;
NodePath path;
+ int blend_idx = -1;
ObjectID object_id;
real_t total_weight = 0.0;
@@ -269,7 +271,7 @@ protected:
// Audio track information for mixng and ending.
struct PlayingAudioTrackInfo {
- HashMap<int, PlayingAudioStreamInfo> stream_info;
+ AHashMap<int, PlayingAudioStreamInfo> stream_info;
double length = 0.0;
double time = 0.0;
real_t volume = 0.0;
@@ -308,7 +310,8 @@ protected:
};
RootMotionCache root_motion_cache;
- HashMap<Animation::TypeHash, TrackCache *> track_cache;
+ AHashMap<Animation::TypeHash, TrackCache *, HashHasher> track_cache;
+ AHashMap<Ref<Animation>, LocalVector<TrackCache *>> animation_track_num_to_track_cashe;
HashSet<TrackCache *> playing_caches;
Vector<Node *> playing_audio_stream_players;
@@ -324,7 +327,7 @@ protected:
/* ---- Blending processor ---- */
LocalVector<AnimationInstance> animation_instances;
- HashMap<NodePath, int> track_map;
+ AHashMap<NodePath, int> track_map;
int track_count = 0;
bool deterministic = false;
@@ -359,12 +362,12 @@ protected:
virtual void _process_animation(double p_delta, bool p_update_only = false);
// For post process with retrieved key value during blending.
- virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
+ virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant &p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, ObjectID, int);
void _blend_init();
- virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
+ virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map);
virtual void _blend_capture(double p_delta);
void _blend_calc_total_weight(); // For undeterministic blending.
void _blend_process(double p_delta, bool p_update_only = false);
@@ -485,11 +488,11 @@ public:
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
- HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> data;
+ AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> data;
public:
- void set_data(const HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> p_data);
- HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> get_data() const;
+ void set_data(const AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> p_data);
+ AHashMap<Animation::TypeHash, AnimationMixer::TrackCache *, HashHasher> get_data() const;
void clear_data();
AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const;
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index c3c5399a6b..5cc204100c 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -1619,7 +1619,7 @@ AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationM
playback_new = playback_new->duplicate(); // Don't process original when testing.
}
- return playback_new->process(node_state.base_path, this, p_playback_info, p_test_only);
+ return playback_new->process(node_state.get_base_path(), this, p_playback_info, p_test_only);
}
String AnimationNodeStateMachine::get_caption() const {
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 8a2ca47920..b3a75a75a0 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -133,7 +133,7 @@ void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
List<PropertyInfo> anim_names;
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
- HashMap<StringName, StringName>::ConstIterator F = animation_next_set.find(E.key);
+ AHashMap<StringName, StringName>::ConstIterator F = animation_next_set.find(E.key);
if (F && F->value != StringName()) {
anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
@@ -299,7 +299,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
}
}
-bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
+bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
if (!playback.current.from) {
_set_process(false);
return false;
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 3223e2522d..06b3eecb89 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -52,7 +52,7 @@ public:
#endif // DISABLE_DEPRECATED
private:
- HashMap<StringName, StringName> animation_next_set; // For auto advance.
+ AHashMap<StringName, StringName> animation_next_set; // For auto advance.
float speed_scale = 1.0;
double default_blend_time = 0.0;
@@ -138,7 +138,7 @@ protected:
static void _bind_methods();
// Make animation instances.
- virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
+ virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) override;
virtual void _blend_capture(double p_delta) override;
virtual void _blend_post_process() override;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 19080e61de..d676e2acf4 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -75,20 +75,34 @@ void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_val
if (process_state->is_testing) {
return;
}
+
+ const AHashMap<StringName, int>::Iterator it = property_cache.find(p_name);
+ if (it) {
+ process_state->tree->property_map.get_by_index(it->value).value.first = p_value;
+ return;
+ }
+
ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path));
ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name));
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
-
- process_state->tree->property_map[path].first = p_value;
+ int idx = process_state->tree->property_map.get_index(path);
+ property_cache.insert_new(p_name, idx);
+ process_state->tree->property_map.get_by_index(idx).value.first = p_value;
}
Variant AnimationNode::get_parameter(const StringName &p_name) const {
ERR_FAIL_NULL_V(process_state, Variant());
+ const AHashMap<StringName, int>::ConstIterator it = property_cache.find(p_name);
+ if (it) {
+ return process_state->tree->property_map.get_by_index(it->value).value.first;
+ }
ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant());
ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant());
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
- return process_state->tree->property_map[path].first;
+ int idx = process_state->tree->property_map.get_index(path);
+ property_cache.insert_new(p_name, idx);
+ return process_state->tree->property_map.get_by_index(idx).value.first;
}
void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) {
@@ -203,7 +217,7 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
}
for (const KeyValue<NodePath, bool> &E : filter) {
- const HashMap<NodePath, int> &map = *process_state->track_map;
+ const AHashMap<NodePath, int> &map = *process_state->track_map;
if (!map.has(E.key)) {
continue;
}
@@ -292,7 +306,7 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
// This process, which depends on p_sync is needed to process sync correctly in the case of
// that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync.
- p_node->node_state.base_path = new_path;
+ p_node->set_node_state_base_path(new_path);
p_node->node_state.parent = new_parent;
if (!p_playback_info.seeked && !p_sync && !any_valid) {
p_playback_info.delta = 0.0;
@@ -603,7 +617,7 @@ Ref<AnimationRootNode> AnimationTree::get_root_animation_node() const {
return root_animation_node;
}
-bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
+bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) {
_update_properties(); // If properties need updating, update them.
if (!root_animation_node.is_valid()) {
@@ -627,7 +641,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
for (int i = 0; i < p_track_count; i++) {
src_blendsw[i] = 1.0; // By default all go to 1 for the root input.
}
- root_animation_node->node_state.base_path = SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data());
+ root_animation_node->set_node_state_base_path(SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data()));
root_animation_node->node_state.parent = nullptr;
}
@@ -732,7 +746,7 @@ void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringN
void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node) {
ERR_FAIL_COND(p_node.is_null());
if (!property_parent_map.has(p_base_path)) {
- property_parent_map[p_base_path] = HashMap<StringName, StringName>();
+ property_parent_map[p_base_path] = AHashMap<StringName, StringName>();
}
if (!property_reference_map.has(p_node->get_instance_id())) {
property_reference_map[p_node->get_instance_id()] = p_base_path;
@@ -767,7 +781,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A
pinfo.name = p_base_path + key;
properties.push_back(pinfo);
}
-
+ p_node->make_cache_dirty();
List<AnimationNode::ChildNode> children;
p_node->get_child_nodes(&children);
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index d4b7bf31c9..8ee80f29ee 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -60,7 +60,7 @@ public:
bool closable = false;
Vector<Input> inputs;
- HashMap<NodePath, bool> filter;
+ AHashMap<NodePath, bool> filter;
bool filter_enabled = false;
// To propagate information from upstream for use in estimation of playback progress.
@@ -97,22 +97,57 @@ public:
// Temporary state for blending process which needs to be stored in each AnimationNodes.
struct NodeState {
+ friend AnimationNode;
+
+ private:
StringName base_path;
+
+ public:
AnimationNode *parent = nullptr;
Vector<StringName> connections;
Vector<real_t> track_weights;
+
+ const StringName get_base_path() const {
+ return base_path;
+ }
+
} node_state;
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
struct ProcessState {
AnimationTree *tree = nullptr;
- const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
+ const AHashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
uint64_t last_pass = 0;
} *process_state = nullptr;
+private:
+ mutable AHashMap<StringName, int> property_cache;
+
+public:
+ void set_node_state_base_path(const StringName p_base_path) {
+ if (p_base_path != node_state.base_path) {
+ node_state.base_path = p_base_path;
+ make_cache_dirty();
+ }
+ }
+
+ void set_node_state_base_path(const String p_base_path) {
+ if (p_base_path != node_state.base_path) {
+ node_state.base_path = p_base_path;
+ make_cache_dirty();
+ }
+ }
+
+ const StringName get_node_state_base_path() const {
+ return node_state.get_base_path();
+ }
+
+ void make_cache_dirty() {
+ property_cache.clear();
+ }
Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
@@ -250,9 +285,9 @@ private:
friend class AnimationNode;
List<PropertyInfo> properties;
- HashMap<StringName, HashMap<StringName, StringName>> property_parent_map;
- HashMap<ObjectID, StringName> property_reference_map;
- HashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
+ AHashMap<StringName, AHashMap<StringName, StringName>> property_parent_map;
+ AHashMap<ObjectID, StringName> property_reference_map;
+ AHashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
bool properties_dirty = true;
@@ -286,7 +321,7 @@ private:
virtual void _set_active(bool p_active) override;
// Make animation instances.
- virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
+ virtual bool _blend_pre_process(double p_delta, int p_track_count, const AHashMap<NodePath, int> &p_track_map) override;
#ifndef DISABLE_DEPRECATED
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 0a5f2ec6c7..9df4bfde92 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -296,19 +296,12 @@ void Button::_notification(int p_what) {
}
} break;
case DRAW_HOVER_PRESSED: {
- // Edge case for CheckButton and CheckBox.
- if (has_theme_stylebox("hover_pressed")) {
- if (has_theme_color(SNAME("font_hover_pressed_color"))) {
- font_color = theme_cache.font_hover_pressed_color;
- }
- if (has_theme_color(SNAME("icon_hover_pressed_color"))) {
- icon_modulate_color = theme_cache.icon_hover_pressed_color;
- }
-
- break;
+ font_color = theme_cache.font_hover_pressed_color;
+ if (has_theme_color(SNAME("icon_hover_pressed_color"))) {
+ icon_modulate_color = theme_cache.icon_hover_pressed_color;
}
- }
- [[fallthrough]];
+
+ } break;
case DRAW_PRESSED: {
if (has_theme_color(SNAME("font_pressed_color"))) {
font_color = theme_cache.font_pressed_color;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index fe4c91cb56..2d88a9bddf 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -245,21 +245,7 @@ void ColorPicker::finish_shaders() {
}
void ColorPicker::set_focus_on_line_edit() {
- bool has_hardware_keyboard = true;
-#if defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
- has_hardware_keyboard = DisplayServer::get_singleton()->has_hardware_keyboard();
-#endif // ANDROID_ENABLED || IOS_ENABLED
- if (has_hardware_keyboard) {
- callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
- } else {
- // A hack to avoid showing the virtual keyboard when the ColorPicker window popups and
- // no hardware keyboard is detected on Android and IOS.
- // This will only focus the LineEdit without editing, the virtual keyboard will only be visible when
- // we touch the LineEdit to enter edit mode.
- callable_mp(c_text, &LineEdit::set_editable).call_deferred(false);
- callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
- callable_mp(c_text, &LineEdit::set_editable).call_deferred(true);
- }
+ callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
}
void ColorPicker::_update_controls() {
@@ -1567,23 +1553,21 @@ void ColorPicker::_pick_button_pressed_legacy() {
picker_texture_rect = memnew(TextureRect);
picker_texture_rect->set_anchors_preset(Control::PRESET_FULL_RECT);
+ picker_texture_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
picker_window->add_child(picker_texture_rect);
picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND);
picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input));
- picker_preview = memnew(Panel);
- picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP);
- picker_preview->set_mouse_filter(MOUSE_FILTER_IGNORE);
- picker_window->add_child(picker_preview);
-
picker_preview_label = memnew(Label);
- picker_preview->set_anchors_preset(Control::PRESET_CENTER_TOP);
+ picker_preview_label->set_anchors_preset(Control::PRESET_CENTER_TOP);
picker_preview_label->set_text(ETR("Color Picking active"));
- picker_preview->add_child(picker_preview_label);
- picker_preview_style_box = (Ref<StyleBoxFlat>)memnew(StyleBoxFlat);
+ picker_preview_style_box.instantiate();
picker_preview_style_box->set_bg_color(Color(1.0, 1.0, 1.0));
- picker_preview->add_theme_style_override(SceneStringName(panel), picker_preview_style_box);
+ picker_preview_style_box->set_content_margin_all(4.0);
+ picker_preview_label->add_theme_style_override(CoreStringName(normal), picker_preview_style_box);
+
+ picker_window->add_child(picker_preview_label);
}
Rect2i screen_rect;
@@ -1625,7 +1609,7 @@ void ColorPicker::_pick_button_pressed_legacy() {
}
picker_window->set_size(screen_rect.size);
- picker_preview->set_size(screen_rect.size / 10.0); // 10% of size in each axis.
+ picker_preview_label->set_custom_minimum_size(screen_rect.size / 10); // 10% of size in each axis.
picker_window->popup();
}
@@ -1648,7 +1632,7 @@ void ColorPicker::_picker_texture_input(const Ref<InputEvent> &p_event) {
Vector2 ofs = mev->get_position();
picker_color = img->get_pixel(ofs.x, ofs.y);
picker_preview_style_box->set_bg_color(picker_color);
- picker_preview_label->set_self_modulate(picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f));
+ picker_preview_label->add_theme_color_override(SceneStringName(font_color), picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f));
}
}
}
@@ -2103,7 +2087,9 @@ void ColorPickerButton::pressed() {
float v_offset = show_above ? -minsize.y : get_size().y;
popup->set_position(get_screen_position() + Vector2(h_offset, v_offset));
popup->popup();
- picker->set_focus_on_line_edit();
+ if (DisplayServer::get_singleton()->has_hardware_keyboard()) {
+ picker->set_focus_on_line_edit();
+ }
}
void ColorPickerButton::_notification(int p_what) {
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index ad028584b1..59540d9ace 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -130,7 +130,6 @@ private:
Popup *picker_window = nullptr;
// Legacy color picking.
TextureRect *picker_texture_rect = nullptr;
- Panel *picker_preview = nullptr;
Label *picker_preview_label = nullptr;
Ref<StyleBoxFlat> picker_preview_style_box;
Color picker_color;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index c1d197ea9b..5052deb65a 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -3042,7 +3042,7 @@ void Control::set_layout_direction(Control::LayoutDirection p_direction) {
if (data.layout_dir == p_direction) {
return;
}
- ERR_FAIL_INDEX((int)p_direction, 4);
+ ERR_FAIL_INDEX(p_direction, LAYOUT_DIRECTION_MAX);
data.layout_dir = p_direction;
@@ -3115,13 +3115,20 @@ bool Control::is_layout_rtl() const {
String locale = TranslationServer::get_singleton()->get_tool_locale();
const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
}
- } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) {
+ } else if (data.layout_dir == LAYOUT_DIRECTION_APPLICATION_LOCALE) {
if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
const_cast<Control *>(this)->data.is_rtl = true;
} else {
String locale = TranslationServer::get_singleton()->get_tool_locale();
const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
}
+ } else if (data.layout_dir == LAYOUT_DIRECTION_SYSTEM_LOCALE) {
+ if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
+ const_cast<Control *>(this)->data.is_rtl = true;
+ } else {
+ String locale = OS::get_singleton()->get_locale();
+ const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale);
+ }
} else {
const_cast<Control *>(this)->data.is_rtl = (data.layout_dir == LAYOUT_DIRECTION_RTL);
}
@@ -3574,7 +3581,7 @@ void Control::_bind_methods() {
ADD_GROUP("Layout", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Based on Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), "set_layout_direction", "get_layout_direction");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode");
ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION);
@@ -3723,9 +3730,14 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(ANCHOR_END);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
- BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_APPLICATION_LOCALE);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_RTL);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_SYSTEM_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_MAX);
+#ifndef DISABLE_DEPRECATED
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+#endif // DISABLE_DEPRECATED
BIND_ENUM_CONSTANT(TEXT_DIRECTION_INHERITED);
BIND_ENUM_CONSTANT(TEXT_DIRECTION_AUTO);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 6e1621ce58..6cabf10971 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -140,9 +140,14 @@ public:
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
- LAYOUT_DIRECTION_LOCALE,
+ LAYOUT_DIRECTION_APPLICATION_LOCALE,
LAYOUT_DIRECTION_LTR,
- LAYOUT_DIRECTION_RTL
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_SYSTEM_LOCALE,
+ LAYOUT_DIRECTION_MAX,
+#ifndef DISABLE_DEPRECATED
+ LAYOUT_DIRECTION_LOCALE = LAYOUT_DIRECTION_APPLICATION_LOCALE,
+#endif // DISABLE_DEPRECATED
};
enum TextDirection {
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index f3cf29d0cf..1ba5ef309b 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1056,7 +1056,7 @@ void ItemList::_notification(int p_what) {
scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM));
Size2 size = get_size();
- int width = size.width - theme_cache.panel_style->get_margin(SIDE_RIGHT);
+ int width = size.width - theme_cache.panel_style->get_minimum_size().width;
if (scroll_bar->is_visible()) {
width -= scroll_bar_minwidth;
}
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 46c9c7cccc..9853d699d4 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -510,6 +510,7 @@ void MenuBar::_refresh_menu_names() {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ queue_redraw();
if (is_global && menu_cache[i].submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid);
if (item_idx >= 0) {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 646cd9c70e..40917ee8f1 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -961,19 +961,17 @@ TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) {
if (!prev_item) {
current = current->parent;
- if (current == tree->root && tree->hide_root) {
- return nullptr;
- } else if (!current) {
- if (p_wrap) {
- current = this;
- TreeItem *temp = get_next_visible();
- while (temp) {
- current = temp;
- temp = temp->get_next_visible();
- }
- } else {
+ if (!current || (current == tree->root && tree->hide_root)) {
+ if (!p_wrap) {
return nullptr;
}
+ // Wrap around to the last visible item.
+ current = this;
+ TreeItem *temp = get_next_visible();
+ while (temp) {
+ current = temp;
+ temp = temp->get_next_visible();
+ }
}
} else {
current = prev_item;
@@ -2165,12 +2163,18 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.h_separation : theme_cache.item_margin);
int skip2 = 0;
+
+ bool is_row_hovered = (!cache.hover_header_row && cache.hover_item == p_item);
+
for (int i = 0; i < columns.size(); i++) {
if (skip2) {
skip2--;
continue;
}
+ bool is_col_hovered = cache.hover_column == i;
+ bool is_cell_hovered = is_row_hovered && is_col_hovered;
+ bool is_cell_button_hovered = is_cell_hovered && cache.hover_button_index_in_column != -1;
int item_width = get_column_width(i);
if (i == 0) {
@@ -2203,6 +2207,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int total_ofs = ofs - theme_cache.offset.x;
+ // If part of the column is beyond the right side of the control due to scrolling, clamp the label width
+ // so that all buttons attached to the cell remain within view.
if (total_ofs + item_width > p_draw_size.width) {
item_width = MAX(buttons_width, p_draw_size.width - total_ofs);
}
@@ -2247,17 +2253,42 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, theme_cache.guide_color, 1);
}
- if (i == 0) {
- if (p_item->cells[0].selected && select_mode == SELECT_ROW) {
+ if (i == 0 && select_mode == SELECT_ROW) {
+ if (p_item->cells[0].selected || is_row_hovered) {
const Rect2 content_rect = _get_content_rect();
Rect2i row_rect = Rect2i(Point2i(content_rect.position.x, item_rect.position.y), Size2i(content_rect.size.x, item_rect.size.y));
if (rtl) {
row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x;
}
- if (has_focus()) {
- theme_cache.selected_focus->draw(ci, row_rect);
+
+ if (p_item->cells[0].selected) {
+ if (has_focus()) {
+ theme_cache.selected_focus->draw(ci, row_rect);
+ } else {
+ theme_cache.selected->draw(ci, row_rect);
+ }
+ } else if (!drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, row_rect);
+ } else {
+ theme_cache.hovered->draw(ci, row_rect);
+ }
+ }
+ }
+ }
+
+ if (select_mode != SELECT_ROW) {
+ Rect2i r = cell_rect;
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
+
+ // Cell hover.
+ if (is_cell_hovered && !p_item->cells[i].selected && !drop_mode_flags) {
+ if (is_cell_button_hovered) {
+ theme_cache.hovered_dimmed->draw(ci, r);
} else {
- theme_cache.selected->draw(ci, row_rect);
+ theme_cache.hovered->draw(ci, r);
}
}
}
@@ -2335,7 +2366,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (p_item->cells[i].custom_color) {
cell_color = p_item->cells[i].color;
} else {
- cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : theme_cache.font_color;
+ bool draw_as_hover = !drop_mode_flags && (select_mode == SELECT_ROW ? is_row_hovered : is_cell_hovered);
+ bool draw_as_hover_dim = draw_as_hover && is_cell_button_hovered;
+ cell_color = p_item->cells[i].selected ? theme_cache.font_selected_color : (draw_as_hover_dim ? theme_cache.font_hovered_dimmed_color : (draw_as_hover ? theme_cache.font_hovered_color : theme_cache.font_color));
}
Color font_outline_color = theme_cache.font_outline_color;
@@ -2487,7 +2520,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.size.width -= downarrow->get_width();
if (p_item->cells[i].custom_button) {
- if (cache.hover_item == p_item && cache.hover_cell == i) {
+ if (cache.hover_item == p_item && cache.hover_column == i) {
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
draw_style_box(theme_cache.custom_button_pressed, ir);
} else {
@@ -2508,19 +2541,26 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} break;
}
+ // Draw the buttons inside the cell.
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> button_texture = p_item->cells[i].buttons[j].texture;
Size2 button_size = button_texture->get_size() + theme_cache.button_pressed->get_minimum_size();
Point2i button_ofs = Point2i(ofs + item_width_with_buttons - button_size.width, p_pos.y) - theme_cache.offset + p_draw_ofs;
- if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
- // Being pressed.
+ bool should_draw_pressed = cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled;
+ bool should_draw_hovered = !should_draw_pressed && !drop_mode_flags && cache.hover_item == p_item && cache.hover_column == i && cache.hover_button_index_in_column == j && !p_item->cells[i].buttons[j].disabled;
+
+ if (should_draw_pressed || should_draw_hovered) {
Point2 od = button_ofs;
if (rtl) {
od.x = get_size().width - od.x - button_size.x;
}
- theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ if (should_draw_pressed) {
+ theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ } else {
+ theme_cache.button_hover->draw(get_canvas_item(), Rect2(od.x, od.y, button_size.width, MAX(button_size.height, label_h)));
+ }
}
button_ofs.y += (label_h - button_size.height) / 2;
@@ -2551,6 +2591,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
+ // Draw the folding arrow.
if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box
Ref<Texture2D> arrow;
@@ -2966,6 +3007,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width = MAX(button_w, MIN(limit_w, col_width));
}
+ // Cell button detection code.
for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = c.buttons[j].texture;
int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width;
@@ -2978,7 +3020,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
// Make sure the click is correct.
- Point2 click_pos = get_global_mouse_position() - get_global_position();
+ const Point2 click_pos = get_local_mouse_position();
if (!get_item_at_position(click_pos)) {
pressed_button = -1;
cache.click_type = Cache::CLICK_NONE;
@@ -3485,7 +3527,11 @@ bool Tree::_scroll(bool p_horizontal, float p_pages) {
double prev_value = scroll->get_value();
scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages);
- return scroll->get_value() != prev_value;
+ bool scroll_happened = scroll->get_value() != prev_value;
+ if (scroll_happened) {
+ _determine_hovered_item();
+ }
+ return scroll_happened;
}
Rect2 Tree::_get_scrollbar_layout_rect() const {
@@ -3710,92 +3756,10 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- Ref<StyleBox> bg = theme_cache.panel_style;
- bool rtl = is_layout_rtl();
-
- Point2 pos = mm->get_position();
- if (rtl) {
- pos.x = get_size().width - pos.x;
- }
- pos -= theme_cache.panel_style->get_offset();
-
- Cache::ClickType old_hover = cache.hover_type;
- int old_index = cache.hover_index;
-
- cache.hover_type = Cache::CLICK_NONE;
- cache.hover_index = 0;
- if (show_column_titles) {
- pos.y -= _get_title_button_height();
- if (pos.y < 0) {
- pos.x += theme_cache.offset.x;
- int len = 0;
- for (int i = 0; i < columns.size(); i++) {
- len += get_column_width(i);
- if (pos.x < len) {
- cache.hover_type = Cache::CLICK_TITLE;
- cache.hover_index = i;
- break;
- }
- }
- }
- }
-
- if (root) {
- Point2 mpos = mm->get_position();
- if (rtl) {
- mpos.x = get_size().width - mpos.x;
- }
- mpos -= theme_cache.panel_style->get_offset();
- mpos.y -= _get_title_button_height();
- if (mpos.y >= 0) {
- if (h_scroll->is_visible_in_tree()) {
- mpos.x += h_scroll->get_value();
- }
- if (v_scroll->is_visible_in_tree()) {
- mpos.y += v_scroll->get_value();
- }
-
- TreeItem *old_it = cache.hover_item;
- int old_col = cache.hover_cell;
-
- int col, h, section;
- TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
-
- if (drop_mode_flags) {
- if (it != drop_mode_over) {
- drop_mode_over = it;
- queue_redraw();
- }
- if (it && section != drop_mode_section) {
- drop_mode_section = section;
- queue_redraw();
- }
- }
-
- cache.hover_item = it;
- cache.hover_cell = col;
-
- if (it != old_it || col != old_col) {
- if (old_it && old_col >= old_it->cells.size()) {
- // Columns may have changed since last redraw().
- queue_redraw();
- } else {
- // Only need to update if mouse enters/exits a button
- bool was_over_button = old_it && old_it->cells[old_col].custom_button;
- bool is_over_button = it && it->cells[col].custom_button;
- if (was_over_button || is_over_button) {
- queue_redraw();
- }
- }
- }
- }
- }
-
- // Update if mouse enters/exits columns
- if (cache.hover_type != old_hover || cache.hover_index != old_index) {
- queue_redraw();
- }
+ hovered_pos = mm->get_position();
+ _determine_hovered_item();
+ bool rtl = is_layout_rtl();
if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
/* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
popup_edited_item = popup_pressing_edited_item;
@@ -4080,6 +4044,174 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
+void Tree::_determine_hovered_item() {
+ Ref<StyleBox> bg = theme_cache.panel_style;
+ bool rtl = is_layout_rtl();
+
+ Point2 pos = hovered_pos;
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= theme_cache.panel_style->get_offset();
+
+ bool old_header_row = cache.hover_header_row;
+ int old_header_column = cache.hover_header_column;
+ TreeItem *old_item = cache.hover_item;
+ int old_column = cache.hover_column;
+ int old_button_index_in_column = cache.hover_button_index_in_column;
+
+ // Determine hover on column headers.
+ cache.hover_header_row = false;
+ cache.hover_header_column = 0;
+ if (show_column_titles && is_mouse_hovering) {
+ pos.y -= _get_title_button_height();
+ if (pos.y < 0) {
+ pos.x += theme_cache.offset.x;
+ int len = 0;
+ for (int i = 0; i < columns.size(); i++) {
+ len += get_column_width(i);
+ if (pos.x < len) {
+ cache.hover_header_row = true;
+ cache.hover_header_column = i;
+ cache.hover_button_index_in_column = -1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Determine hover on rows and items.
+ if (root && is_mouse_hovering) {
+ Point2 mpos = hovered_pos;
+ if (rtl) {
+ mpos.x = get_size().width - mpos.x;
+ }
+ mpos -= theme_cache.panel_style->get_offset();
+ mpos.y -= _get_title_button_height();
+ if (mpos.y >= 0) {
+ if (h_scroll->is_visible_in_tree()) {
+ mpos.x += h_scroll->get_value();
+ }
+ if (v_scroll->is_visible_in_tree()) {
+ mpos.y += v_scroll->get_value();
+ }
+
+ int col, h, section;
+ TreeItem *it = _find_item_at_pos(root, mpos, col, h, section);
+
+ // Find possible hovered button in cell.
+ int col_button_index = -1;
+
+ Point2 cpos = mpos;
+
+ if (it) {
+ const TreeItem::Cell &c = it->cells[col];
+ int col_width = get_column_width(col);
+
+ // In the first column, tree nesting indent impacts the leftmost possible buttons position
+ // and the clickable area of the folding arrow.
+ int col_indent = 0;
+ if (col == 0) {
+ col_indent = _get_item_h_offset(it);
+ }
+
+ // Compute total width of buttons block including spacings.
+ int buttons_width = 0;
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ buttons_width += size.width + theme_cache.button_margin;
+ }
+
+ // Adjust when buttons are shifted left into view so that they remain visible even
+ // if part of the cell is beyond the right border due to horizontal scrolling and
+ // a long string in one of the items. This matches the drawing & click handling algorithms
+ // that are based on recursion.
+ int clamped_column_offset = 0;
+ int col_left = 0;
+
+ for (int i = 0; i < col; i++) {
+ int i_col_w = get_column_width(i);
+ cpos.x -= i_col_w;
+ col_left += i_col_w;
+ }
+ col_left -= theme_cache.offset.x;
+
+ // Compute buttons offset that makes them visible, in comparison to what would be their
+ // natural position that would cut them off.
+ if (!rtl) {
+ const Rect2 content_rect = _get_content_rect();
+ int cw = content_rect.size.width;
+ int col_right = col_left + col_width;
+ if (col_right > cw) {
+ clamped_column_offset = col_right - cw - theme_cache.scrollbar_h_separation;
+ int max_clamp_offset = col_width - col_indent - buttons_width;
+ if (clamped_column_offset > max_clamp_offset) {
+ clamped_column_offset = max_clamp_offset;
+ }
+ }
+ }
+ col_width -= clamped_column_offset;
+
+ // Find the actual button under coordinates.
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size();
+ if (cpos.x > col_width - size.width && col_button_index == -1) {
+ col_button_index = j;
+ }
+ col_width -= size.width + theme_cache.button_margin;
+ }
+ }
+
+ if (drop_mode_flags) {
+ if (it != drop_mode_over) {
+ drop_mode_over = it;
+ queue_redraw();
+ }
+ if (it && section != drop_mode_section) {
+ drop_mode_section = section;
+ queue_redraw();
+ }
+ }
+
+ cache.hover_item = it;
+ cache.hover_column = col;
+ cache.hover_button_index_in_column = col_button_index;
+
+ if (it != old_item || col != old_column) {
+ if (old_item && old_column >= old_item->cells.size()) {
+ // Columns may have changed since last redraw().
+ queue_redraw();
+ } else {
+ // Only need to update if mouse enters/exits a button.
+ bool was_over_button = old_item && old_item->cells[old_column].custom_button;
+ bool is_over_button = it && it->cells[col].custom_button;
+ if (was_over_button || is_over_button) {
+ queue_redraw();
+ }
+ }
+ }
+ }
+ }
+
+ // Reduce useless redraw calls.
+
+ bool hovered_cell_button_changed = (cache.hover_button_index_in_column != old_button_index_in_column);
+ bool hovered_column_changed = (cache.hover_column != old_column);
+
+ // Mouse has moved from row to row, or from cell to cell within same row unless selection mode is full row which saves a useless redraw.
+ bool item_hover_needs_redraw = !cache.hover_header_row && (cache.hover_item != old_item || hovered_cell_button_changed || (select_mode != SELECT_ROW && hovered_column_changed));
+ // Mouse has moved between two different column header sections.
+ bool header_hover_needs_redraw = cache.hover_header_row && cache.hover_header_column != old_header_column;
+ // Mouse has moved between header and "main" areas.
+ bool whole_needs_redraw = cache.hover_header_row != old_header_row;
+
+ if (whole_needs_redraw || header_hover_needs_redraw || item_hover_needs_redraw) {
+ queue_redraw();
+ }
+}
+
bool Tree::edit_selected(bool p_force_edit) {
TreeItem *s = get_selected();
ERR_FAIL_NULL_V_MSG(s, false, "No item selected.");
@@ -4304,9 +4436,20 @@ void Tree::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ is_mouse_hovering = true;
+ _determine_hovered_item();
+ } break;
+
case NOTIFICATION_MOUSE_EXIT: {
- if (cache.hover_type != Cache::CLICK_NONE) {
- cache.hover_type = Cache::CLICK_NONE;
+ is_mouse_hovering = false;
+ // Clear hovered item cache.
+ if (cache.hover_header_row || cache.hover_item != nullptr) {
+ cache.hover_header_row = false;
+ cache.hover_header_column = -1;
+ cache.hover_item = nullptr;
+ cache.hover_column = -1;
+ cache.hover_button_index_in_column = -1;
queue_redraw();
}
} break;
@@ -4420,7 +4563,7 @@ void Tree::_notification(int p_what) {
//title buttons
int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT);
for (int i = 0; i < columns.size(); i++) {
- Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button);
+ Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_header_row && cache.hover_header_column == i) ? theme_cache.title_button_hover : theme_cache.title_button);
Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh);
if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
@@ -4536,6 +4679,8 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) {
}
}
+ _determine_hovered_item();
+
return ti;
}
@@ -4679,6 +4824,8 @@ void Tree::clear() {
popup_edited_item = nullptr;
popup_pressing_edited_item = nullptr;
+ _determine_hovered_item();
+
queue_redraw();
};
@@ -4931,6 +5078,7 @@ int Tree::get_columns() const {
}
void Tree::_scroll_moved(float) {
+ _determine_hovered_item();
queue_redraw();
}
@@ -4972,7 +5120,47 @@ int Tree::get_item_offset(TreeItem *p_item) const {
}
}
- return -1; //not found
+ return -1; // Not found.
+}
+
+int Tree::_get_item_h_offset(TreeItem *p_item) const {
+ TreeItem *it = root;
+ int nesting_level = 0;
+ if (!it) {
+ return 0;
+ }
+
+ while (true) {
+ if (it == p_item) {
+ if (!hide_root) {
+ nesting_level += 1;
+ }
+ if (hide_folding) {
+ nesting_level -= 1;
+ }
+ return nesting_level * theme_cache.item_margin;
+ }
+
+ if (it->first_child && !it->collapsed) {
+ it = it->first_child;
+ nesting_level += 1;
+
+ } else if (it->next) {
+ it = it->next;
+ } else {
+ while (!it->next) {
+ it = it->parent;
+ nesting_level -= 1;
+ if (it == nullptr) {
+ return 0;
+ }
+ }
+
+ it = it->next;
+ }
+ }
+
+ return -1; // Not found.
}
void Tree::ensure_cursor_is_visible() {
@@ -5803,10 +5991,13 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT, Tree, tb_font, "title_button_font");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT_SIZE, Tree, tb_font_size, "title_button_font_size");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, hovered_dimmed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, selected_focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Tree, cursor_unfocus, "cursor_unfocused");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_hover);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Tree, button_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Tree, checked);
@@ -5827,6 +6018,8 @@ void Tree::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, custom_button_font_highlight);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_color);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_hovered_dimmed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_selected_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Tree, drop_position_color);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 86efdfec52..b417b7ee31 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -450,6 +450,9 @@ private:
Vector2 pressing_pos;
Rect2 pressing_item_rect;
+ Vector2 hovered_pos;
+ bool is_mouse_hovering = false;
+
float range_drag_base = 0.0;
bool range_drag_enabled = false;
Vector2 range_drag_capture_pos;
@@ -545,10 +548,13 @@ private:
int font_size = 0;
int tb_font_size = 0;
+ Ref<StyleBox> hovered;
+ Ref<StyleBox> hovered_dimmed;
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
Ref<StyleBox> cursor;
Ref<StyleBox> cursor_unfocus;
+ Ref<StyleBox> button_hover;
Ref<StyleBox> button_pressed;
Ref<StyleBox> title_button;
Ref<StyleBox> title_button_hover;
@@ -572,6 +578,8 @@ private:
Ref<Texture2D> updown;
Color font_color;
+ Color font_hovered_color;
+ Color font_hovered_dimmed_color;
Color font_selected_color;
Color font_disabled_color;
Color guide_color;
@@ -623,16 +631,17 @@ private:
};
ClickType click_type = Cache::CLICK_NONE;
- ClickType hover_type = Cache::CLICK_NONE;
int click_index = -1;
int click_id = -1;
TreeItem *click_item = nullptr;
int click_column = 0;
- int hover_index = -1;
+ int hover_header_column = -1;
+ bool hover_header_row = false;
Point2 click_pos;
TreeItem *hover_item = nullptr;
- int hover_cell = -1;
+ int hover_column = -1;
+ int hover_button_index_in_column = -1;
bool rtl = false;
} cache;
@@ -660,6 +669,7 @@ private:
TreeItem *_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards = false);
TreeItem *_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const;
+ int _get_item_h_offset(TreeItem *p_item) const;
void _find_button_at_pos(const Point2 &p_pos, TreeItem *&r_item, int &r_column, int &r_index) const;
@@ -689,6 +699,8 @@ private:
bool enable_recursive_folding = true;
+ void _determine_hovered_item();
+
int _count_selected_items(TreeItem *p_from) const;
bool _is_branch_selected(TreeItem *p_from) const;
bool _is_sibling_branch_selected(TreeItem *p_from) const;
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index c0386b056f..7c8bf9c809 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -1253,7 +1253,7 @@ void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("force_update_transform"), &CanvasItem::force_update_transform);
- ClassDB::bind_method(D_METHOD("make_canvas_position_local", "screen_point"), &CanvasItem::make_canvas_position_local);
+ ClassDB::bind_method(D_METHOD("make_canvas_position_local", "viewport_point"), &CanvasItem::make_canvas_position_local);
ClassDB::bind_method(D_METHOD("make_input_local", "event"), &CanvasItem::make_input_local);
ClassDB::bind_method(D_METHOD("set_visibility_layer", "layer"), &CanvasItem::set_visibility_layer);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 0cdb23618f..8755d5f51e 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1921,7 +1921,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(mpos));
tooltip = tooltip.strip_edges();
- if (tooltip.is_empty() || tooltip != gui.tooltip_text) {
+ if (tooltip != gui.tooltip_text) {
_gui_cancel_tooltip();
} else {
is_tooltip_shown = true;
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 803ce89bc9..045c3ae02d 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -2635,7 +2635,7 @@ void Window::set_unparent_when_invisible(bool p_unparent) {
void Window::set_layout_direction(Window::LayoutDirection p_direction) {
ERR_MAIN_THREAD_GUARD;
- ERR_FAIL_INDEX((int)p_direction, 4);
+ ERR_FAIL_INDEX(p_direction, LAYOUT_DIRECTION_MAX);
layout_dir = p_direction;
propagate_notification(Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED);
@@ -2700,13 +2700,20 @@ bool Window::is_layout_rtl() const {
String locale = TranslationServer::get_singleton()->get_tool_locale();
return TS->is_locale_right_to_left(locale);
}
- } else if (layout_dir == LAYOUT_DIRECTION_LOCALE) {
+ } else if (layout_dir == LAYOUT_DIRECTION_APPLICATION_LOCALE) {
if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
return true;
} else {
String locale = TranslationServer::get_singleton()->get_tool_locale();
return TS->is_locale_right_to_left(locale);
}
+ } else if (layout_dir == LAYOUT_DIRECTION_SYSTEM_LOCALE) {
+ if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) {
+ return true;
+ } else {
+ String locale = OS::get_singleton()->get_locale();
+ return TS->is_locale_right_to_left(locale);
+ }
} else {
return (layout_dir == LAYOUT_DIRECTION_RTL);
}
@@ -3001,6 +3008,7 @@ void Window::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "popup_window"), "set_flag", "get_flag", FLAG_POPUP);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "extend_to_title"), "set_flag", "get_flag", FLAG_EXTEND_TO_TITLE);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "mouse_passthrough"), "set_flag", "get_flag", FLAG_MOUSE_PASSTHROUGH);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "sharp_corners"), "set_flag", "get_flag", FLAG_SHARP_CORNERS);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_native"), "set_force_native", "get_force_native");
ADD_GROUP("Limits", "");
@@ -3054,6 +3062,7 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_POPUP);
BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH);
+ BIND_ENUM_CONSTANT(FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED);
@@ -3070,9 +3079,14 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_INTEGER);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
- BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_APPLICATION_LOCALE);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_RTL);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_SYSTEM_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_MAX);
+#ifndef DISABLE_DEPRECATED
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+#endif // DISABLE_DEPRECATED
BIND_ENUM_CONSTANT(WINDOW_INITIAL_POSITION_ABSOLUTE);
BIND_ENUM_CONSTANT(WINDOW_INITIAL_POSITION_CENTER_PRIMARY_SCREEN);
diff --git a/scene/main/window.h b/scene/main/window.h
index 47aaf73728..6517350b78 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -62,6 +62,7 @@ public:
FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP,
FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE,
FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH,
+ FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS,
FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX,
};
@@ -86,9 +87,14 @@ public:
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
- LAYOUT_DIRECTION_LOCALE,
+ LAYOUT_DIRECTION_APPLICATION_LOCALE,
LAYOUT_DIRECTION_LTR,
- LAYOUT_DIRECTION_RTL
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_SYSTEM_LOCALE,
+ LAYOUT_DIRECTION_MAX,
+#ifndef DISABLE_DEPRECATED
+ LAYOUT_DIRECTION_LOCALE = LAYOUT_DIRECTION_APPLICATION_LOCALE,
+#endif // DISABLE_DEPRECATED
};
enum {
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
index 94a037bd9b..f068e34beb 100644
--- a/scene/property_utils.cpp
+++ b/scene/property_utils.cpp
@@ -89,6 +89,16 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const
*r_is_valid = false;
}
+ // Handle special case "script" property, where the default value is either null or the custom type script.
+ // Do this only if there's no states stack cache to trace for default values.
+ if (!p_states_stack_cache && p_property == CoreStringName(script) && p_object->has_meta(SceneStringName(_custom_type_script))) {
+ Ref<Script> ct_scr = p_object->get_meta(SceneStringName(_custom_type_script));
+ if (r_is_valid) {
+ *r_is_valid = true;
+ }
+ return ct_scr;
+ }
+
Ref<Script> topmost_script;
if (const Node *node = Object::cast_to<Node>(p_object)) {
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index 229e18be23..e624bdb32f 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -6480,9 +6480,9 @@ int TileData::get_terrain_set() const {
}
void TileData::set_terrain(int p_terrain) {
- ERR_FAIL_COND(terrain_set < 0);
ERR_FAIL_COND(p_terrain < -1);
- if (tile_set) {
+ ERR_FAIL_COND(terrain_set < 0 && p_terrain != -1);
+ if (tile_set && terrain_set >= 0) {
ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set));
}
terrain = p_terrain;
@@ -6495,9 +6495,9 @@ int TileData::get_terrain() const {
void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) {
ERR_FAIL_INDEX(p_peering_bit, TileSet::CellNeighbor::CELL_NEIGHBOR_MAX);
- ERR_FAIL_COND(terrain_set < 0);
ERR_FAIL_COND(p_terrain_index < -1);
- if (tile_set) {
+ ERR_FAIL_COND(terrain_set < 0 && p_terrain_index != -1);
+ if (tile_set && terrain_set >= 0) {
ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set));
ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit));
}
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 47cd64f19a..fa2532a310 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -503,6 +503,7 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
merged_normals_f32.ptr(),
sizeof(float) * 3, // Attribute stride
normal_weights, 3,
+ nullptr, // Vertex lock
index_target,
max_mesh_error,
simplify_options,
diff --git a/scene/resources/external_texture.cpp b/scene/resources/external_texture.cpp
index 0552bbd081..c8b714372a 100644
--- a/scene/resources/external_texture.cpp
+++ b/scene/resources/external_texture.cpp
@@ -39,12 +39,14 @@ void ExternalTexture::_bind_methods() {
}
uint64_t ExternalTexture::get_external_texture_id() const {
+ _ensure_created();
return RenderingServer::get_singleton()->texture_get_native_handle(texture);
}
void ExternalTexture::set_size(const Size2 &p_size) {
if (p_size.width > 0 && p_size.height > 0 && p_size != size) {
size = p_size;
+ _ensure_created();
RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer);
emit_changed();
}
@@ -57,6 +59,7 @@ Size2 ExternalTexture::get_size() const {
void ExternalTexture::set_external_buffer_id(uint64_t p_external_buffer) {
if (p_external_buffer != external_buffer) {
external_buffer = p_external_buffer;
+ _ensure_created();
RenderingServer::get_singleton()->texture_external_update(texture, size.width, size.height, external_buffer);
}
}
@@ -74,11 +77,29 @@ bool ExternalTexture::has_alpha() const {
}
RID ExternalTexture::get_rid() const {
+ if (!texture.is_valid()) {
+ texture = RenderingServer::get_singleton()->texture_2d_placeholder_create();
+ using_placeholder = true;
+ }
return texture;
}
+void ExternalTexture::_ensure_created() const {
+ if (texture.is_valid() && !using_placeholder) {
+ return;
+ }
+
+ RID new_texture = RenderingServer::get_singleton()->texture_external_create(size.width, size.height);
+ if (using_placeholder) {
+ DEV_ASSERT(texture.is_valid());
+ RenderingServer::get_singleton()->texture_replace(texture, new_texture);
+ using_placeholder = false;
+ } else {
+ texture = new_texture;
+ }
+}
+
ExternalTexture::ExternalTexture() {
- texture = RenderingServer::get_singleton()->texture_external_create(size.width, size.height);
}
ExternalTexture::~ExternalTexture() {
diff --git a/scene/resources/external_texture.h b/scene/resources/external_texture.h
index 96bcd8d0fe..cd60bcc030 100644
--- a/scene/resources/external_texture.h
+++ b/scene/resources/external_texture.h
@@ -38,10 +38,13 @@ class ExternalTexture : public Texture2D {
GDCLASS(ExternalTexture, Texture2D);
private:
- RID texture;
+ mutable RID texture;
+ mutable bool using_placeholder = false;
Size2 size = Size2(256, 256);
uint64_t external_buffer = 0;
+ void _ensure_created() const;
+
protected:
static void _bind_methods();
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index e234a81c88..4a318a10f0 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1431,8 +1431,8 @@ void ResourceFormatLoaderText::get_recognized_extensions_for_type(const String &
p_extensions->push_back("tscn");
}
- // Don't allow .tres for PackedScenes.
- if (p_type != "PackedScene") {
+ // Don't allow .tres for PackedScenes or GDExtension.
+ if (p_type != "PackedScene" && p_type != "GDExtension") {
p_extensions->push_back("tres");
}
}
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index 24d17108d5..d163a42fa9 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -32,6 +32,7 @@
#include "shader.compat.inc"
#include "core/io/file_access.h"
+#include "scene/main/scene_tree.h"
#include "servers/rendering/shader_language.h"
#include "servers/rendering/shader_preprocessor.h"
#include "servers/rendering_server.h"
@@ -138,6 +139,14 @@ String Shader::get_code() const {
return code;
}
+void Shader::inspect_native_shader_code() {
+ SceneTree *st = SceneTree::get_singleton();
+ RID _shader = get_rid();
+ if (st && _shader.is_valid()) {
+ st->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, "_native_shader_source_visualizer", "_inspect_shader", _shader);
+ }
+}
+
void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups) const {
_update_shader();
_check_shader_rid();
@@ -267,6 +276,9 @@ void Shader::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_shader_uniform_list", "get_groups"), &Shader::_get_shader_uniform_list, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("inspect_native_shader_code"), &Shader::inspect_native_shader_code);
+ ClassDB::set_method_flags(get_class_static(), _scs_create("inspect_native_shader_code"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_code", "get_code");
BIND_ENUM_CONSTANT(MODE_SPATIAL);
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 18197419f3..7234d37579 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -88,6 +88,8 @@ public:
void set_code(const String &p_code);
String get_code() const;
+ void inspect_native_shader_code();
+
void get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups = false) const;
void set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index = 0);
diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp
index 15816925c1..202ab3615b 100644
--- a/scene/resources/style_box_flat.cpp
+++ b/scene/resources/style_box_flat.cpp
@@ -596,10 +596,10 @@ void StyleBoxFlat::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "skew"), "set_skew", "get_skew");
ADD_GROUP("Border Width", "border_width_");
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_TOP);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_border_width", "get_border_width", SIDE_BOTTOM);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_left", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_border_width", "get_border_width", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_top", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_border_width", "get_border_width", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_right", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_border_width", "get_border_width", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "border_width_bottom", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_border_width", "get_border_width", SIDE_BOTTOM);
ADD_GROUP("Border", "border_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color");
@@ -607,18 +607,18 @@ void StyleBoxFlat::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "border_blend"), "set_border_blend", "get_border_blend");
ADD_GROUP("Corner Radius", "corner_radius_");
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,1024,1,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_left", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_top_right", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_TOP_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_right", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "corner_radius_bottom_left", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_corner_radius", "get_corner_radius", CORNER_BOTTOM_LEFT);
ADD_PROPERTY(PropertyInfo(Variant::INT, "corner_detail", PROPERTY_HINT_RANGE, "1,20,1"), "set_corner_detail", "get_corner_detail");
ADD_GROUP("Expand Margins", "expand_margin_");
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,2048,1,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_left", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_top", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_right", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "expand_margin_bottom", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_expand_margin", "get_expand_margin", SIDE_BOTTOM);
ADD_GROUP("Shadow", "shadow_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "shadow_color"), "set_shadow_color", "get_shadow_color");
diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h
index a072df5bee..5200eba2a8 100644
--- a/scene/resources/surface_tool.h
+++ b/scene/resources/surface_tool.h
@@ -80,13 +80,19 @@ public:
enum {
/* Do not move vertices that are located on the topological border (vertices on triangle edges that don't have a paired triangle). Useful for simplifying portions of the larger mesh. */
SIMPLIFY_LOCK_BORDER = 1 << 0, // From meshopt_SimplifyLockBorder
+ /* Improve simplification performance assuming input indices are a sparse subset of the mesh. Note that error becomes relative to subset extents. */
+ SIMPLIFY_SPARSE = 1 << 1, // From meshopt_SimplifySparse
+ /* Treat error limit and resulting error as absolute instead of relative to mesh extents. */
+ SIMPLIFY_ERROR_ABSOLUTE = 1 << 2, // From meshopt_SimplifyErrorAbsolute
+ /* Remove disconnected parts of the mesh during simplification incrementally, regardless of the topological restrictions inside components. */
+ SIMPLIFY_PRUNE = 1 << 3, // From meshopt_SimplifyPrune
};
typedef void (*OptimizeVertexCacheFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, size_t vertex_count);
static OptimizeVertexCacheFunc optimize_vertex_cache_func;
typedef size_t (*SimplifyFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float *r_error);
static SimplifyFunc simplify_func;
- typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, const float *attributes, size_t attribute_stride, const float *attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float *result_error);
+ typedef size_t (*SimplifyWithAttribFunc)(unsigned int *destination, const unsigned int *indices, size_t index_count, const float *vertex_data, size_t vertex_count, size_t vertex_stride, const float *attributes, size_t attribute_stride, const float *attribute_weights, size_t attribute_count, const unsigned char *vertex_lock, size_t target_index_count, float target_error, unsigned int options, float *result_error);
static SimplifyWithAttribFunc simplify_with_attrib_func;
typedef float (*SimplifyScaleFunc)(const float *vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
static SimplifyScaleFunc simplify_scale_func;
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 140e588291..31daeb3ae3 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -130,6 +130,8 @@ SceneStringNames::SceneStringNames() {
shader_overrides_group = StaticCString::create("_shader_overrides_group_");
shader_overrides_group_active = StaticCString::create("_shader_overrides_group_active_");
+ _custom_type_script = StaticCString::create("_custom_type_script");
+
pressed = StaticCString::create("pressed");
id_pressed = StaticCString::create("id_pressed");
toggled = StaticCString::create("toggled");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index fc22be33b2..0a2ebeda7a 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -143,6 +143,8 @@ public:
StringName shader_overrides_group;
StringName shader_overrides_group_active;
+ StringName _custom_type_script;
+
StringName pressed;
StringName id_pressed;
StringName toggled;
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index caf44ac392..f5065e8de1 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -30,6 +30,7 @@
#include "default_theme.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "default_font.gen.h"
#include "default_theme_icons.gen.h"
@@ -832,10 +833,13 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox(SceneStringName(panel), "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_stylebox("focus", "Tree", focus);
+ theme->set_stylebox("hovered", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
+ theme->set_stylebox("hovered_dimmed", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.03)));
theme->set_stylebox("selected", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("selected_focus", "Tree", make_flat_stylebox(style_selected_color));
theme->set_stylebox("cursor", "Tree", focus);
theme->set_stylebox("cursor_unfocused", "Tree", focus);
+ theme->set_stylebox("button_hover", "Tree", make_flat_stylebox(Color(1, 1, 1, 0.07)));
theme->set_stylebox("button_pressed", "Tree", button_pressed);
theme->set_stylebox("title_button_normal", "Tree", make_flat_stylebox(style_pressed_color, 4, 4, 4, 4));
theme->set_stylebox("title_button_pressed", "Tree", make_flat_stylebox(style_hover_color, 4, 4, 4, 4));
@@ -863,6 +867,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("title_button_color", "Tree", control_font_color);
theme->set_color(SceneStringName(font_color), "Tree", control_font_low_color);
+ theme->set_color("font_hovered_color", "Tree", control_font_hover_color);
+ theme->set_color("font_hovered_dimmed_color", "Tree", control_font_color);
theme->set_color("font_selected_color", "Tree", control_font_pressed_color);
theme->set_color("font_disabled_color", "Tree", control_font_disabled_color);
theme->set_color("font_outline_color", "Tree", Color(0, 0, 0));
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 1886ebe1ac..d400b5790f 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -76,6 +76,42 @@ int AudioStreamPlayback::mix(AudioFrame *p_buffer, float p_rate_scale, int p_fra
return ret;
}
+PackedVector2Array AudioStreamPlayback::_mix_audio_bind(float p_rate_scale, int p_frames) {
+ Vector<AudioFrame> frames = mix_audio(p_rate_scale, p_frames);
+
+ PackedVector2Array res;
+ res.resize(frames.size());
+
+ Vector2 *res_ptrw = res.ptrw();
+ for (int i = 0; i < frames.size(); i++) {
+ res_ptrw[i] = Vector2(frames[i].left, frames[i].right);
+ }
+
+ return res;
+}
+
+Vector<AudioFrame> AudioStreamPlayback::mix_audio(float p_rate_scale, int p_frames) {
+ Vector<AudioFrame> res;
+ res.resize(p_frames);
+
+ int frames = mix(res.ptrw(), p_rate_scale, p_frames);
+ res.resize(frames);
+
+ return res;
+}
+
+void AudioStreamPlayback::start_playback(double p_from_pos) {
+ start(p_from_pos);
+}
+
+void AudioStreamPlayback::stop_playback() {
+ stop();
+}
+
+void AudioStreamPlayback::seek_playback(double p_time) {
+ seek(p_time);
+}
+
void AudioStreamPlayback::tag_used_streams() {
GDVIRTUAL_CALL(_tag_used_streams);
}
@@ -108,6 +144,13 @@ void AudioStreamPlayback::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_sample_playback", "playback_sample"), &AudioStreamPlayback::set_sample_playback);
ClassDB::bind_method(D_METHOD("get_sample_playback"), &AudioStreamPlayback::get_sample_playback);
+ ClassDB::bind_method(D_METHOD("mix_audio", "rate_scale", "frames"), &AudioStreamPlayback::_mix_audio_bind);
+ ClassDB::bind_method(D_METHOD("start", "from_pos"), &AudioStreamPlayback::start_playback, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("seek", "time"), &AudioStreamPlayback::seek_playback, DEFVAL(0.0));
+ ClassDB::bind_method(D_METHOD("stop"), &AudioStreamPlayback::stop_playback);
+ ClassDB::bind_method(D_METHOD("get_loop_count"), &AudioStreamPlayback::get_loop_count);
+ ClassDB::bind_method(D_METHOD("get_playback_position"), &AudioStreamPlayback::get_playback_position);
+ ClassDB::bind_method(D_METHOD("is_playing"), &AudioStreamPlayback::is_playing);
}
AudioStreamPlayback::AudioStreamPlayback() {}
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 3feaa53630..65efccdc28 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -83,6 +83,7 @@ class AudioStreamPlayback : public RefCounted {
protected:
static void _bind_methods();
+ PackedVector2Array _mix_audio_bind(float p_rate_scale, int p_frames);
GDVIRTUAL1(_start, double)
GDVIRTUAL0(_stop)
GDVIRTUAL0RC(bool, _is_playing)
@@ -118,6 +119,11 @@ public:
AudioStreamPlayback();
~AudioStreamPlayback();
+
+ Vector<AudioFrame> mix_audio(float p_rate_scale, int p_frames);
+ void start_playback(double p_from_pos = 0.0);
+ void stop_playback();
+ void seek_playback(double p_time);
};
class AudioStreamPlaybackResampled : public AudioStreamPlayback {
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 70ef88e36d..17b573ab7b 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -1440,6 +1440,10 @@ uint64_t AudioServer::get_mixed_frames() const {
return mix_frames;
}
+String AudioServer::get_driver_name() const {
+ return AudioDriver::get_singleton()->get_name();
+}
+
void AudioServer::notify_listener_changed() {
for (CallbackItem *ci : listener_changed_callback_list) {
ci->callback(ci->userdata);
@@ -1947,6 +1951,8 @@ void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
+ ClassDB::bind_method(D_METHOD("get_driver_name"), &AudioServer::get_driver_name);
+
ClassDB::bind_method(D_METHOD("get_output_device_list"), &AudioServer::get_output_device_list);
ClassDB::bind_method(D_METHOD("get_output_device"), &AudioServer::get_output_device);
ClassDB::bind_method(D_METHOD("set_output_device", "name"), &AudioServer::set_output_device);
diff --git a/servers/audio_server.h b/servers/audio_server.h
index 16fcc029b3..d4e1aa9995 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -427,6 +427,8 @@ public:
uint64_t get_mix_count() const;
uint64_t get_mixed_frames() const;
+ String get_driver_name() const;
+
void notify_listener_changed();
virtual void init();
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index ce0d6cb996..dc5752cf4d 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1128,6 +1128,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_FLAG_POPUP);
BIND_ENUM_CONSTANT(WINDOW_FLAG_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MOUSE_PASSTHROUGH);
+ BIND_ENUM_CONSTANT(WINDOW_FLAG_SHARP_CORNERS);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX);
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER);
@@ -1148,6 +1149,8 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_HANDLE);
BIND_ENUM_CONSTANT(WINDOW_VIEW);
BIND_ENUM_CONSTANT(OPENGL_CONTEXT);
+ BIND_ENUM_CONSTANT(EGL_DISPLAY);
+ BIND_ENUM_CONSTANT(EGL_CONFIG);
BIND_ENUM_CONSTANT(TTS_UTTERANCE_STARTED);
BIND_ENUM_CONSTANT(TTS_UTTERANCE_ENDED);
@@ -1229,6 +1232,10 @@ void DisplayServer::_input_set_custom_mouse_cursor_func(const Ref<Resource> &p_i
}
bool DisplayServer::can_create_rendering_device() {
+ if (get_singleton()->get_name() == "headless") {
+ return false;
+ }
+
#if defined(RD_ENABLED)
RenderingDevice *device = RenderingDevice::get_singleton();
if (device) {
diff --git a/servers/display_server.h b/servers/display_server.h
index 36798bd011..5726f26792 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -32,6 +32,7 @@
#define DISPLAY_SERVER_H
#include "core/input/input.h"
+#include "core/io/image.h"
#include "core/io/resource.h"
#include "core/os/os.h"
#include "core/variant/callable.h"
@@ -39,7 +40,6 @@
#include "display/native_menu.h"
class Texture2D;
-class Image;
class DisplayServer : public Object {
GDCLASS(DisplayServer, Object)
@@ -82,6 +82,8 @@ public:
WINDOW_HANDLE,
WINDOW_VIEW,
OPENGL_CONTEXT,
+ EGL_DISPLAY,
+ EGL_CONFIG,
};
enum Context {
@@ -381,6 +383,7 @@ public:
WINDOW_FLAG_POPUP,
WINDOW_FLAG_EXTEND_TO_TITLE,
WINDOW_FLAG_MOUSE_PASSTHROUGH,
+ WINDOW_FLAG_SHARP_CORNERS,
WINDOW_FLAG_MAX,
};
@@ -394,6 +397,7 @@ public:
WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP),
WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE),
WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH),
+ WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS),
};
virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID);
diff --git a/servers/movie_writer/movie_writer.h b/servers/movie_writer/movie_writer.h
index e1dc8ef8cf..69d6b1ba2b 100644
--- a/servers/movie_writer/movie_writer.h
+++ b/servers/movie_writer/movie_writer.h
@@ -31,6 +31,7 @@
#ifndef MOVIE_WRITER_H
#define MOVIE_WRITER_H
+#include "core/io/image.h"
#include "core/templates/local_vector.h"
#include "servers/audio/audio_driver_dummy.h"
#include "servers/audio_server.h"
diff --git a/servers/rendering/dummy/storage/light_storage.h b/servers/rendering/dummy/storage/light_storage.h
index c3b63cdbf6..d25523753c 100644
--- a/servers/rendering/dummy/storage/light_storage.h
+++ b/servers/rendering/dummy/storage/light_storage.h
@@ -78,6 +78,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override {}
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override {}
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override {}
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override {}
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override { return 0xFFFFFFFF; }
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override {}
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override {}
diff --git a/servers/rendering/renderer_rd/environment/fog.cpp b/servers/rendering/renderer_rd/environment/fog.cpp
index 2dfcd67411..903d73ff2b 100644
--- a/servers/rendering/renderer_rd/environment/fog.cpp
+++ b/servers/rendering/renderer_rd/environment/fog.cpp
@@ -622,6 +622,7 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
bool any_uses_time = false;
+ Vector3 cam_position = p_cam_transform.get_origin();
for (int i = 0; i < (int)p_fog_volumes.size(); i++) {
FogVolumeInstance *fog_volume_instance = fog_volume_instance_owner.get_or_null(p_fog_volumes[i]);
@@ -652,41 +653,68 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
any_uses_time |= shader_data->uses_time;
- Vector3i min;
- Vector3i max;
+ Vector3i froxel_min;
+ Vector3i froxel_max;
Vector3i kernel_size;
- Vector3 position = fog_volume_instance->transform.get_origin();
+ Vector3 fog_position = fog_volume_instance->transform.get_origin();
RS::FogVolumeShape volume_type = RendererRD::Fog::get_singleton()->fog_volume_get_shape(fog_volume);
Vector3 extents = RendererRD::Fog::get_singleton()->fog_volume_get_size(fog_volume) / 2;
if (volume_type != RS::FOG_VOLUME_SHAPE_WORLD) {
// Local fog volume.
- Vector3i points[8];
Vector3 fog_size = Vector3(fog->width, fog->height, fog->depth);
float volumetric_fog_detail_spread = RendererSceneRenderRD::get_singleton()->environment_get_volumetric_fog_detail_spread(p_settings.env);
- points[0] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[1] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[2] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[3] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[4] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[5] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[6] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
- points[7] = _point_get_position_in_froxel_volume(fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, -extents.z)), fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
-
- min = Vector3i(int32_t(fog->width) - 1, int32_t(fog->height) - 1, int32_t(fog->depth) - 1);
- max = Vector3i(1, 1, 1);
-
+ Vector3 corners[8]{
+ fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(extents.x, -extents.y, -extents.z)),
+ fog_volume_instance->transform.xform(Vector3(-extents.x, -extents.y, -extents.z))
+ };
+ Vector3i froxels[8];
+ Vector3 corner_min = corners[0];
+ Vector3 corner_max = corners[0];
for (int j = 0; j < 8; j++) {
- min = min.min(points[j]);
- max = max.max(points[j]);
+ froxels[j] = _point_get_position_in_froxel_volume(corners[j], fog_end, fog_near_size, fog_far_size, volumetric_fog_detail_spread, fog_size, p_cam_transform);
+ corner_min = corner_min.min(corners[j]);
+ corner_max = corner_max.max(corners[j]);
+ }
+
+ froxel_min = Vector3i(int32_t(fog->width) - 1, int32_t(fog->height) - 1, int32_t(fog->depth) - 1);
+ froxel_max = Vector3i(1, 1, 1);
+
+ // Tracking just the corners of the fog volume can result in missing some fog:
+ // when the camera's near plane is inside the fog, we must always consider the entire screen
+ Vector3 near_plane_corner(frustum_near_size.x, frustum_near_size.y, z_near);
+ float expand = near_plane_corner.length();
+ if (cam_position.x > (corner_min.x - expand) && cam_position.x < (corner_max.x + expand) &&
+ cam_position.y > (corner_min.y - expand) && cam_position.y < (corner_max.y + expand) &&
+ cam_position.z > (corner_min.z - expand) && cam_position.z < (corner_max.z + expand)) {
+ froxel_min.x = 0;
+ froxel_min.y = 0;
+ froxel_min.z = 0;
+ froxel_max.x = int32_t(fog->width);
+ froxel_max.y = int32_t(fog->height);
+ for (int j = 0; j < 8; j++) {
+ froxel_max.z = MAX(froxel_max.z, froxels[j].z);
+ }
+ } else {
+ // Camera is guaranteed to be outside the fog volume
+ for (int j = 0; j < 8; j++) {
+ froxel_min = froxel_min.min(froxels[j]);
+ froxel_max = froxel_max.max(froxels[j]);
+ }
}
- kernel_size = max - min;
+ kernel_size = froxel_max - froxel_min;
} else {
// Volume type global runs on all cells
extents = Vector3(fog->width, fog->height, fog->depth);
- min = Vector3i(0, 0, 0);
+ froxel_min = Vector3i(0, 0, 0);
kernel_size = Vector3i(int32_t(fog->width), int32_t(fog->height), int32_t(fog->depth));
}
@@ -695,15 +723,15 @@ void Fog::volumetric_fog_update(const VolumetricFogSettings &p_settings, const P
}
VolumetricFogShader::FogPushConstant push_constant;
- push_constant.position[0] = position.x;
- push_constant.position[1] = position.y;
- push_constant.position[2] = position.z;
+ push_constant.position[0] = fog_position.x;
+ push_constant.position[1] = fog_position.y;
+ push_constant.position[2] = fog_position.z;
push_constant.size[0] = extents.x * 2;
push_constant.size[1] = extents.y * 2;
push_constant.size[2] = extents.z * 2;
- push_constant.corner[0] = min.x;
- push_constant.corner[1] = min.y;
- push_constant.corner[2] = min.z;
+ push_constant.corner[0] = froxel_min.x;
+ push_constant.corner[1] = froxel_min.y;
+ push_constant.corner[2] = froxel_min.z;
push_constant.shape = uint32_t(RendererRD::Fog::get_singleton()->fog_volume_get_shape(fog_volume));
RendererRD::MaterialStorage::store_transform(fog_volume_instance->transform.affine_inverse(), push_constant.transform);
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 53982af590..532b5b6484 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -610,6 +610,8 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.renames["PI"] = _MKSTR(Math_PI);
actions.renames["TAU"] = _MKSTR(Math_TAU);
actions.renames["E"] = _MKSTR(Math_E);
+ actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["VIEWPORT_SIZE"] = "read_viewport_size";
actions.renames["FRAGCOORD"] = "gl_FragCoord";
@@ -649,8 +651,6 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.renames["CUSTOM1"] = "custom1_attrib";
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
- actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
- actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index 69f084f4c0..6dcaadddd3 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -521,6 +521,8 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.renames["PI"] = _MKSTR(Math_PI);
actions.renames["TAU"] = _MKSTR(Math_TAU);
actions.renames["E"] = _MKSTR(Math_E);
+ actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["VIEWPORT_SIZE"] = "read_viewport_size";
actions.renames["FRAGCOORD"] = "gl_FragCoord";
@@ -560,8 +562,6 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.renames["CUSTOM1"] = "custom1_attrib";
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
- actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
- actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h
index dcd3e90e1b..2547f08715 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.h
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h
@@ -31,6 +31,7 @@
#ifndef RENDERER_COMPOSITOR_RD_H
#define RENDERER_COMPOSITOR_RD_H
+#include "core/io/image.h"
#include "core/os/os.h"
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/renderer_rd/environment/fog.h"
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 0c21fec282..dc2605b670 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -31,6 +31,7 @@
#include "renderer_scene_render_rd.h"
#include "core/config/project_settings.h"
+#include "core/io/image.h"
#include "core/os/os.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/environment/fog.h"
diff --git a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
index a797891ab6..90bbdfe685 100644
--- a/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
+++ b/servers/rendering/renderer_rd/shaders/environment/volumetric_fog_process.glsl
@@ -513,6 +513,7 @@ void main() {
shadow_sample.z = 1.0 + abs(shadow_sample.z);
vec3 pos = vec3(shadow_sample.xy / shadow_sample.z, shadow_len - omni_lights.data[light_index].shadow_bias);
pos.z *= omni_lights.data[light_index].inv_radius;
+ pos.z = 1.0 - pos.z;
pos.xy = pos.xy * 0.5 + 0.5;
pos.xy = uv_rect.xy + pos.xy * uv_rect.zw;
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index 9d47711599..2f0e4e0bea 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -105,10 +105,6 @@ layout(location = 6) mediump out vec3 binormal_interp;
layout(location = 7) highp out vec4 diffuse_light_interp;
layout(location = 8) highp out vec4 specular_light_interp;
-layout(constant_id = 9) const bool sc_disable_omni_lights = false;
-layout(constant_id = 10) const bool sc_disable_spot_lights = false;
-layout(constant_id = 12) const bool sc_disable_directional_lights = false;
-
#include "../scene_forward_vertex_lights_inc.glsl"
#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED) && defined(USE_VERTEX_LIGHTING)
#ifdef MATERIAL_UNIFORMS_USED
@@ -1606,7 +1602,6 @@ void main() {
#endif
#undef BIAS_FUNC
}
-#endif
if (i < 4) {
shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
@@ -1614,6 +1609,7 @@ void main() {
shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
}
}
+#endif // SHADOWS_DISABLED
#ifndef USE_VERTEX_LIGHTING
for (uint i = 0; i < scene_data.directional_light_count; i++) {
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index 8f71909154..3639b5739b 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -285,6 +285,23 @@ void LightStorage::light_set_reverse_cull_face_mode(RID p_light, bool p_enabled)
light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
}
+void LightStorage::light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL(light);
+
+ light->shadow_caster_mask = p_caster_mask;
+
+ light->version++;
+ light->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_LIGHT);
+}
+
+uint32_t LightStorage::light_get_shadow_caster_mask(RID p_light) const {
+ Light *light = light_owner.get_or_null(p_light);
+ ERR_FAIL_NULL_V(light, 0);
+
+ return light->shadow_caster_mask;
+}
+
void LightStorage::light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) {
Light *light = light_owner.get_or_null(p_light);
ERR_FAIL_NULL(light);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 59303e8a73..80c62a7bc4 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -72,6 +72,7 @@ private:
RS::LightBakeMode bake_mode = RS::LIGHT_BAKE_DYNAMIC;
uint32_t max_sdfgi_cascade = 2;
uint32_t cull_mask = 0xFFFFFFFF;
+ uint32_t shadow_caster_mask = 0xFFFFFFFF;
bool distance_fade = false;
real_t distance_fade_begin = 40.0;
real_t distance_fade_shadow = 50.0;
@@ -480,6 +481,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) override;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) override;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) override;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) override;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const override;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) override;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) override;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index 9dc606620b..42fce65b2d 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -518,6 +518,32 @@ TextureStorage::TextureStorage() {
rt_sdf.pipelines[i] = RD::get_singleton()->compute_pipeline_create(rt_sdf.shader.version_get_shader(rt_sdf.shader_version, i));
}
}
+
+ // Initialize texture placeholder data for the `texture_*_placeholder_initialize()` methods.
+
+ constexpr int placeholder_size = 4;
+ texture_2d_placeholder = Image::create_empty(placeholder_size, placeholder_size, false, Image::FORMAT_RGBA8);
+ // Draw a magenta/black checkerboard pattern.
+ for (int i = 0; i < placeholder_size * placeholder_size; i++) {
+ const int x = i % placeholder_size;
+ const int y = i / placeholder_size;
+ texture_2d_placeholder->set_pixel(x, y, (x + y) % 2 == 0 ? Color(1, 0, 1) : Color(0, 0, 0));
+ }
+
+ texture_2d_array_placeholder.push_back(texture_2d_placeholder);
+
+ for (int i = 0; i < 6; i++) {
+ cubemap_placeholder.push_back(texture_2d_placeholder);
+ }
+
+ Ref<Image> texture_2d_placeholder_rotated;
+ texture_2d_placeholder_rotated.instantiate();
+ texture_2d_placeholder_rotated->copy_from(texture_2d_placeholder);
+ texture_2d_placeholder_rotated->rotate_90(CLOCKWISE);
+ for (int i = 0; i < 4; i++) {
+ // Alternate checkerboard pattern on odd layers (by using a copy that is rotated 90 degrees).
+ texture_3d_placeholder.push_back(i % 2 == 0 ? texture_2d_placeholder : texture_2d_placeholder_rotated);
+ }
}
TextureStorage::~TextureStorage() {
@@ -1365,46 +1391,19 @@ void TextureStorage::texture_proxy_update(RID p_texture, RID p_proxy_to) {
//these two APIs can be used together or in combination with the others.
void TextureStorage::texture_2d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- texture_2d_initialize(p_texture, image);
+ texture_2d_initialize(p_texture, texture_2d_placeholder);
}
void TextureStorage::texture_2d_layered_placeholder_initialize(RID p_texture, RS::TextureLayeredType p_layered_type) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
if (p_layered_type == RS::TEXTURE_LAYERED_2D_ARRAY) {
- images.push_back(image);
+ texture_2d_layered_initialize(p_texture, texture_2d_array_placeholder, p_layered_type);
} else {
- //cube
- for (int i = 0; i < 6; i++) {
- images.push_back(image);
- }
+ texture_2d_layered_initialize(p_texture, cubemap_placeholder, p_layered_type);
}
-
- texture_2d_layered_initialize(p_texture, images, p_layered_type);
}
void TextureStorage::texture_3d_placeholder_initialize(RID p_texture) {
- //this could be better optimized to reuse an existing image , done this way
- //for now to get it working
- Ref<Image> image = Image::create_empty(4, 4, false, Image::FORMAT_RGBA8);
- image->fill(Color(1, 0, 1, 1));
-
- Vector<Ref<Image>> images;
- //cube
- for (int i = 0; i < 4; i++) {
- images.push_back(image);
- }
-
- texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, images);
+ texture_3d_initialize(p_texture, Image::FORMAT_RGBA8, 4, 4, 4, false, texture_3d_placeholder);
}
Ref<Image> TextureStorage::texture_2d_get(RID p_texture) const {
@@ -2269,6 +2268,16 @@ void TextureStorage::_texture_format_from_rd(RD::DataFormat p_rd_format, Texture
r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
} break;
+ case RD::DATA_FORMAT_B8G8R8A8_UNORM:
+ case RD::DATA_FORMAT_B8G8R8A8_SRGB: {
+ r_format.image_format = Image::FORMAT_RGBA8;
+ r_format.rd_format = RD::DATA_FORMAT_B8G8R8A8_UNORM;
+ r_format.rd_format_srgb = RD::DATA_FORMAT_B8G8R8A8_SRGB;
+ r_format.swizzle_r = RD::TEXTURE_SWIZZLE_R;
+ r_format.swizzle_g = RD::TEXTURE_SWIZZLE_G;
+ r_format.swizzle_b = RD::TEXTURE_SWIZZLE_B;
+ r_format.swizzle_a = RD::TEXTURE_SWIZZLE_A;
+ } break;
case RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16: {
r_format.image_format = Image::FORMAT_RGBA4444;
r_format.rd_format = RD::DATA_FORMAT_B4G4R4A4_UNORM_PACK16;
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
index 2135ee3e3b..866fdd50ac 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h
@@ -520,6 +520,11 @@ public:
virtual void texture_external_update(RID p_texture, int p_width, int p_height, uint64_t p_external_buffer) override;
virtual void texture_proxy_update(RID p_proxy, RID p_base) override;
+ Ref<Image> texture_2d_placeholder;
+ Vector<Ref<Image>> texture_2d_array_placeholder;
+ Vector<Ref<Image>> cubemap_placeholder;
+ Vector<Ref<Image>> texture_3d_placeholder;
+
//these two APIs can be used together or in combination with the others.
virtual void texture_2d_placeholder_initialize(RID p_texture) override;
virtual void texture_2d_layered_placeholder_initialize(RID p_texture, RenderingServer::TextureLayeredType p_layered_type) override;
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 8b7ec08868..45f980ab41 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1998,6 +1998,9 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
pair.bvh2 = &p_instance->scenario->indexers[Scenario::INDEXER_VOLUMES];
}
pair.cull_mask = RSG::light_storage->light_get_cull_mask(p_instance->base);
+ } else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
+ pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
+ pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
} else if (geometry_instance_pair_mask & (1 << RS::INSTANCE_REFLECTION_PROBE) && (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE)) {
pair.pair_mask = RS::INSTANCE_GEOMETRY_MASK;
pair.bvh = &p_instance->scenario->indexers[Scenario::INDEXER_GEOMETRY];
@@ -2296,6 +2299,7 @@ void RendererSceneCull::_light_instance_setup_directional_shadow(int p_shadow_in
cull.shadow_count = p_shadow_index + 1;
cull.shadows[p_shadow_index].cascade_count = splits;
cull.shadows[p_shadow_index].light_instance = light->instance;
+ cull.shadows[p_shadow_index].caster_mask = RSG::light_storage->light_get_shadow_caster_mask(p_instance->base);
for (int i = 0; i < splits; i++) {
RENDER_TIMESTAMP("Cull DirectionalLight3D, Split " + itos(i));
@@ -2526,7 +2530,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -2608,7 +2612,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -2675,7 +2679,7 @@ bool RendererSceneCull::_light_instance_update_shadow(Instance *p_instance, cons
for (int j = 0; j < (int)instance_shadow_cull_result.size(); j++) {
Instance *instance = instance_shadow_cull_result[j];
- if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask)) {
+ if (!instance->visible || !((1 << instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) || !static_cast<InstanceGeometryData *>(instance->base_data)->can_cast_shadows || !(p_visible_layers & instance->layer_mask & RSG::light_storage->light_get_shadow_caster_mask(p_instance->base))) {
continue;
} else {
if (static_cast<InstanceGeometryData *>(instance->base_data)->material_is_animated) {
@@ -3033,6 +3037,10 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
for (const Instance *E : geom->lights) {
InstanceLightData *light = static_cast<InstanceLightData *>(E->base_data);
+ if (!(RSG::light_storage->light_get_cull_mask(E->base) & idata.layer_mask)) {
+ continue;
+ }
+
instance_pair_buffer[idx++] = light->instance;
if (idx == MAX_INSTANCE_PAIRS) {
break;
@@ -3137,7 +3145,7 @@ void RendererSceneCull::_scene_cull(CullData &cull_data, InstanceCullResult &cul
if (IN_FRUSTUM(cull_data.cull->shadows[j].cascades[k].frustum) && VIS_CHECK) {
uint32_t base_type = idata.flags & InstanceData::FLAG_BASE_TYPE_MASK;
- if (((1 << base_type) & RS::INSTANCE_GEOMETRY_MASK) && idata.flags & InstanceData::FLAG_CAST_SHADOWS && LAYER_CHECK) {
+ if (((1 << base_type) & RS::INSTANCE_GEOMETRY_MASK) && idata.flags & InstanceData::FLAG_CAST_SHADOWS && (LAYER_CHECK & cull_data.cull->shadows[j].caster_mask)) {
cull_result.directional_shadows[j].cascade_geometry_instances[k].push_back(idata.instance_geometry);
mesh_visible = true;
}
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 5aae59eb51..d3f03c4789 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1121,6 +1121,7 @@ public:
struct Cull {
struct Shadow {
RID light_instance;
+ uint32_t caster_mask;
struct Cascade {
Frustum frustum;
diff --git a/servers/rendering/renderer_scene_occlusion_cull.h b/servers/rendering/renderer_scene_occlusion_cull.h
index df403c5484..0ef92dd9ff 100644
--- a/servers/rendering/renderer_scene_occlusion_cull.h
+++ b/servers/rendering/renderer_scene_occlusion_cull.h
@@ -72,7 +72,7 @@ public:
return false;
}
- float min_depth = -closest_point_view.z * 0.95f;
+ float min_depth = (closest_point - p_cam_position).length();
Vector2 rect_min = Vector2(FLT_MAX, FLT_MAX);
Vector2 rect_max = Vector2(FLT_MIN, FLT_MIN);
@@ -83,6 +83,10 @@ public:
Vector3 corner = Vector3(p_bounds[0] * c.x + p_bounds[3] * nc.x, p_bounds[1] * c.y + p_bounds[4] * nc.y, p_bounds[2] * c.z + p_bounds[5] * nc.z);
Vector3 view = p_cam_inv_transform.xform(corner);
+ if (p_cam_projection.is_orthogonal()) {
+ min_depth = MIN(min_depth, view.z);
+ }
+
Plane vp = Plane(view, 1.0);
Plane projected = p_cam_projection.xform4(vp);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index b287550986..6eb1386749 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -1243,6 +1243,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
TransferWorker *transfer_worker = nullptr;
const uint8_t *read_ptr = p_data.ptr();
uint8_t *write_ptr = nullptr;
+ const RDD::TextureLayout copy_dst_layout = driver->api_trait_get(RDD::API_TRAIT_USE_GENERAL_IN_COPY_QUEUES) ? RDD::TEXTURE_LAYOUT_GENERAL : RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
for (uint32_t pass = 0; pass < 2; pass++) {
const bool copy_pass = (pass == 1);
if (copy_pass) {
@@ -1267,7 +1268,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
tb.texture = texture->driver_id;
tb.dst_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
tb.prev_layout = RDD::TEXTURE_LAYOUT_UNDEFINED;
- tb.next_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.next_layout = copy_dst_layout;
tb.subresources.aspect = texture->barrier_aspect_flags;
tb.subresources.mipmap_count = texture->mipmaps;
tb.subresources.base_layer = p_layer;
@@ -1313,7 +1314,7 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
copy_region.texture_subresources.layer_count = 1;
copy_region.texture_offset = Vector3i(0, 0, z);
copy_region.texture_region_size = Vector3i(logic_width, logic_height, 1);
- driver->command_copy_buffer_to_texture(transfer_worker->command_buffer, transfer_worker->staging_buffer, texture->driver_id, RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL, copy_region);
+ driver->command_copy_buffer_to_texture(transfer_worker->command_buffer, transfer_worker->staging_buffer, texture->driver_id, copy_dst_layout, copy_region);
}
staging_local_offset += to_allocate;
@@ -1332,14 +1333,13 @@ Error RenderingDevice::_texture_initialize(RID p_texture, uint32_t p_layer, cons
RDD::TextureBarrier tb;
tb.texture = texture->driver_id;
tb.src_access = RDD::BARRIER_ACCESS_COPY_WRITE_BIT;
- tb.prev_layout = RDD::TEXTURE_LAYOUT_COPY_DST_OPTIMAL;
+ tb.prev_layout = copy_dst_layout;
tb.next_layout = RDD::TEXTURE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
tb.subresources.aspect = texture->barrier_aspect_flags;
tb.subresources.mipmap_count = texture->mipmaps;
tb.subresources.base_layer = p_layer;
tb.subresources.layer_count = 1;
-
- driver->command_pipeline_barrier(transfer_worker->command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {}, {}, tb);
+ transfer_worker->texture_barriers.push_back(tb);
}
_release_transfer_worker(transfer_worker);
@@ -5052,7 +5052,6 @@ RenderingDevice::TransferWorker *RenderingDevice::_acquire_transfer_worker(uint3
// No existing worker was picked, we create a new one.
transfer_worker = memnew(TransferWorker);
transfer_worker->command_fence = driver->fence_create();
- transfer_worker->command_semaphore = driver->semaphore_create();
transfer_worker->command_pool = driver->command_pool_create(transfer_queue_family, RDD::COMMAND_BUFFER_TYPE_PRIMARY);
transfer_worker->command_buffer = driver->command_buffer_create(transfer_worker->command_pool);
transfer_worker->index = transfer_worker_pool.size();
@@ -5075,7 +5074,7 @@ RenderingDevice::TransferWorker *RenderingDevice::_acquire_transfer_worker(uint3
// If there's not enough bytes to use on the staging buffer, we submit everything pending from the worker and wait for the work to be finished.
if (transfer_worker->recording) {
_end_transfer_worker(transfer_worker);
- _submit_transfer_worker(transfer_worker, false);
+ _submit_transfer_worker(transfer_worker);
}
if (transfer_worker->submitted) {
@@ -5128,12 +5127,12 @@ void RenderingDevice::_end_transfer_worker(TransferWorker *p_transfer_worker) {
p_transfer_worker->recording = false;
}
-void RenderingDevice::_submit_transfer_worker(TransferWorker *p_transfer_worker, bool p_signal_semaphore) {
- const VectorView<RDD::SemaphoreID> execute_semaphore = p_signal_semaphore ? p_transfer_worker->command_semaphore : VectorView<RDD::SemaphoreID>();
- driver->command_queue_execute_and_present(transfer_queue, {}, p_transfer_worker->command_buffer, execute_semaphore, p_transfer_worker->command_fence, {});
- if (p_signal_semaphore) {
+void RenderingDevice::_submit_transfer_worker(TransferWorker *p_transfer_worker, VectorView<RDD::SemaphoreID> p_signal_semaphores) {
+ driver->command_queue_execute_and_present(transfer_queue, {}, p_transfer_worker->command_buffer, p_signal_semaphores, p_transfer_worker->command_fence, {});
+
+ for (uint32_t i = 0; i < p_signal_semaphores.size(); i++) {
// Indicate the frame should wait on these semaphores before executing the main command buffer.
- frames[frame].semaphores_to_wait_on.push_back(p_transfer_worker->command_semaphore);
+ frames[frame].semaphores_to_wait_on.push_back(p_signal_semaphores[i]);
}
p_transfer_worker->submitted = true;
@@ -5153,6 +5152,21 @@ void RenderingDevice::_wait_for_transfer_worker(TransferWorker *p_transfer_worke
MutexLock lock(p_transfer_worker->operations_mutex);
p_transfer_worker->operations_processed = p_transfer_worker->operations_submitted;
}
+
+ if (!p_transfer_worker->texture_barriers.is_empty()) {
+ MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
+ _flush_barriers_for_transfer_worker(p_transfer_worker);
+ }
+}
+
+void RenderingDevice::_flush_barriers_for_transfer_worker(TransferWorker *p_transfer_worker) {
+ if (!p_transfer_worker->texture_barriers.is_empty()) {
+ for (uint32_t i = 0; i < p_transfer_worker->texture_barriers.size(); i++) {
+ transfer_worker_pool_texture_barriers.push_back(p_transfer_worker->texture_barriers[i]);
+ }
+
+ p_transfer_worker->texture_barriers.clear();
+ }
}
void RenderingDevice::_check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation) {
@@ -5194,10 +5208,11 @@ void RenderingDevice::_check_transfer_worker_index_array(IndexArray *p_index_arr
}
}
-void RenderingDevice::_submit_transfer_workers(bool p_operations_used_by_draw) {
+void RenderingDevice::_submit_transfer_workers(RDD::CommandBufferID p_draw_command_buffer) {
MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
- for (TransferWorker *worker : transfer_worker_pool) {
- if (p_operations_used_by_draw) {
+ for (uint32_t i = 0; i < transfer_worker_pool.size(); i++) {
+ TransferWorker *worker = transfer_worker_pool[i];
+ if (p_draw_command_buffer) {
MutexLock lock(worker->operations_mutex);
if (worker->operations_processed >= transfer_worker_operation_used_by_draw[worker->index]) {
// The operation used by the draw has already been processed, we don't need to wait on the worker.
@@ -5208,11 +5223,21 @@ void RenderingDevice::_submit_transfer_workers(bool p_operations_used_by_draw) {
{
MutexLock lock(worker->thread_mutex);
if (worker->recording) {
+ VectorView<RDD::SemaphoreID> semaphores = p_draw_command_buffer ? frames[frame].transfer_worker_semaphores[i] : VectorView<RDD::SemaphoreID>();
_end_transfer_worker(worker);
- _submit_transfer_worker(worker, true);
+ _submit_transfer_worker(worker, semaphores);
+ }
+
+ if (p_draw_command_buffer) {
+ _flush_barriers_for_transfer_worker(worker);
}
}
}
+
+ if (p_draw_command_buffer && !transfer_worker_pool_texture_barriers.is_empty()) {
+ driver->command_pipeline_barrier(p_draw_command_buffer, RDD::PIPELINE_STAGE_COPY_BIT, RDD::PIPELINE_STAGE_ALL_COMMANDS_BIT, {}, {}, transfer_worker_pool_texture_barriers);
+ transfer_worker_pool_texture_barriers.clear();
+ }
}
void RenderingDevice::_wait_for_transfer_workers() {
@@ -5228,7 +5253,6 @@ void RenderingDevice::_wait_for_transfer_workers() {
void RenderingDevice::_free_transfer_workers() {
MutexLock transfer_worker_lock(transfer_worker_pool_mutex);
for (TransferWorker *worker : transfer_worker_pool) {
- driver->semaphore_free(worker->command_semaphore);
driver->fence_free(worker->command_fence);
driver->buffer_free(worker->staging_buffer);
driver->command_pool_free(worker->command_pool);
@@ -5807,10 +5831,10 @@ void RenderingDevice::_end_frame() {
ERR_PRINT("Found open compute list at the end of the frame, this should never happen (further compute will likely not work).");
}
- _submit_transfer_workers(true);
-
// The command buffer must be copied into a stack variable as the driver workarounds can change the command buffer in use.
RDD::CommandBufferID command_buffer = frames[frame].command_buffer;
+ _submit_transfer_workers(command_buffer);
+
draw_graph.end(RENDER_GRAPH_REORDER, RENDER_GRAPH_FULL_BARRIERS, command_buffer, frames[frame].command_buffer_pool);
driver->command_buffer_end(command_buffer);
driver->end_segment();
@@ -6014,6 +6038,9 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
present_queue_family = main_queue_family;
}
+ // Use the processor count as the max amount of transfer workers that can be created.
+ transfer_worker_pool_max_size = OS::get_singleton()->get_processor_count();
+
// Create data for all the frames.
for (uint32_t i = 0; i < frames.size(); i++) {
frames[i].index = 0;
@@ -6041,6 +6068,13 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
// Assign the main queue family and command pool to the command buffer pool.
frames[i].command_buffer_pool.pool = frames[i].command_pool;
+
+ // Create the semaphores for the transfer workers.
+ frames[i].transfer_worker_semaphores.resize(transfer_worker_pool_max_size);
+ for (uint32_t j = 0; j < transfer_worker_pool_max_size; j++) {
+ frames[i].transfer_worker_semaphores[j] = driver->semaphore_create();
+ ERR_FAIL_COND_V(!frames[i].transfer_worker_semaphores[j], FAILED);
+ }
}
// Start from frame count, so everything else is immediately old.
@@ -6087,9 +6121,6 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
ERR_FAIL_COND_V(err, FAILED);
}
- // TODO: How should this max size be determined?
- transfer_worker_pool_max_size = OS::get_singleton()->get_processor_count();
-
draw_list = nullptr;
compute_list = nullptr;
@@ -6380,7 +6411,7 @@ void RenderingDevice::finalize() {
}
// Wait for transfer workers to finish.
- _submit_transfer_workers(false);
+ _submit_transfer_workers();
_wait_for_transfer_workers();
// Delete everything the graph has created.
@@ -6452,6 +6483,10 @@ void RenderingDevice::finalize() {
for (uint32_t j = 0; j < buffer_pool.buffers.size(); j++) {
driver->semaphore_free(buffer_pool.semaphores[j]);
}
+
+ for (uint32_t j = 0; j < frames[i].transfer_worker_semaphores.size(); j++) {
+ driver->semaphore_free(frames[i].transfer_worker_semaphores[j]);
+ }
}
if (pipeline_cache_enabled) {
@@ -7225,6 +7260,10 @@ void RenderingDevice::_bind_methods() {
BIND_ENUM_CONSTANT(DEBUG_PASS);
}
+void RenderingDevice::make_current() {
+ render_thread_id = Thread::get_caller_id();
+}
+
RenderingDevice::~RenderingDevice() {
finalize();
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 19d21dfda2..c440e11cd4 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -1267,7 +1267,7 @@ private:
RDD::CommandBufferID command_buffer;
RDD::CommandPoolID command_pool;
RDD::FenceID command_fence;
- RDD::SemaphoreID command_semaphore;
+ LocalVector<RDD::TextureBarrier> texture_barriers;
bool recording = false;
bool submitted = false;
BinaryMutex thread_mutex;
@@ -1281,20 +1281,22 @@ private:
uint32_t transfer_worker_pool_max_size = 1;
LocalVector<uint64_t> transfer_worker_operation_used_by_draw;
LocalVector<uint32_t> transfer_worker_pool_available_list;
+ LocalVector<RDD::TextureBarrier> transfer_worker_pool_texture_barriers;
BinaryMutex transfer_worker_pool_mutex;
ConditionVariable transfer_worker_pool_condition;
TransferWorker *_acquire_transfer_worker(uint32_t p_transfer_size, uint32_t p_required_align, uint32_t &r_staging_offset);
void _release_transfer_worker(TransferWorker *p_transfer_worker);
void _end_transfer_worker(TransferWorker *p_transfer_worker);
- void _submit_transfer_worker(TransferWorker *p_transfer_worker, bool p_signal_semaphore);
+ void _submit_transfer_worker(TransferWorker *p_transfer_worker, VectorView<RDD::SemaphoreID> p_signal_semaphores = VectorView<RDD::SemaphoreID>());
void _wait_for_transfer_worker(TransferWorker *p_transfer_worker);
+ void _flush_barriers_for_transfer_worker(TransferWorker *p_transfer_worker);
void _check_transfer_worker_operation(uint32_t p_transfer_worker_index, uint64_t p_transfer_worker_operation);
void _check_transfer_worker_buffer(Buffer *p_buffer);
void _check_transfer_worker_texture(Texture *p_texture);
void _check_transfer_worker_vertex_array(VertexArray *p_vertex_array);
void _check_transfer_worker_index_array(IndexArray *p_index_array);
- void _submit_transfer_workers(bool p_operations_used_by_draw);
+ void _submit_transfer_workers(RDD::CommandBufferID p_draw_command_buffer = RDD::CommandBufferID());
void _wait_for_transfer_workers();
void _free_transfer_workers();
@@ -1372,6 +1374,10 @@ private:
// Swap chains prepared for drawing during the frame that must be presented.
LocalVector<RDD::SwapChainID> swap_chains_to_present;
+ // Semaphores the transfer workers can use to wait before rendering the frame.
+ // This must have the same size of the transfer worker pool.
+ TightLocalVector<RDD::SemaphoreID> transfer_worker_semaphores;
+
// Extra command buffer pool used for driver workarounds.
RDG::CommandBufferPool command_buffer_pool;
@@ -1490,6 +1496,8 @@ public:
static RenderingDevice *get_singleton();
+ void make_current();
+
RenderingDevice();
~RenderingDevice();
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index d9ca286b15..e41a56b0a3 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -31,7 +31,10 @@
#include "rendering_device_binds.h"
Error RDShaderFile::parse_versions_from_text(const String &p_text, const String p_defines, OpenIncludeFunction p_include_func, void *p_include_func_userdata) {
- ERR_FAIL_NULL_V(RenderingDevice::get_singleton(), ERR_UNAVAILABLE);
+ ERR_FAIL_NULL_V_MSG(
+ RenderingDevice::get_singleton(),
+ ERR_UNAVAILABLE,
+ "Cannot import custom .glsl shaders when running without a RenderingDevice. This can happen if you are using the headless more or the Compatibility backend.");
Vector<String> lines = p_text.split("\n");
diff --git a/servers/rendering/rendering_device_driver.cpp b/servers/rendering/rendering_device_driver.cpp
index 3b8e3efeb8..9ff7b83215 100644
--- a/servers/rendering/rendering_device_driver.cpp
+++ b/servers/rendering/rendering_device_driver.cpp
@@ -374,6 +374,8 @@ uint64_t RenderingDeviceDriver::api_trait_get(ApiTrait p_trait) {
return 1;
case API_TRAIT_CLEARS_WITH_COPY_ENGINE:
return true;
+ case API_TRAIT_USE_GENERAL_IN_COPY_QUEUES:
+ return false;
default:
ERR_FAIL_V(0);
}
diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h
index 91da67c8d7..637d52c060 100644
--- a/servers/rendering/rendering_device_driver.h
+++ b/servers/rendering/rendering_device_driver.h
@@ -220,6 +220,7 @@ public:
enum TextureLayout {
TEXTURE_LAYOUT_UNDEFINED,
+ TEXTURE_LAYOUT_GENERAL,
TEXTURE_LAYOUT_STORAGE_OPTIMAL,
TEXTURE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
TEXTURE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
@@ -750,6 +751,7 @@ public:
API_TRAIT_TEXTURE_DATA_ROW_PITCH_STEP,
API_TRAIT_SECONDARY_VIEWPORT_SCISSOR,
API_TRAIT_CLEARS_WITH_COPY_ENGINE,
+ API_TRAIT_USE_GENERAL_IN_COPY_QUEUES,
};
enum ShaderChangeInvalidation {
diff --git a/servers/rendering/rendering_device_graph.cpp b/servers/rendering/rendering_device_graph.cpp
index abcb76cd43..48bb235cac 100644
--- a/servers/rendering/rendering_device_graph.cpp
+++ b/servers/rendering/rendering_device_graph.cpp
@@ -438,6 +438,15 @@ void RenderingDeviceGraph::_add_command_to_graph(ResourceTracker **p_resource_tr
// Always update the access of the tracker according to the latest usage.
resource_tracker->usage_access = new_usage_access;
+ // Always accumulate the stages of the tracker with the commands that use it.
+ search_tracker->current_frame_stages = search_tracker->current_frame_stages | r_command->self_stages;
+
+ if (!search_tracker->previous_frame_stages.is_empty()) {
+ // Add to the command the stages the tracker was used on in the previous frame.
+ r_command->previous_stages = r_command->previous_stages | search_tracker->previous_frame_stages;
+ search_tracker->previous_frame_stages.clear();
+ }
+
if (different_usage) {
// Even if the usage of the resource isn't a write usage explicitly, a different usage implies a transition and it should therefore be considered a write.
write_usage = true;
@@ -874,7 +883,7 @@ void RenderingDeviceGraph::_run_label_command_change(RDD::CommandBufferID p_comm
}
if (p_ignore_previous_value || p_new_label_index != r_current_label_index || p_new_level != r_current_label_level) {
- if (!p_ignore_previous_value && (p_use_label_for_empty || r_current_label_index >= 0)) {
+ if (!p_ignore_previous_value && (p_use_label_for_empty || r_current_label_index >= 0 || r_current_label_level >= 0)) {
// End the current label.
driver->command_end_label(p_command_buffer);
}
@@ -888,6 +897,8 @@ void RenderingDeviceGraph::_run_label_command_change(RDD::CommandBufferID p_comm
} else if (p_use_label_for_empty) {
label_name = "Command graph";
label_color = Color(1, 1, 1, 1);
+ } else {
+ return;
}
// Add the level to the name.
@@ -1404,7 +1415,9 @@ void RenderingDeviceGraph::add_buffer_update(RDD::BufferID p_dst, ResourceTracke
void RenderingDeviceGraph::add_compute_list_begin(RDD::BreadcrumbMarker p_phase, uint32_t p_breadcrumb_data) {
compute_instruction_list.clear();
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
compute_instruction_list.breadcrumb = p_breadcrumb_data | (p_phase & ((1 << 16) - 1));
+#endif
compute_instruction_list.index++;
}
@@ -1500,7 +1513,9 @@ void RenderingDeviceGraph::add_draw_list_begin(RDD::RenderPassID p_render_pass,
draw_instruction_list.render_pass = p_render_pass;
draw_instruction_list.framebuffer = p_framebuffer;
draw_instruction_list.region = p_region;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
draw_instruction_list.breadcrumb = p_breadcrumb;
+#endif
draw_instruction_list.clear_values.resize(p_clear_values.size());
for (uint32_t i = 0; i < p_clear_values.size(); i++) {
draw_instruction_list.clear_values[i] = p_clear_values[i];
@@ -1711,7 +1726,9 @@ void RenderingDeviceGraph::add_draw_list_end() {
command->framebuffer = draw_instruction_list.framebuffer;
command->command_buffer_type = command_buffer_type;
command->region = draw_instruction_list.region;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
command->breadcrumb = draw_instruction_list.breadcrumb;
+#endif
command->clear_values_count = draw_instruction_list.clear_values.size();
RDD::RenderPassClearValue *clear_values = command->clear_values();
@@ -2064,7 +2081,7 @@ void RenderingDeviceGraph::end(bool p_reorder_commands, bool p_full_barriers, RD
}
}
- _run_label_command_change(r_command_buffer, -1, -1, true, false, nullptr, 0, current_label_index, current_label_level);
+ _run_label_command_change(r_command_buffer, -1, -1, false, false, nullptr, 0, current_label_index, current_label_level);
#if PRINT_COMMAND_RECORDING
print_line(vformat("Recorded %d commands", command_count));
diff --git a/servers/rendering/rendering_device_graph.h b/servers/rendering/rendering_device_graph.h
index e13e3a0429..89eb44494b 100644
--- a/servers/rendering/rendering_device_graph.h
+++ b/servers/rendering/rendering_device_graph.h
@@ -151,6 +151,8 @@ public:
struct ResourceTracker {
uint32_t reference_count = 0;
int64_t command_frame = -1;
+ BitField<RDD::PipelineStageBits> previous_frame_stages;
+ BitField<RDD::PipelineStageBits> current_frame_stages;
int32_t read_full_command_list_index = -1;
int32_t read_slice_command_list_index = -1;
int32_t write_command_or_list_index = -1;
@@ -174,8 +176,9 @@ public:
_FORCE_INLINE_ void reset_if_outdated(int64_t new_command_frame) {
if (new_command_frame != command_frame) {
- usage_access.clear();
command_frame = new_command_frame;
+ previous_frame_stages = current_frame_stages;
+ current_frame_stages.clear();
read_full_command_list_index = -1;
read_slice_command_list_index = -1;
write_command_or_list_index = -1;
@@ -218,14 +221,18 @@ private:
};
struct ComputeInstructionList : InstructionList {
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
uint32_t breadcrumb;
+#endif
};
struct DrawInstructionList : InstructionList {
RDD::RenderPassID render_pass;
RDD::FramebufferID framebuffer;
Rect2i region;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
uint32_t breadcrumb;
+#endif
LocalVector<RDD::RenderPassClearValue> clear_values;
};
@@ -314,7 +321,9 @@ private:
RDD::FramebufferID framebuffer;
RDD::CommandBufferType command_buffer_type;
Rect2i region;
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
uint32_t breadcrumb = 0;
+#endif
uint32_t clear_values_count = 0;
_FORCE_INLINE_ RDD::RenderPassClearValue *clear_values() {
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index 20f1f9ad6f..2ec693cbbf 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -370,6 +370,8 @@ Size2i RenderingServerDefault::get_maximum_viewport_size() const {
void RenderingServerDefault::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
server_thread = Thread::get_caller_id();
server_task_id = p_pump_task_id;
+ // This is needed because the main RD is created on the main thread.
+ RenderingDevice::get_singleton()->make_current();
}
void RenderingServerDefault::_thread_exit() {
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 225a67fb52..766ec8fa96 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -443,6 +443,7 @@ public:
FUNC2(light_set_cull_mask, RID, uint32_t)
FUNC5(light_set_distance_fade, RID, bool, float, float, float)
FUNC2(light_set_reverse_cull_face_mode, RID, bool)
+ FUNC2(light_set_shadow_caster_mask, RID, uint32_t)
FUNC2(light_set_bake_mode, RID, LightBakeMode)
FUNC2(light_set_max_sdfgi_cascade, RID, uint32_t)
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index b6770c773c..7c4128b0e3 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -356,7 +356,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_CF_BREAK, "break", CF_BLOCK, {}, {} },
{ TK_CF_CONTINUE, "continue", CF_BLOCK, {}, {} },
{ TK_CF_RETURN, "return", CF_BLOCK, {}, {} },
- { TK_CF_DISCARD, "discard", CF_BLOCK, { "particles", "sky", "fog" }, { "fragment" } },
+ { TK_CF_DISCARD, "discard", CF_BLOCK, { "particles", "sky", "fog" }, { "vertex" } },
// function specifier keywords
@@ -3565,28 +3565,33 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
int argcount = args.size();
- if (p_function_info.stage_functions.has(name)) {
- //stage based function
- const StageFunctionInfo &sf = p_function_info.stage_functions[name];
- if (argcount != sf.arguments.size()) {
- _set_error(vformat(RTR("Invalid number of arguments when calling stage function '%s', which expects %d arguments."), String(name), sf.arguments.size()));
- return false;
- }
- //validate arguments
- for (int i = 0; i < argcount; i++) {
- if (args[i] != sf.arguments[i].type) {
- _set_error(vformat(RTR("Invalid argument type when calling stage function '%s', type expected is '%s'."), String(name), get_datatype_name(sf.arguments[i].type)));
- return false;
- }
- }
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ if (E.value.stage_functions.has(name)) {
+ // Stage-based function.
+ const StageFunctionInfo &sf = E.value.stage_functions[name];
+ if (argcount != sf.arguments.size()) {
+ _set_error(vformat(RTR("Invalid number of arguments when calling stage function '%s', which expects %d arguments."), String(name), sf.arguments.size()));
+ return false;
+ }
+ // Validate arguments.
+ for (int i = 0; i < argcount; i++) {
+ if (args[i] != sf.arguments[i].type) {
+ _set_error(vformat(RTR("Invalid argument type when calling stage function '%s', type expected is '%s'."), String(name), get_datatype_name(sf.arguments[i].type)));
+ return false;
+ }
+ }
- if (r_ret_type) {
- *r_ret_type = sf.return_type;
- }
- if (r_ret_type_str) {
- *r_ret_type_str = "";
+ if (r_ret_type) {
+ *r_ret_type = sf.return_type;
+ }
+ if (r_ret_type_str) {
+ *r_ret_type_str = "";
+ }
+ return true;
+ }
}
- return true;
}
bool failed_builtin = false;
@@ -5937,22 +5942,35 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
calls_info[current_function].calls.push_back(&calls_info[name]);
}
- int idx = 0;
bool is_builtin = false;
- while (frag_only_func_defs[idx].name) {
- if (frag_only_func_defs[idx].name == name) {
- // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
- if (!is_supported_frag_only_funcs) {
- _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
- return nullptr;
+ if (is_supported_frag_only_funcs && stages) {
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ if (E.value.stage_functions.has(name)) {
+ // Register usage of the restricted stage function.
+ calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
+ is_builtin = true;
+ break;
}
- // Register usage of the restricted function.
- calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
- is_builtin = true;
- break;
}
- idx++;
+ }
+
+ if (!is_builtin) {
+ int idx = 0;
+ while (frag_only_func_defs[idx].name) {
+ if (frag_only_func_defs[idx].name == name) {
+ // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
+ if (!is_supported_frag_only_funcs) {
+ _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
+ return nullptr;
+ }
+ // Register usage of the restricted function.
+ calls_info[current_function].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>(name, CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, _get_tkpos())));
+ is_builtin = true;
+ break;
+ }
+ idx++;
+ }
}
// Recursively checks for the restricted function call.
@@ -8581,6 +8599,11 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
block = block->parent_block;
}
} else if (tk.type == TK_CF_DISCARD) {
+ if (!is_discard_supported) {
+ _set_error(vformat(RTR("Use of '%s' is not supported for the '%s' shader type."), "discard", shader_type_identifier));
+ return ERR_PARSE_ERROR;
+ }
+
//check return type
BlockNode *b = p_block;
while (b && !b->parent_function) {
@@ -8592,7 +8615,7 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
}
if (!b->parent_function->can_discard) {
- _set_error(vformat(RTR("Use of '%s' is not allowed here."), "discard"));
+ _set_error(vformat(RTR("'%s' cannot be used within the '%s' processor function."), "discard", b->parent_function->name));
return ERR_PARSE_ERROR;
}
@@ -8601,6 +8624,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
pos = _get_tkpos();
tk = _get_token();
+
+ calls_info[b->parent_function->name].uses_restricted_items.push_back(Pair<StringName, CallInfo::Item>("discard", CallInfo::Item(CallInfo::Item::ITEM_TYPE_BUILTIN, pos)));
+
if (tk.type != TK_SEMICOLON) {
_set_expected_after_error(";", "discard");
return ERR_PARSE_ERROR;
@@ -8838,7 +8864,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
ShaderNode::Uniform::Scope uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL;
stages = &p_functions;
- is_supported_frag_only_funcs = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial" || shader_type_identifier == "sky";
+
+ is_discard_supported = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial";
+ is_supported_frag_only_funcs = is_discard_supported || shader_type_identifier == "sky";
const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo();
@@ -10332,6 +10360,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
if (p_functions.has(name)) {
func_node->can_discard = p_functions[name].can_discard;
+ } else {
+ func_node->can_discard = is_discard_supported; // Allow use it for custom functions (in supported shader types).
}
if (!function_overload_count.has(name)) {
@@ -10922,10 +10952,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
break; // Ignore hint keywords (parsed below).
}
if (keyword_list[i].flags & keyword_completion_context) {
- if (keyword_list[i].excluded_shader_types.has(shader_type_identifier)) {
- continue;
- }
- if (!keyword_list[i].functions.is_empty() && !keyword_list[i].functions.has(current_function)) {
+ if (keyword_list[i].excluded_shader_types.has(shader_type_identifier) || keyword_list[i].excluded_functions.has(current_function)) {
continue;
}
ScriptLanguage::CodeCompletionOption option(keyword_list[i].text, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
@@ -11160,9 +11187,15 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
int idx = 0;
bool low_end = RenderingServer::get_singleton()->is_low_end();
- if (stages && stages->has(skip_function)) {
- for (const KeyValue<StringName, StageFunctionInfo> &E : (*stages)[skip_function].stage_functions) {
- matches.insert(String(E.key), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &E : *stages) {
+ for (const KeyValue<StringName, StageFunctionInfo> &F : E.value.stage_functions) {
+ if (F.value.skip_function == skip_function && stages->has(skip_function)) {
+ continue;
+ }
+ matches.insert(String(F.key), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ }
}
}
@@ -11292,9 +11325,15 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
return OK;
}
- if (stages && stages->has(block_function)) {
- for (const KeyValue<StringName, StageFunctionInfo> &E : (*stages)[block_function].stage_functions) {
- if (completion_function == E.key) {
+ if (stages) {
+ // Stage functions can be used in custom functions as well, that why need to check them all.
+ for (const KeyValue<StringName, FunctionInfo> &S : *stages) {
+ for (const KeyValue<StringName, StageFunctionInfo> &E : S.value.stage_functions) {
+ // No need to check for the skip function here.
+ if (completion_function != E.key) {
+ continue;
+ }
+
calltip += get_datatype_name(E.value.return_type);
calltip += " ";
calltip += E.key;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index fb0a526230..ddd4c41059 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -859,6 +859,7 @@ public:
Vector<Argument> arguments;
DataType return_type = TYPE_VOID;
+ String skip_function;
};
struct ModeInfo {
@@ -934,7 +935,7 @@ private:
const char *text;
uint32_t flags;
const Vector<String> excluded_shader_types;
- const Vector<String> functions;
+ const Vector<String> excluded_functions;
};
static const KeyWord keyword_list[];
@@ -1150,6 +1151,7 @@ private:
const HashMap<StringName, FunctionInfo> *stages = nullptr;
bool is_supported_frag_only_funcs = false;
+ bool is_discard_supported = false;
bool _get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier);
static const BuiltinFuncDef builtin_func_defs[];
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index 27e39551ba..0e41a178b5 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -393,6 +393,8 @@ void ShaderPreprocessor::process_directive(Tokenizer *p_tokenizer) {
process_else(p_tokenizer);
} else if (directive == "endif") {
process_endif(p_tokenizer);
+ } else if (directive == "error") {
+ process_error(p_tokenizer);
} else if (directive == "define") {
process_define(p_tokenizer);
} else if (directive == "undef") {
@@ -466,7 +468,7 @@ void ShaderPreprocessor::process_elif(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
if (state->current_branch == nullptr || state->current_branch->else_defined) {
- set_error(RTR("Unmatched elif."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "elif"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -523,7 +525,7 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
if (state->current_branch == nullptr || state->current_branch->else_defined) {
- set_error(RTR("Unmatched else."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "else"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -531,7 +533,7 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid else."), p_tokenizer->get_line());
+ set_error(vformat(RTR("Invalid '%s' directive."), "else"), line);
}
bool skip = false;
@@ -559,7 +561,7 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) {
state->condition_depth--;
if (state->condition_depth < 0) {
- set_error(RTR("Unmatched endif."), line);
+ set_error(vformat(RTR("Unmatched '%s' directive."), "endif"), line);
return;
}
if (state->previous_region != nullptr) {
@@ -568,13 +570,28 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid endif."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "endif"), line);
}
state->current_branch = state->current_branch->parent;
state->branches.pop_back();
}
+void ShaderPreprocessor::process_error(Tokenizer *p_tokenizer) {
+ const int line = p_tokenizer->get_line();
+
+ const String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges();
+ if (body.is_empty()) {
+ set_error(" ", line);
+ } else {
+ set_error(body, line);
+ }
+
+ if (!p_tokenizer->consume_empty_line()) {
+ set_error(vformat(RTR("Invalid '%s' directive."), "error"), line);
+ }
+}
+
void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
@@ -626,7 +643,7 @@ void ShaderPreprocessor::process_ifdef(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid ifdef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "ifdef"), line);
return;
}
@@ -648,7 +665,7 @@ void ShaderPreprocessor::process_ifndef(Tokenizer *p_tokenizer) {
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid ifndef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "ifndef"), line);
return;
}
@@ -771,21 +788,21 @@ void ShaderPreprocessor::process_pragma(Tokenizer *p_tokenizer) {
}
if (label.is_empty()) {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
- // Rxplicitly handle pragma values here.
+ // Explicitly handle pragma values here.
// If more pragma options are created, then refactor into a more defined structure.
if (label == "disable_preprocessor") {
state->disabled = true;
} else {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
if (!p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid pragma directive."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "pragma"), line);
return;
}
}
@@ -794,7 +811,7 @@ void ShaderPreprocessor::process_undef(Tokenizer *p_tokenizer) {
const int line = p_tokenizer->get_line();
const String label = p_tokenizer->get_identifier();
if (label.is_empty() || !p_tokenizer->consume_empty_line()) {
- set_error(RTR("Invalid undef."), line);
+ set_error(vformat(RTR("Invalid '%s' directive."), "undef"), line);
return;
}
@@ -1383,6 +1400,7 @@ void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_inclu
r_keywords->push_back("else");
}
r_keywords->push_back("endif");
+ r_keywords->push_back("error");
if (p_include_shader_keywords) {
r_keywords->push_back("if");
}
diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h
index b29239105a..0a90aec958 100644
--- a/servers/rendering/shader_preprocessor.h
+++ b/servers/rendering/shader_preprocessor.h
@@ -191,6 +191,7 @@ private:
void process_elif(Tokenizer *p_tokenizer);
void process_else(Tokenizer *p_tokenizer);
void process_endif(Tokenizer *p_tokenizer);
+ void process_error(Tokenizer *p_tokenizer);
void process_if(Tokenizer *p_tokenizer);
void process_ifdef(Tokenizer *p_tokenizer);
void process_ifndef(Tokenizer *p_tokenizer);
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index f498c0bf93..821009f07c 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -63,6 +63,8 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["constants"].built_ins["PI"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["constants"].built_ins["TAU"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["constants"].built_ins["E"] = constt(ShaderLanguage::TYPE_FLOAT);
+ shader_modes[RS::SHADER_SPATIAL].functions["constants"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
+ shader_modes[RS::SHADER_SPATIAL].functions["constants"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["VERTEX"] = ShaderLanguage::TYPE_VEC3;
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["NORMAL"] = ShaderLanguage::TYPE_VEC3;
@@ -96,8 +98,6 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["MODELVIEW_MATRIX"] = ShaderLanguage::TYPE_MAT4;
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["MODELVIEW_NORMAL_MATRIX"] = ShaderLanguage::TYPE_MAT3;
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["VIEWPORT_SIZE"] = constt(ShaderLanguage::TYPE_VEC2);
- shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
- shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["MAIN_CAM_INV_VIEW_MATRIX"] = constt(ShaderLanguage::TYPE_MAT4);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["NODE_POSITION_WORLD"] = constt(ShaderLanguage::TYPE_VEC3);
@@ -159,9 +159,6 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["VIEW_RIGHT"] = constt(ShaderLanguage::TYPE_INT);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["EYE_OFFSET"] = constt(ShaderLanguage::TYPE_VEC3);
- shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
- shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
-
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["MODEL_MATRIX"] = constt(ShaderLanguage::TYPE_MAT4);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["MODEL_NORMAL_MATRIX"] = constt(ShaderLanguage::TYPE_MAT3);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["VIEW_MATRIX"] = constt(ShaderLanguage::TYPE_MAT4);
@@ -203,8 +200,6 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ROUGHNESS"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["DIFFUSE_LIGHT"] = ShaderLanguage::TYPE_VEC3;
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["SPECULAR_LIGHT"] = ShaderLanguage::TYPE_VEC3;
- shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
- shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ALPHA"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[RS::SHADER_SPATIAL].functions["light"].can_discard = true;
@@ -284,6 +279,7 @@ ShaderTypes::ShaderTypes() {
{
ShaderLanguage::StageFunctionInfo func;
+ func.skip_function = "vertex";
func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("sdf_pos", ShaderLanguage::TYPE_VEC2));
func.return_type = ShaderLanguage::TYPE_FLOAT; //whether it could emit
shader_modes[RS::SHADER_CANVAS_ITEM].functions["fragment"].stage_functions["texture_sdf"] = func;
@@ -297,6 +293,7 @@ ShaderTypes::ShaderTypes() {
{
ShaderLanguage::StageFunctionInfo func;
+ func.skip_function = "vertex";
func.arguments.push_back(ShaderLanguage::StageFunctionInfo::Argument("uv", ShaderLanguage::TYPE_VEC2));
func.return_type = ShaderLanguage::TYPE_VEC2; //whether it could emit
shader_modes[RS::SHADER_CANVAS_ITEM].functions["fragment"].stage_functions["screen_uv_to_sdf"] = func;
diff --git a/servers/rendering/storage/light_storage.h b/servers/rendering/storage/light_storage.h
index 6a0adfa596..1e149e3a97 100644
--- a/servers/rendering/storage/light_storage.h
+++ b/servers/rendering/storage/light_storage.h
@@ -59,6 +59,8 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) = 0;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) = 0;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) = 0;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) = 0;
+ virtual uint32_t light_get_shadow_caster_mask(RID p_light) const = 0;
virtual void light_set_bake_mode(RID p_light, RS::LightBakeMode p_bake_mode) = 0;
virtual void light_set_max_sdfgi_cascade(RID p_light, uint32_t p_cascade) = 0;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 32ef5261f3..53dda24dc3 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2479,6 +2479,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("light_set_cull_mask", "light", "mask"), &RenderingServer::light_set_cull_mask);
ClassDB::bind_method(D_METHOD("light_set_distance_fade", "decal", "enabled", "begin", "shadow", "length"), &RenderingServer::light_set_distance_fade);
ClassDB::bind_method(D_METHOD("light_set_reverse_cull_face_mode", "light", "enabled"), &RenderingServer::light_set_reverse_cull_face_mode);
+ ClassDB::bind_method(D_METHOD("light_set_shadow_caster_mask", "light", "mask"), &RenderingServer::light_set_shadow_caster_mask);
ClassDB::bind_method(D_METHOD("light_set_bake_mode", "light", "bake_mode"), &RenderingServer::light_set_bake_mode);
ClassDB::bind_method(D_METHOD("light_set_max_sdfgi_cascade", "light", "cascade"), &RenderingServer::light_set_max_sdfgi_cascade);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 0208a640a5..6c1e1274d4 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -542,6 +542,7 @@ public:
virtual void light_set_cull_mask(RID p_light, uint32_t p_mask) = 0;
virtual void light_set_distance_fade(RID p_light, bool p_enabled, float p_begin, float p_shadow, float p_length) = 0;
virtual void light_set_reverse_cull_face_mode(RID p_light, bool p_enabled) = 0;
+ virtual void light_set_shadow_caster_mask(RID p_light, uint32_t p_caster_mask) = 0;
enum LightBakeMode {
LIGHT_BAKE_DISABLED,
diff --git a/servers/text_server.h b/servers/text_server.h
index ba3fdaa35e..7dd9669818 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -31,6 +31,7 @@
#ifndef TEXT_SERVER_H
#define TEXT_SERVER_H
+#include "core/io/image.h"
#include "core/object/ref_counted.h"
#include "core/os/os.h"
#include "core/templates/rid.h"
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 8559737e74..8d6137cf62 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -460,11 +460,27 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
+ CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
CHECK(String::num_scientific(30000000) == "3e+07");
+
+ // String::num_int64 tests.
CHECK(String::num_int64(3141593) == "3141593");
+ CHECK(String::num_int64(-3141593) == "-3141593");
CHECK(String::num_int64(0xA141593, 16) == "a141593");
CHECK(String::num_int64(0xA141593, 16, true) == "A141593");
- CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
+ ERR_PRINT_OFF;
+ CHECK(String::num_int64(3141593, 1) == ""); // Invalid base < 2.
+ CHECK(String::num_int64(3141593, 37) == ""); // Invalid base > 36.
+ ERR_PRINT_ON;
+
+ // String::num_uint64 tests.
+ CHECK(String::num_uint64(4294967295) == "4294967295");
+ CHECK(String::num_uint64(0xF141593, 16) == "f141593");
+ CHECK(String::num_uint64(0xF141593, 16, true) == "F141593");
+ ERR_PRINT_OFF;
+ CHECK(String::num_uint64(4294967295, 1) == ""); // Invalid base < 2.
+ CHECK(String::num_uint64(4294967295, 37) == ""); // Invalid base > 36.
+ ERR_PRINT_ON;
// String::num_real tests.
CHECK(String::num_real(1.0) == "1.0");
diff --git a/tests/core/templates/test_a_hash_map.h b/tests/core/templates/test_a_hash_map.h
new file mode 100644
index 0000000000..e67ee7b441
--- /dev/null
+++ b/tests/core/templates/test_a_hash_map.h
@@ -0,0 +1,295 @@
+/**************************************************************************/
+/* test_a_hash_map.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_A_HASH_MAP_H
+#define TEST_A_HASH_MAP_H
+
+#include "core/templates/a_hash_map.h"
+
+#include "tests/test_macros.h"
+
+namespace TestAHashMap {
+
+TEST_CASE("[AHashMap] Insert element") {
+ AHashMap<int, int> map;
+ AHashMap<int, int>::Iterator e = map.insert(42, 84);
+
+ CHECK(e);
+ CHECK(e->key == 42);
+ CHECK(e->value == 84);
+ CHECK(map[42] == 84);
+ CHECK(map.has(42));
+ CHECK(map.find(42));
+}
+
+TEST_CASE("[AHashMap] Overwrite element") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(42, 1234);
+
+ CHECK(map[42] == 1234);
+}
+
+TEST_CASE("[AHashMap] Erase via element") {
+ AHashMap<int, int> map;
+ AHashMap<int, int>::Iterator e = map.insert(42, 84);
+ map.remove(e);
+ CHECK(!map.has(42));
+ CHECK(!map.find(42));
+}
+
+TEST_CASE("[AHashMap] Erase via key") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.erase(42);
+ CHECK(!map.has(42));
+ CHECK(!map.find(42));
+}
+
+TEST_CASE("[AHashMap] Size") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 84);
+ map.insert(123, 84);
+ map.insert(0, 84);
+ map.insert(123485, 84);
+
+ CHECK(map.size() == 4);
+}
+
+TEST_CASE("[AHashMap] Iteration") {
+ AHashMap<int, int> map;
+
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+ map.insert(123485, 1238888);
+ map.insert(123, 111111);
+
+ Vector<Pair<int, int>> expected;
+ expected.push_back(Pair<int, int>(42, 84));
+ expected.push_back(Pair<int, int>(123, 111111));
+ expected.push_back(Pair<int, int>(0, 12934));
+ expected.push_back(Pair<int, int>(123485, 1238888));
+
+ int idx = 0;
+ for (const KeyValue<int, int> &E : map) {
+ CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
+ idx++;
+ }
+
+ idx--;
+ for (AHashMap<int, int>::Iterator it = map.last(); it; --it) {
+ CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
+ idx--;
+ }
+}
+
+TEST_CASE("[AHashMap] Const iteration") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+ map.insert(123485, 1238888);
+ map.insert(123, 111111);
+
+ const AHashMap<int, int> const_map = map;
+
+ Vector<Pair<int, int>> expected;
+ expected.push_back(Pair<int, int>(42, 84));
+ expected.push_back(Pair<int, int>(123, 111111));
+ expected.push_back(Pair<int, int>(0, 12934));
+ expected.push_back(Pair<int, int>(123485, 1238888));
+ expected.push_back(Pair<int, int>(123, 111111));
+
+ int idx = 0;
+ for (const KeyValue<int, int> &E : const_map) {
+ CHECK(expected[idx] == Pair<int, int>(E.key, E.value));
+ idx++;
+ }
+
+ idx--;
+ for (AHashMap<int, int>::ConstIterator it = const_map.last(); it; --it) {
+ CHECK(expected[idx] == Pair<int, int>(it->key, it->value));
+ idx--;
+ }
+}
+
+TEST_CASE("[AHashMap] Replace key") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(0, 12934);
+ CHECK(map.replace_key(0, 1));
+ CHECK(map.has(1));
+ CHECK(map[1] == 12934);
+}
+
+TEST_CASE("[AHashMap] Clear") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+
+ map.clear();
+ CHECK(!map.has(42));
+ CHECK(map.size() == 0);
+ CHECK(map.is_empty());
+}
+
+TEST_CASE("[AHashMap] Get") {
+ AHashMap<int, int> map;
+ map.insert(42, 84);
+ map.insert(123, 12385);
+ map.insert(0, 12934);
+
+ CHECK(map.get(123) == 12385);
+ map.get(123) = 10;
+ CHECK(map.get(123) == 10);
+
+ CHECK(*map.getptr(0) == 12934);
+ *map.getptr(0) = 1;
+ CHECK(*map.getptr(0) == 1);
+
+ CHECK(map.get(42) == 84);
+ CHECK(map.getptr(-10) == nullptr);
+}
+
+TEST_CASE("[AHashMap] Insert, iterate and remove many elements") {
+ const int elem_max = 1234;
+ AHashMap<int, int> map;
+ for (int i = 0; i < elem_max; i++) {
+ map.insert(i, i);
+ }
+
+ //insert order should have been kept
+ int idx = 0;
+ for (auto &K : map) {
+ CHECK(idx == K.key);
+ CHECK(idx == K.value);
+ CHECK(map.has(idx));
+ idx++;
+ }
+
+ Vector<int> elems_still_valid;
+
+ for (int i = 0; i < elem_max; i++) {
+ if ((i % 5) == 0) {
+ map.erase(i);
+ } else {
+ elems_still_valid.push_back(i);
+ }
+ }
+
+ CHECK(elems_still_valid.size() == map.size());
+
+ for (int i = 0; i < elems_still_valid.size(); i++) {
+ CHECK(map.has(elems_still_valid[i]));
+ }
+}
+
+TEST_CASE("[AHashMap] Insert, iterate and remove many strings") {
+ const int elem_max = 432;
+ AHashMap<String, String> map;
+ for (int i = 0; i < elem_max; i++) {
+ map.insert(itos(i), itos(i));
+ }
+
+ //insert order should have been kept
+ int idx = 0;
+ for (auto &K : map) {
+ CHECK(itos(idx) == K.key);
+ CHECK(itos(idx) == K.value);
+ CHECK(map.has(itos(idx)));
+ idx++;
+ }
+
+ Vector<String> elems_still_valid;
+
+ for (int i = 0; i < elem_max; i++) {
+ if ((i % 5) == 0) {
+ map.erase(itos(i));
+ } else {
+ elems_still_valid.push_back(itos(i));
+ }
+ }
+
+ CHECK(elems_still_valid.size() == map.size());
+
+ for (int i = 0; i < elems_still_valid.size(); i++) {
+ CHECK(map.has(elems_still_valid[i]));
+ }
+
+ elems_still_valid.clear();
+}
+
+TEST_CASE("[AHashMap] Copy constructor") {
+ AHashMap<int, int> map0;
+ const uint32_t count = 5;
+ for (uint32_t i = 0; i < count; i++) {
+ map0.insert(i, i);
+ }
+ AHashMap<int, int> map1(map0);
+ CHECK(map0.size() == map1.size());
+ CHECK(map0.get_capacity() == map1.get_capacity());
+ CHECK(*map0.getptr(0) == *map1.getptr(0));
+}
+
+TEST_CASE("[AHashMap] Operator =") {
+ AHashMap<int, int> map0;
+ AHashMap<int, int> map1;
+ const uint32_t count = 5;
+ map1.insert(1234, 1234);
+ for (uint32_t i = 0; i < count; i++) {
+ map0.insert(i, i);
+ }
+ map1 = map0;
+ CHECK(map0.size() == map1.size());
+ CHECK(map0.get_capacity() == map1.get_capacity());
+ CHECK(*map0.getptr(0) == *map1.getptr(0));
+}
+
+TEST_CASE("[AHashMap] Array methods") {
+ AHashMap<int, int> map;
+ for (int i = 0; i < 100; i++) {
+ map.insert(100 - i, i);
+ }
+ for (int i = 0; i < 100; i++) {
+ CHECK(map.get_by_index(i).value == i);
+ }
+ int index = map.get_index(1);
+ CHECK(map.get_by_index(index).value == 99);
+ CHECK(map.erase_by_index(index));
+ CHECK(!map.erase_by_index(index));
+ CHECK(map.get_index(1) == -1);
+}
+
+} // namespace TestAHashMap
+
+#endif // TEST_A_HASH_MAP_H
diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h
index be615975f8..599a282b20 100644
--- a/tests/core/variant/test_variant.h
+++ b/tests/core/variant/test_variant.h
@@ -1806,6 +1806,14 @@ TEST_CASE("[Variant] Writer and parser dictionary") {
CHECK_MESSAGE(d_parsed == Variant(d), "Should parse back.");
}
+TEST_CASE("[Variant] Writer key sorting") {
+ Dictionary d = build_dictionary(StringName("C"), 3, "A", 1, StringName("B"), 2, "D", 4);
+ String d_str;
+ VariantWriter::write_to_string(d, d_str);
+
+ CHECK_EQ(d_str, "{\n\"A\": 1,\n&\"B\": 2,\n&\"C\": 3,\n\"D\": 4\n}");
+}
+
TEST_CASE("[Variant] Writer recursive dictionary") {
// There is no way to accurately represent a recursive dictionary,
// the only thing we can do is make sure the writer doesn't blow up
diff --git a/tests/scene/test_tree.h b/tests/scene/test_tree.h
index e19f8311e2..a74158d328 100644
--- a/tests/scene/test_tree.h
+++ b/tests/scene/test_tree.h
@@ -139,18 +139,30 @@ TEST_CASE("[SceneTree][Tree]") {
TreeItem *child1 = tree->create_item();
TreeItem *child2 = tree->create_item();
TreeItem *child3 = tree->create_item();
+ CHECK_EQ(root->get_next(), nullptr);
+ CHECK_EQ(root->get_next_visible(), child1);
+ CHECK_EQ(root->get_next_in_tree(), child1);
CHECK_EQ(child1->get_next(), child2);
+ CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
+ CHECK_EQ(child2->get_next_visible(), child3);
CHECK_EQ(child2->get_next_in_tree(), child3);
CHECK_EQ(child3->get_next(), nullptr);
+ CHECK_EQ(child3->get_next_visible(), nullptr);
CHECK_EQ(child3->get_next_in_tree(), nullptr);
+ CHECK_EQ(root->get_prev(), nullptr);
+ CHECK_EQ(root->get_prev_visible(), nullptr);
+ CHECK_EQ(root->get_prev_in_tree(), nullptr);
CHECK_EQ(child1->get_prev(), nullptr);
+ CHECK_EQ(child1->get_prev_visible(), root);
CHECK_EQ(child1->get_prev_in_tree(), root);
CHECK_EQ(child2->get_prev(), child1);
+ CHECK_EQ(child2->get_prev_visible(), child1);
CHECK_EQ(child2->get_prev_in_tree(), child1);
CHECK_EQ(child3->get_prev(), child2);
+ CHECK_EQ(child3->get_prev_visible(), child2);
CHECK_EQ(child3->get_prev_in_tree(), child2);
TreeItem *nested1 = tree->create_item(child2);
@@ -158,13 +170,127 @@ TEST_CASE("[SceneTree][Tree]") {
TreeItem *nested3 = tree->create_item(child2);
CHECK_EQ(child1->get_next(), child2);
+ CHECK_EQ(child1->get_next_visible(), child2);
CHECK_EQ(child1->get_next_in_tree(), child2);
CHECK_EQ(child2->get_next(), child3);
+ CHECK_EQ(child2->get_next_visible(), nested1);
CHECK_EQ(child2->get_next_in_tree(), nested1);
CHECK_EQ(child3->get_prev(), child2);
+ CHECK_EQ(child3->get_prev_visible(), nested3);
CHECK_EQ(child3->get_prev_in_tree(), nested3);
CHECK_EQ(nested1->get_prev_in_tree(), child2);
CHECK_EQ(nested1->get_next_in_tree(), nested2);
+ CHECK_EQ(nested3->get_next_in_tree(), child3);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Previous and Next items with hide root.") {
+ Tree *tree = memnew(Tree);
+ tree->set_hide_root(true);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item();
+ TreeItem *child2 = tree->create_item();
+ TreeItem *child3 = tree->create_item();
+ CHECK_EQ(root->get_next(), nullptr);
+ CHECK_EQ(root->get_next_visible(), child1);
+ CHECK_EQ(root->get_next_in_tree(), child1);
+ CHECK_EQ(child1->get_next(), child2);
+ CHECK_EQ(child1->get_next_visible(), child2);
+ CHECK_EQ(child1->get_next_in_tree(), child2);
+ CHECK_EQ(child2->get_next(), child3);
+ CHECK_EQ(child2->get_next_visible(), child3);
+ CHECK_EQ(child2->get_next_in_tree(), child3);
+ CHECK_EQ(child3->get_next(), nullptr);
+ CHECK_EQ(child3->get_next_visible(), nullptr);
+ CHECK_EQ(child3->get_next_in_tree(), nullptr);
+
+ CHECK_EQ(root->get_prev(), nullptr);
+ CHECK_EQ(root->get_prev_visible(), nullptr);
+ CHECK_EQ(root->get_prev_in_tree(), nullptr);
+ CHECK_EQ(child1->get_prev(), nullptr);
+ CHECK_EQ(child1->get_prev_visible(), nullptr);
+ CHECK_EQ(child1->get_prev_in_tree(), nullptr);
+ CHECK_EQ(child2->get_prev(), child1);
+ CHECK_EQ(child2->get_prev_visible(), child1);
+ CHECK_EQ(child2->get_prev_in_tree(), child1);
+ CHECK_EQ(child3->get_prev(), child2);
+ CHECK_EQ(child3->get_prev_visible(), child2);
+ CHECK_EQ(child3->get_prev_in_tree(), child2);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Previous and Next items wrapping.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item();
+ TreeItem *child2 = tree->create_item();
+ TreeItem *child3 = tree->create_item();
+ CHECK_EQ(root->get_next_visible(true), child1);
+ CHECK_EQ(root->get_next_in_tree(true), child1);
+ CHECK_EQ(child1->get_next_visible(true), child2);
+ CHECK_EQ(child1->get_next_in_tree(true), child2);
+ CHECK_EQ(child2->get_next_visible(true), child3);
+ CHECK_EQ(child2->get_next_in_tree(true), child3);
+ CHECK_EQ(child3->get_next_visible(true), root);
+ CHECK_EQ(child3->get_next_in_tree(true), root);
+
+ CHECK_EQ(root->get_prev_visible(true), child3);
+ CHECK_EQ(root->get_prev_in_tree(true), child3);
+ CHECK_EQ(child1->get_prev_visible(true), root);
+ CHECK_EQ(child1->get_prev_in_tree(true), root);
+ CHECK_EQ(child2->get_prev_visible(true), child1);
+ CHECK_EQ(child2->get_prev_in_tree(true), child1);
+ CHECK_EQ(child3->get_prev_visible(true), child2);
+ CHECK_EQ(child3->get_prev_in_tree(true), child2);
+
+ TreeItem *nested1 = tree->create_item(child2);
+ TreeItem *nested2 = tree->create_item(child2);
+ TreeItem *nested3 = tree->create_item(child2);
+
+ CHECK_EQ(child1->get_next_visible(true), child2);
+ CHECK_EQ(child1->get_next_in_tree(true), child2);
+ CHECK_EQ(child2->get_next_visible(true), nested1);
+ CHECK_EQ(child2->get_next_in_tree(true), nested1);
+ CHECK_EQ(nested3->get_next_visible(true), child3);
+ CHECK_EQ(nested3->get_next_in_tree(true), child3);
+ CHECK_EQ(child3->get_prev_visible(true), nested3);
+ CHECK_EQ(child3->get_prev_in_tree(true), nested3);
+ CHECK_EQ(nested1->get_prev_in_tree(true), child2);
+ CHECK_EQ(nested1->get_next_in_tree(true), nested2);
+ CHECK_EQ(nested3->get_next_in_tree(true), child3);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Previous and Next items wrapping with hide root.") {
+ Tree *tree = memnew(Tree);
+ tree->set_hide_root(true);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item();
+ TreeItem *child2 = tree->create_item();
+ TreeItem *child3 = tree->create_item();
+ CHECK_EQ(root->get_next_visible(true), child1);
+ CHECK_EQ(root->get_next_in_tree(true), child1);
+ CHECK_EQ(child1->get_next_visible(true), child2);
+ CHECK_EQ(child1->get_next_in_tree(true), child2);
+ CHECK_EQ(child2->get_next_visible(true), child3);
+ CHECK_EQ(child2->get_next_in_tree(true), child3);
+ CHECK_EQ(child3->get_next_visible(true), root);
+ CHECK_EQ(child3->get_next_in_tree(true), root);
+
+ CHECK_EQ(root->get_prev_visible(true), child3);
+ CHECK_EQ(root->get_prev_in_tree(true), child3);
+ CHECK_EQ(child1->get_prev_visible(true), child3);
+ CHECK_EQ(child1->get_prev_in_tree(true), child3);
+ CHECK_EQ(child2->get_prev_visible(true), child1);
+ CHECK_EQ(child2->get_prev_in_tree(true), child1);
+ CHECK_EQ(child3->get_prev_visible(true), child2);
+ CHECK_EQ(child3->get_prev_in_tree(true), child2);
memdelete(tree);
}
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 465484d605..979aee8001 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -86,6 +86,7 @@
#include "tests/core/string/test_string.h"
#include "tests/core/string/test_translation.h"
#include "tests/core/string/test_translation_server.h"
+#include "tests/core/templates/test_a_hash_map.h"
#include "tests/core/templates/test_command_queue.h"
#include "tests/core/templates/test_hash_map.h"
#include "tests/core/templates/test_hash_set.h"
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 2ce82e82df..081702f70f 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -565,7 +565,7 @@ File extracted from upstream release tarball:
## meshoptimizer
- Upstream: https://github.com/zeux/meshoptimizer
-- Version: 0.20 (c21d3be6ddf627f8ca852ba4b6db9903b0557858, 2023)
+- Version: 0.22 (4affad044571506a5724c9a6f15424f43e86f731, 2024)
- License: MIT
Files extracted from upstream repository:
diff --git a/thirdparty/meshoptimizer/LICENSE.md b/thirdparty/meshoptimizer/LICENSE.md
index 962ed41ffb..ef9f5919f2 100644
--- a/thirdparty/meshoptimizer/LICENSE.md
+++ b/thirdparty/meshoptimizer/LICENSE.md
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016-2023 Arseny Kapoulkine
+Copyright (c) 2016-2024 Arseny Kapoulkine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/thirdparty/meshoptimizer/allocator.cpp b/thirdparty/meshoptimizer/allocator.cpp
index 072e8e51ac..b8cb33c280 100644
--- a/thirdparty/meshoptimizer/allocator.cpp
+++ b/thirdparty/meshoptimizer/allocator.cpp
@@ -1,7 +1,7 @@
// This file is part of meshoptimizer library; see meshoptimizer.h for version/license details
#include "meshoptimizer.h"
-void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*))
+void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*))
{
meshopt_Allocator::Storage::allocate = allocate;
meshopt_Allocator::Storage::deallocate = deallocate;
diff --git a/thirdparty/meshoptimizer/clusterizer.cpp b/thirdparty/meshoptimizer/clusterizer.cpp
index c4672ad606..738add5f2f 100644
--- a/thirdparty/meshoptimizer/clusterizer.cpp
+++ b/thirdparty/meshoptimizer/clusterizer.cpp
@@ -238,7 +238,7 @@ static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int
bool result = false;
- unsigned int used_extra = (av == 0xff) + (bv == 0xff) + (cv == 0xff);
+ int used_extra = (av == 0xff) + (bv == 0xff) + (cv == 0xff);
if (meshlet.vertex_count + used_extra > max_vertices || meshlet.triangle_count >= max_triangles)
{
@@ -283,10 +283,10 @@ static bool appendMeshlet(meshopt_Meshlet& meshlet, unsigned int a, unsigned int
return result;
}
-static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone* meshlet_cone, unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const unsigned char* used, float meshlet_expected_radius, float cone_weight, unsigned int* out_extra)
+static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Cone* meshlet_cone, unsigned int* meshlet_vertices, const unsigned int* indices, const TriangleAdjacency2& adjacency, const Cone* triangles, const unsigned int* live_triangles, const unsigned char* used, float meshlet_expected_radius, float cone_weight)
{
unsigned int best_triangle = ~0u;
- unsigned int best_extra = 5;
+ int best_priority = 5;
float best_score = FLT_MAX;
for (size_t i = 0; i < meshlet.vertex_count; ++i)
@@ -301,20 +301,26 @@ static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Co
unsigned int triangle = neighbors[j];
unsigned int a = indices[triangle * 3 + 0], b = indices[triangle * 3 + 1], c = indices[triangle * 3 + 2];
- unsigned int extra = (used[a] == 0xff) + (used[b] == 0xff) + (used[c] == 0xff);
+ int extra = (used[a] == 0xff) + (used[b] == 0xff) + (used[c] == 0xff);
+ assert(extra <= 2);
- // triangles that don't add new vertices to meshlets are max. priority
- if (extra != 0)
- {
- // artificially increase the priority of dangling triangles as they're expensive to add to new meshlets
- if (live_triangles[a] == 1 || live_triangles[b] == 1 || live_triangles[c] == 1)
- extra = 0;
+ int priority = -1;
- extra++;
- }
+ // triangles that don't add new vertices to meshlets are max. priority
+ if (extra == 0)
+ priority = 0;
+ // artificially increase the priority of dangling triangles as they're expensive to add to new meshlets
+ else if (live_triangles[a] == 1 || live_triangles[b] == 1 || live_triangles[c] == 1)
+ priority = 1;
+ // if two vertices have live count of 2, removing this triangle will make another triangle dangling which is good for overall flow
+ else if ((live_triangles[a] == 2) + (live_triangles[b] == 2) + (live_triangles[c] == 2) >= 2)
+ priority = 1 + extra;
+ // otherwise adjust priority to be after the above cases, 3 or 4 based on used[] count
+ else
+ priority = 2 + extra;
// since topology-based priority is always more important than the score, we can skip scoring in some cases
- if (extra > best_extra)
+ if (priority > best_priority)
continue;
float score = 0;
@@ -341,18 +347,15 @@ static unsigned int getNeighborTriangle(const meshopt_Meshlet& meshlet, const Co
// note that topology-based priority is always more important than the score
// this helps maintain reasonable effectiveness of meshlet data and reduces scoring cost
- if (extra < best_extra || score < best_score)
+ if (priority < best_priority || score < best_score)
{
best_triangle = triangle;
- best_extra = extra;
+ best_priority = priority;
best_score = score;
}
}
}
- if (out_extra)
- *out_extra = best_extra;
-
return best_triangle;
}
@@ -441,7 +444,7 @@ static size_t kdtreeBuild(size_t offset, KDNode* nodes, size_t node_count, const
}
// split axis is one where the variance is largest
- unsigned int axis = vars[0] >= vars[1] && vars[0] >= vars[2] ? 0 : vars[1] >= vars[2] ? 1 : 2;
+ unsigned int axis = (vars[0] >= vars[1] && vars[0] >= vars[2]) ? 0 : (vars[1] >= vars[2] ? 1 : 2);
float split = mean[axis];
size_t middle = kdtreePartition(indices, count, points, stride, axis, split);
@@ -588,13 +591,13 @@ size_t meshopt_buildMeshlets(meshopt_Meshlet* meshlets, unsigned int* meshlet_ve
{
Cone meshlet_cone = getMeshletCone(meshlet_cone_acc, meshlet.triangle_count);
- unsigned int best_extra = 0;
- unsigned int best_triangle = getNeighborTriangle(meshlet, &meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight, &best_extra);
+ unsigned int best_triangle = getNeighborTriangle(meshlet, &meshlet_cone, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, cone_weight);
+ int best_extra = best_triangle == ~0u ? -1 : (used[indices[best_triangle * 3 + 0]] == 0xff) + (used[indices[best_triangle * 3 + 1]] == 0xff) + (used[indices[best_triangle * 3 + 2]] == 0xff);
// if the best triangle doesn't fit into current meshlet, the spatial scoring we've used is not very meaningful, so we re-select using topological scoring
if (best_triangle != ~0u && (meshlet.vertex_count + best_extra > max_vertices || meshlet.triangle_count >= max_triangles))
{
- best_triangle = getNeighborTriangle(meshlet, NULL, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, 0.f, NULL);
+ best_triangle = getNeighborTriangle(meshlet, NULL, meshlet_vertices, indices, adjacency, triangles, live_triangles, used, meshlet_expected_radius, 0.f);
}
// when we run out of neighboring triangles we need to switch to spatial search; we currently just pick the closest triangle irrespective of connectivity
@@ -882,3 +885,93 @@ meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices
return meshopt_computeClusterBounds(indices, triangle_count * 3, vertex_positions, vertex_count, vertex_positions_stride);
}
+
+void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count)
+{
+ using namespace meshopt;
+
+ assert(triangle_count <= kMeshletMaxTriangles);
+ assert(vertex_count <= kMeshletMaxVertices);
+
+ unsigned char* indices = meshlet_triangles;
+ unsigned int* vertices = meshlet_vertices;
+
+ // cache tracks vertex timestamps (corresponding to triangle index! all 3 vertices are added at the same time and never removed)
+ unsigned char cache[kMeshletMaxVertices];
+ memset(cache, 0, vertex_count);
+
+ // note that we start from a value that means all vertices aren't in cache
+ unsigned char cache_last = 128;
+ const unsigned char cache_cutoff = 3; // 3 triangles = ~5..9 vertices depending on reuse
+
+ for (size_t i = 0; i < triangle_count; ++i)
+ {
+ int next = -1;
+ int next_match = -1;
+
+ for (size_t j = i; j < triangle_count; ++j)
+ {
+ unsigned char a = indices[j * 3 + 0], b = indices[j * 3 + 1], c = indices[j * 3 + 2];
+ assert(a < vertex_count && b < vertex_count && c < vertex_count);
+
+ // score each triangle by how many vertices are in cache
+ // note: the distance is computed using unsigned 8-bit values, so cache timestamp overflow is handled gracefully
+ int aok = (unsigned char)(cache_last - cache[a]) < cache_cutoff;
+ int bok = (unsigned char)(cache_last - cache[b]) < cache_cutoff;
+ int cok = (unsigned char)(cache_last - cache[c]) < cache_cutoff;
+
+ if (aok + bok + cok > next_match)
+ {
+ next = (int)j;
+ next_match = aok + bok + cok;
+
+ // note that we could end up with all 3 vertices in the cache, but 2 is enough for ~strip traversal
+ if (next_match >= 2)
+ break;
+ }
+ }
+
+ assert(next >= 0);
+
+ unsigned char a = indices[next * 3 + 0], b = indices[next * 3 + 1], c = indices[next * 3 + 2];
+
+ // shift triangles before the next one forward so that we always keep an ordered partition
+ // note: this could have swapped triangles [i] and [next] but that distorts the order and may skew the output sequence
+ memmove(indices + (i + 1) * 3, indices + i * 3, (next - i) * 3 * sizeof(unsigned char));
+
+ indices[i * 3 + 0] = a;
+ indices[i * 3 + 1] = b;
+ indices[i * 3 + 2] = c;
+
+ // cache timestamp is the same between all vertices of each triangle to reduce overflow
+ cache_last++;
+ cache[a] = cache_last;
+ cache[b] = cache_last;
+ cache[c] = cache_last;
+ }
+
+ // reorder meshlet vertices for access locality assuming index buffer is scanned sequentially
+ unsigned int order[kMeshletMaxVertices];
+
+ unsigned char remap[kMeshletMaxVertices];
+ memset(remap, -1, vertex_count);
+
+ size_t vertex_offset = 0;
+
+ for (size_t i = 0; i < triangle_count * 3; ++i)
+ {
+ unsigned char& r = remap[indices[i]];
+
+ if (r == 0xff)
+ {
+ r = (unsigned char)(vertex_offset);
+ order[vertex_offset] = vertices[indices[i]];
+ vertex_offset++;
+ }
+
+ indices[i] = r;
+ }
+
+ assert(vertex_offset <= vertex_count);
+ memcpy(vertices, order, vertex_offset * sizeof(unsigned int));
+}
diff --git a/thirdparty/meshoptimizer/indexcodec.cpp b/thirdparty/meshoptimizer/indexcodec.cpp
index 4cc2fea63a..b300460052 100644
--- a/thirdparty/meshoptimizer/indexcodec.cpp
+++ b/thirdparty/meshoptimizer/indexcodec.cpp
@@ -33,7 +33,7 @@ static int rotateTriangle(unsigned int a, unsigned int b, unsigned int c, unsign
{
(void)a;
- return (b == next) ? 1 : (c == next) ? 2 : 0;
+ return (b == next) ? 1 : (c == next ? 2 : 0);
}
static int getEdgeFifo(EdgeFifo fifo, unsigned int a, unsigned int b, unsigned int c, size_t offset)
@@ -217,7 +217,7 @@ size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, cons
int fe = fer >> 2;
int fc = getVertexFifo(vertexfifo, c, vertexfifooffset);
- int fec = (fc >= 1 && fc < fecmax) ? fc : (c == next) ? (next++, 0) : 15;
+ int fec = (fc >= 1 && fc < fecmax) ? fc : (c == next ? (next++, 0) : 15);
if (fec == 15 && version >= 1)
{
@@ -267,8 +267,8 @@ size_t meshopt_encodeIndexBuffer(unsigned char* buffer, size_t buffer_size, cons
// after rotation, a is almost always equal to next, so we don't waste bits on FIFO encoding for a
int fea = (a == next) ? (next++, 0) : 15;
- int feb = (fb >= 0 && fb < 14) ? (fb + 1) : (b == next) ? (next++, 0) : 15;
- int fec = (fc >= 0 && fc < 14) ? (fc + 1) : (c == next) ? (next++, 0) : 15;
+ int feb = (fb >= 0 && fb < 14) ? fb + 1 : (b == next ? (next++, 0) : 15);
+ int fec = (fc >= 0 && fc < 14) ? fc + 1 : (c == next ? (next++, 0) : 15);
// we encode feb & fec in 4 bits using a table if possible, and as a full byte otherwise
unsigned char codeaux = (unsigned char)((feb << 4) | fec);
diff --git a/thirdparty/meshoptimizer/indexgenerator.cpp b/thirdparty/meshoptimizer/indexgenerator.cpp
index f6728345a9..0d53020e32 100644
--- a/thirdparty/meshoptimizer/indexgenerator.cpp
+++ b/thirdparty/meshoptimizer/indexgenerator.cpp
@@ -6,6 +6,7 @@
// This work is based on:
// John McDonald, Mark Kilgard. Crack-Free Point-Normal Triangles using Adjacent Edge Normals. 2010
+// John Hable. Variable Rate Shading with Visibility Buffer Rendering. 2024
namespace meshopt
{
@@ -576,3 +577,99 @@ void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const un
memcpy(destination + i * 4, patch, sizeof(patch));
}
}
+
+size_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, unsigned int* reorder, const unsigned int* indices, size_t index_count, size_t vertex_count)
+{
+ assert(index_count % 3 == 0);
+
+ meshopt_Allocator allocator;
+
+ unsigned int* remap = allocator.allocate<unsigned int>(vertex_count);
+ memset(remap, -1, vertex_count * sizeof(unsigned int));
+
+ // compute vertex valence; this is used to prioritize least used corner
+ // note: we use 8-bit counters for performance; for outlier vertices the valence is incorrect but that just affects the heuristic
+ unsigned char* valence = allocator.allocate<unsigned char>(vertex_count);
+ memset(valence, 0, vertex_count);
+
+ for (size_t i = 0; i < index_count; ++i)
+ {
+ unsigned int index = indices[i];
+ assert(index < vertex_count);
+
+ valence[index]++;
+ }
+
+ unsigned int reorder_offset = 0;
+
+ // assign provoking vertices; leave the rest for the next pass
+ for (size_t i = 0; i < index_count; i += 3)
+ {
+ unsigned int a = indices[i + 0], b = indices[i + 1], c = indices[i + 2];
+ assert(a < vertex_count && b < vertex_count && c < vertex_count);
+
+ // try to rotate triangle such that provoking vertex hasn't been seen before
+ // if multiple vertices are new, prioritize the one with least valence
+ // this reduces the risk that a future triangle will have all three vertices seen
+ unsigned int va = remap[a] == ~0u ? valence[a] : ~0u;
+ unsigned int vb = remap[b] == ~0u ? valence[b] : ~0u;
+ unsigned int vc = remap[c] == ~0u ? valence[c] : ~0u;
+
+ if (vb != ~0u && vb <= va && vb <= vc)
+ {
+ // abc -> bca
+ unsigned int t = a;
+ a = b, b = c, c = t;
+ }
+ else if (vc != ~0u && vc <= va && vc <= vb)
+ {
+ // abc -> cab
+ unsigned int t = c;
+ c = b, b = a, a = t;
+ }
+
+ unsigned int newidx = reorder_offset;
+
+ // now remap[a] = ~0u or all three vertices are old
+ // recording remap[a] makes it possible to remap future references to the same index, conserving space
+ if (remap[a] == ~0u)
+ remap[a] = newidx;
+
+ // we need to clone the provoking vertex to get a unique index
+ // if all three are used the choice is arbitrary since no future triangle will be able to reuse any of these
+ reorder[reorder_offset++] = a;
+
+ // note: first vertex is final, the other two will be fixed up in next pass
+ destination[i + 0] = newidx;
+ destination[i + 1] = b;
+ destination[i + 2] = c;
+
+ // update vertex valences for corner heuristic
+ valence[a]--;
+ valence[b]--;
+ valence[c]--;
+ }
+
+ // remap or clone non-provoking vertices (iterating to skip provoking vertices)
+ int step = 1;
+
+ for (size_t i = 1; i < index_count; i += step, step ^= 3)
+ {
+ unsigned int index = destination[i];
+
+ if (remap[index] == ~0u)
+ {
+ // we haven't seen the vertex before as a provoking vertex
+ // to maintain the reference to the original vertex we need to clone it
+ unsigned int newidx = reorder_offset;
+
+ remap[index] = newidx;
+ reorder[reorder_offset++] = index;
+ }
+
+ destination[i] = remap[index];
+ }
+
+ assert(reorder_offset <= vertex_count + index_count / 3);
+ return reorder_offset;
+}
diff --git a/thirdparty/meshoptimizer/meshoptimizer.h b/thirdparty/meshoptimizer/meshoptimizer.h
index dbafd4e6e4..77be5371fc 100644
--- a/thirdparty/meshoptimizer/meshoptimizer.h
+++ b/thirdparty/meshoptimizer/meshoptimizer.h
@@ -1,7 +1,7 @@
/**
- * meshoptimizer - version 0.20
+ * meshoptimizer - version 0.22
*
- * Copyright (C) 2016-2023, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * Copyright (C) 2016-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://github.com/zeux/meshoptimizer
*
* This library is distributed under the MIT License. See notice at the end of this file.
@@ -12,7 +12,7 @@
#include <stddef.h>
/* Version macro; major * 1000 + minor * 10 + patch */
-#define MESHOPTIMIZER_VERSION 200 /* 0.20 */
+#define MESHOPTIMIZER_VERSION 220 /* 0.22 */
/* If no API is defined, assume default */
#ifndef MESHOPTIMIZER_API
@@ -29,11 +29,14 @@
#endif
/* Experimental APIs have unstable interface and might have implementation that's not fully tested or optimized */
+#ifndef MESHOPTIMIZER_EXPERIMENTAL
#define MESHOPTIMIZER_EXPERIMENTAL MESHOPTIMIZER_API
+#endif
/* C interface */
#ifdef __cplusplus
-extern "C" {
+extern "C"
+{
#endif
/**
@@ -138,6 +141,19 @@ MESHOPTIMIZER_API void meshopt_generateAdjacencyIndexBuffer(unsigned int* destin
MESHOPTIMIZER_API void meshopt_generateTessellationIndexBuffer(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
/**
+ * Experimental: Generate index buffer that can be used for visibility buffer rendering and returns the size of the reorder table
+ * Each triangle's provoking vertex index is equal to primitive id; this allows passing it to the fragment shader using nointerpolate attribute.
+ * This is important for performance on hardware where primitive id can't be accessed efficiently in fragment shader.
+ * The reorder table stores the original vertex id for each vertex in the new index buffer, and should be used in the vertex shader to load vertex data.
+ * The provoking vertex is assumed to be the first vertex in the triangle; if this is not the case (OpenGL), rotate each triangle (abc -> bca) before rendering.
+ * For maximum efficiency the input index buffer should be optimized for vertex cache first.
+ *
+ * destination must contain enough space for the resulting index buffer (index_count elements)
+ * reorder must contain enough space for the worst case reorder table (vertex_count + index_count/3 elements)
+ */
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_generateProvokingIndexBuffer(unsigned int* destination, unsigned int* reorder, const unsigned int* indices, size_t index_count, size_t vertex_count);
+
+/**
* Vertex transform cache optimizer
* Reorders indices to reduce the number of GPU vertex shader invocations
* If index buffer contains multiple ranges for multiple draw calls, this functions needs to be called on each range individually.
@@ -254,6 +270,7 @@ MESHOPTIMIZER_API int meshopt_decodeIndexSequence(void* destination, size_t inde
* Returns encoded data size on success, 0 on error; the only error condition is if buffer doesn't have enough space
* This function works for a single vertex stream; for multiple vertex streams, call meshopt_encodeVertexBuffer for each stream.
* Note that all vertex_size bytes of each vertex are encoded verbatim, including padding which should be zero-initialized.
+ * For maximum efficiency the vertex buffer being encoded has to be quantized and optimized for locality of reference (cache/fetch) first.
*
* buffer must contain enough space for the encoded vertex buffer (use meshopt_encodeVertexBufferBound to compute worst case size)
*/
@@ -289,9 +306,9 @@ MESHOPTIMIZER_API int meshopt_decodeVertexBuffer(void* destination, size_t verte
* meshopt_decodeFilterExp decodes exponential encoding of floating-point data with 8-bit exponent and 24-bit integer mantissa as 2^E*M.
* Each 32-bit component is decoded in isolation; stride must be divisible by 4.
*/
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_API void meshopt_decodeFilterOct(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_API void meshopt_decodeFilterQuat(void* buffer, size_t count, size_t stride);
+MESHOPTIMIZER_API void meshopt_decodeFilterExp(void* buffer, size_t count, size_t stride);
/**
* Vertex buffer filter encoders
@@ -311,32 +328,40 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_decodeFilterExp(void* buffer, size_t cou
*/
enum meshopt_EncodeExpMode
{
- /* When encoding exponents, use separate values for each component (maximum quality) */
- meshopt_EncodeExpSeparate,
- /* When encoding exponents, use shared value for all components of each vector (better compression) */
- meshopt_EncodeExpSharedVector,
- /* When encoding exponents, use shared value for each component of all vectors (best compression) */
- meshopt_EncodeExpSharedComponent,
+ /* When encoding exponents, use separate values for each component (maximum quality) */
+ meshopt_EncodeExpSeparate,
+ /* When encoding exponents, use shared value for all components of each vector (better compression) */
+ meshopt_EncodeExpSharedVector,
+ /* When encoding exponents, use shared value for each component of all vectors (best compression) */
+ meshopt_EncodeExpSharedComponent,
+ /* Experimental: When encoding exponents, use separate values for each component, but clamp to 0 (good quality if very small values are not important) */
+ meshopt_EncodeExpClamped,
};
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data);
-MESHOPTIMIZER_EXPERIMENTAL void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data, enum meshopt_EncodeExpMode mode);
+MESHOPTIMIZER_API void meshopt_encodeFilterOct(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_API void meshopt_encodeFilterQuat(void* destination, size_t count, size_t stride, int bits, const float* data);
+MESHOPTIMIZER_API void meshopt_encodeFilterExp(void* destination, size_t count, size_t stride, int bits, const float* data, enum meshopt_EncodeExpMode mode);
/**
* Simplification options
*/
enum
{
- /* Do not move vertices that are located on the topological border (vertices on triangle edges that don't have a paired triangle). Useful for simplifying portions of the larger mesh. */
- meshopt_SimplifyLockBorder = 1 << 0,
+ /* Do not move vertices that are located on the topological border (vertices on triangle edges that don't have a paired triangle). Useful for simplifying portions of the larger mesh. */
+ meshopt_SimplifyLockBorder = 1 << 0,
+ /* Improve simplification performance assuming input indices are a sparse subset of the mesh. Note that error becomes relative to subset extents. */
+ meshopt_SimplifySparse = 1 << 1,
+ /* Treat error limit and resulting error as absolute instead of relative to mesh extents. */
+ meshopt_SimplifyErrorAbsolute = 1 << 2,
+ /* Experimental: remove disconnected parts of the mesh during simplification incrementally, regardless of the topological restrictions inside components. */
+ meshopt_SimplifyPrune = 1 << 3,
};
/**
* Mesh simplifier
* Reduces the number of triangles in the mesh, attempting to preserve mesh appearance as much as possible
* The algorithm tries to preserve mesh topology and can stop short of the target goal based on topology constraints or target error.
- * If not all attributes from the input mesh are required, it's recommended to reindex the mesh using meshopt_generateShadowIndexBuffer prior to simplification.
+ * If not all attributes from the input mesh are required, it's recommended to reindex the mesh without them prior to simplification.
* Returns the number of indices after simplification, with destination containing new index data
* The resulting index buffer references vertices from the original vertex buffer.
* If the original vertex data isn't required, creating a compact vertex buffer using meshopt_optimizeVertexFetch is recommended.
@@ -351,15 +376,15 @@ MESHOPTIMIZER_API size_t meshopt_simplify(unsigned int* destination, const unsig
/**
* Experimental: Mesh simplifier with attribute metric
- * The algorithm ehnahces meshopt_simplify by incorporating attribute values into the error metric used to prioritize simplification order; see meshopt_simplify documentation for details.
+ * The algorithm enhances meshopt_simplify by incorporating attribute values into the error metric used to prioritize simplification order; see meshopt_simplify documentation for details.
* Note that the number of attributes affects memory requirements and running time; this algorithm requires ~1.5x more memory and time compared to meshopt_simplify when using 4 scalar attributes.
*
* vertex_attributes should have attribute_count floats for each vertex
- * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position. The recommended weight range is [1e-3..1e-1], assuming attribute data is in [0..1] range.
- * attribute_count must be <= 16
- * TODO target_error/result_error currently use combined distance+attribute error; this may change in the future
+ * attribute_weights should have attribute_count floats in total; the weights determine relative priority of attributes between each other and wrt position
+ * attribute_count must be <= 32
+ * vertex_lock can be NULL; when it's not NULL, it should have a value for each vertex; 1 denotes vertices that can't be moved
*/
-MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float* result_error);
+MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error);
/**
* Experimental: Mesh simplifier (sloppy)
@@ -386,6 +411,7 @@ MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifySloppy(unsigned int* destinati
* destination must contain enough space for the target index buffer (target_vertex_count elements)
* vertex_positions should have float3 position in the first 12 bytes of each vertex
* vertex_colors should can be NULL; when it's not NULL, it should have float3 color in the first 12 bytes of each vertex
+ * color_weight determines relative priority of color wrt position; 1.0 is a safe default
*/
MESHOPTIMIZER_EXPERIMENTAL size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t target_vertex_count);
@@ -464,6 +490,13 @@ struct meshopt_VertexFetchStatistics
*/
MESHOPTIMIZER_API struct meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch(const unsigned int* indices, size_t index_count, size_t vertex_count, size_t vertex_size);
+/**
+ * Meshlet is a small mesh cluster (subset) that consists of:
+ * - triangles, an 8-bit micro triangle (index) buffer, that for each triangle specifies three local vertices to use;
+ * - vertices, a 32-bit vertex indirection buffer, that for each local vertex specifies which mesh vertex to fetch vertex attributes from.
+ *
+ * For efficiency, meshlet triangles and vertices are packed into two large arrays; this structure contains offsets and counts to access the data.
+ */
struct meshopt_Meshlet
{
/* offsets within meshlet_vertices and meshlet_triangles arrays with meshlet data */
@@ -479,6 +512,7 @@ struct meshopt_Meshlet
* Meshlet builder
* Splits the mesh into a set of meshlets where each meshlet has a micro index buffer indexing into meshlet vertices that refer to the original vertex buffer
* The resulting data can be used to render meshes using NVidia programmable mesh shading pipeline, or in other cluster-based renderers.
+ * When targeting mesh shading hardware, for maximum efficiency meshlets should be further optimized using meshopt_optimizeMeshlet.
* When using buildMeshlets, vertex positions need to be provided to minimize the size of the resulting clusters.
* When using buildMeshletsScan, for maximum efficiency the index buffer being converted has to be optimized for vertex cache first.
*
@@ -486,13 +520,23 @@ struct meshopt_Meshlet
* meshlet_vertices must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_vertices
* meshlet_triangles must contain enough space for all meshlets, worst case size is equal to max_meshlets * max_triangles * 3
* vertex_positions should have float3 position in the first 12 bytes of each vertex
- * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512)
+ * max_vertices and max_triangles must not exceed implementation limits (max_vertices <= 255 - not 256!, max_triangles <= 512; max_triangles must be divisible by 4)
* cone_weight should be set to 0 when cone culling is not used, and a value between 0 and 1 otherwise to balance between cluster size and cone culling efficiency
*/
MESHOPTIMIZER_API size_t meshopt_buildMeshlets(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t max_vertices, size_t max_triangles, float cone_weight);
MESHOPTIMIZER_API size_t meshopt_buildMeshletsScan(struct meshopt_Meshlet* meshlets, unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, const unsigned int* indices, size_t index_count, size_t vertex_count, size_t max_vertices, size_t max_triangles);
MESHOPTIMIZER_API size_t meshopt_buildMeshletsBound(size_t index_count, size_t max_vertices, size_t max_triangles);
+/**
+ * Experimental: Meshlet optimizer
+ * Reorders meshlet vertices and triangles to maximize locality to improve rasterizer throughput
+ *
+ * meshlet_triangles and meshlet_vertices must refer to meshlet triangle and vertex index data; when buildMeshlets* is used, these
+ * need to be computed from meshlet's vertex_offset and triangle_offset
+ * triangle_count and vertex_count must not exceed implementation limits (vertex_count <= 255 - not 256!, triangle_count <= 512)
+ */
+MESHOPTIMIZER_EXPERIMENTAL void meshopt_optimizeMeshlet(unsigned int* meshlet_vertices, unsigned char* meshlet_triangles, size_t triangle_count, size_t vertex_count);
+
struct meshopt_Bounds
{
/* bounding sphere, useful for frustum and occlusion culling */
@@ -529,7 +573,8 @@ struct meshopt_Bounds
* Real-Time Rendering 4th Edition, section 19.3).
*
* vertex_positions should have float3 position in the first 12 bytes of each vertex
- * index_count/3 should be less than or equal to 512 (the function assumes clusters of limited size)
+ * vertex_count should specify the number of vertices in the entire mesh, not cluster or meshlet
+ * index_count/3 and triangle_count must not exceed implementation limits (<= 512)
*/
MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeClusterBounds(const unsigned int* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
MESHOPTIMIZER_API struct meshopt_Bounds meshopt_computeMeshletBounds(const unsigned int* meshlet_vertices, const unsigned char* meshlet_triangles, size_t triangle_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
@@ -559,7 +604,7 @@ MESHOPTIMIZER_EXPERIMENTAL void meshopt_spatialSortTriangles(unsigned int* desti
* Note that all algorithms only allocate memory for temporary use.
* allocate/deallocate are always called in a stack-like order - last pointer to be allocated is deallocated first.
*/
-MESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*));
+MESHOPTIMIZER_API void meshopt_setAllocator(void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t), void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*));
#ifdef __cplusplus
} /* extern "C" */
@@ -627,6 +672,8 @@ inline void meshopt_generateAdjacencyIndexBuffer(T* destination, const T* indice
template <typename T>
inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride);
template <typename T>
+inline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count);
+template <typename T>
inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count);
template <typename T>
inline void meshopt_optimizeVertexCacheStrip(T* destination, const T* indices, size_t index_count, size_t vertex_count);
@@ -649,7 +696,7 @@ inline int meshopt_decodeIndexSequence(T* destination, size_t index_count, const
template <typename T>
inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);
template <typename T>
-inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);
+inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options = 0, float* result_error = NULL);
template <typename T>
inline size_t meshopt_simplifySloppy(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* result_error = NULL);
template <typename T>
@@ -705,15 +752,15 @@ public:
template <typename T>
struct StorageT
{
- static void* (MESHOPTIMIZER_ALLOC_CALLCONV *allocate)(size_t);
- static void (MESHOPTIMIZER_ALLOC_CALLCONV *deallocate)(void*);
+ static void* (MESHOPTIMIZER_ALLOC_CALLCONV* allocate)(size_t);
+ static void (MESHOPTIMIZER_ALLOC_CALLCONV* deallocate)(void*);
};
typedef StorageT<void> Storage;
meshopt_Allocator()
- : blocks()
- , count(0)
+ : blocks()
+ , count(0)
{
}
@@ -723,7 +770,8 @@ public:
Storage::deallocate(blocks[i - 1]);
}
- template <typename T> T* allocate(size_t size)
+ template <typename T>
+ T* allocate(size_t size)
{
assert(count < sizeof(blocks) / sizeof(blocks[0]));
T* result = static_cast<T*>(Storage::allocate(size > size_t(-1) / sizeof(T) ? size_t(-1) : size * sizeof(T)));
@@ -744,8 +792,10 @@ private:
};
// This makes sure that allocate/deallocate are lazily generated in translation units that need them and are deduplicated by the linker
-template <typename T> void* (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::allocate)(size_t) = operator new;
-template <typename T> void (MESHOPTIMIZER_ALLOC_CALLCONV *meshopt_Allocator::StorageT<T>::deallocate)(void*) = operator delete;
+template <typename T>
+void* (MESHOPTIMIZER_ALLOC_CALLCONV* meshopt_Allocator::StorageT<T>::allocate)(size_t) = operator new;
+template <typename T>
+void (MESHOPTIMIZER_ALLOC_CALLCONV* meshopt_Allocator::StorageT<T>::deallocate)(void*) = operator delete;
#endif
/* Inline implementation for C++ templated wrappers */
@@ -861,6 +911,19 @@ inline void meshopt_generateTessellationIndexBuffer(T* destination, const T* ind
}
template <typename T>
+inline size_t meshopt_generateProvokingIndexBuffer(T* destination, unsigned int* reorder, const T* indices, size_t index_count, size_t vertex_count)
+{
+ meshopt_IndexAdapter<T> in(NULL, indices, index_count);
+ meshopt_IndexAdapter<T> out(destination, NULL, index_count);
+
+ size_t bound = vertex_count + (index_count / 3);
+ assert(size_t(T(bound - 1)) == bound - 1); // bound - 1 must fit in T
+ (void)bound;
+
+ return meshopt_generateProvokingIndexBuffer(out.data, reorder, in.data, index_count, vertex_count);
+}
+
+template <typename T>
inline void meshopt_optimizeVertexCache(T* destination, const T* indices, size_t index_count, size_t vertex_count)
{
meshopt_IndexAdapter<T> in(NULL, indices, index_count);
@@ -956,12 +1019,12 @@ inline size_t meshopt_simplify(T* destination, const T* indices, size_t index_co
}
template <typename T>
-inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float* result_error)
+inline size_t meshopt_simplifyWithAttributes(T* destination, const T* indices, size_t index_count, const float* vertex_positions, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* result_error)
{
- meshopt_IndexAdapter<T> in(NULL, indices, index_count);
- meshopt_IndexAdapter<T> out(destination, NULL, index_count);
+ meshopt_IndexAdapter<T> in(NULL, indices, index_count);
+ meshopt_IndexAdapter<T> out(destination, NULL, index_count);
- return meshopt_simplifyWithAttributes(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, target_index_count, target_error, options, result_error);
+ return meshopt_simplifyWithAttributes(out.data, in.data, index_count, vertex_positions, vertex_count, vertex_positions_stride, vertex_attributes, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, result_error);
}
template <typename T>
@@ -1050,7 +1113,7 @@ inline void meshopt_spatialSortTriangles(T* destination, const T* indices, size_
#endif
/**
- * Copyright (c) 2016-2023 Arseny Kapoulkine
+ * Copyright (c) 2016-2024 Arseny Kapoulkine
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
diff --git a/thirdparty/meshoptimizer/overdrawanalyzer.cpp b/thirdparty/meshoptimizer/overdrawanalyzer.cpp
index 8b6f254134..31cf6f146a 100644
--- a/thirdparty/meshoptimizer/overdrawanalyzer.cpp
+++ b/thirdparty/meshoptimizer/overdrawanalyzer.cpp
@@ -53,11 +53,10 @@ static void rasterize(OverdrawBuffer* buffer, float v1x, float v1y, float v1z, f
// flip backfacing triangles to simplify rasterization logic
if (sign)
{
- // flipping v2 & v3 preserves depth gradients since they're based on v1
+ // flipping v2 & v3 preserves depth gradients since they're based on v1; only v1z is used below
float t;
t = v2x, v2x = v3x, v3x = t;
t = v2y, v2y = v3y, v3y = t;
- t = v2z, v2z = v3z, v3z = t;
// flip depth since we rasterize backfacing triangles to second buffer with reverse Z; only v1z is used below
v1z = kViewport - v1z;
diff --git a/thirdparty/meshoptimizer/patches/distance-only-metric.patch b/thirdparty/meshoptimizer/patches/distance-only-metric.patch
index 651bdba5ef..ce16f4ab9c 100644
--- a/thirdparty/meshoptimizer/patches/distance-only-metric.patch
+++ b/thirdparty/meshoptimizer/patches/distance-only-metric.patch
@@ -1,39 +1,13 @@
diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
-index 5ba8570076..6f8b0e520e 100644
+index af64cbda49..8a7072fe4e 100644
--- a/thirdparty/meshoptimizer/simplifier.cpp
+++ b/thirdparty/meshoptimizer/simplifier.cpp
-@@ -476,6 +476,8 @@ struct Collapse
- float error;
- unsigned int errorui;
- };
-+
-+ float distance_error;
- };
+@@ -2048,7 +2048,7 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
- static float normalize(Vector3& v)
-@@ -941,6 +943,8 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
- float ei = quadricError(vertex_quadrics[remap[i0]], vertex_positions[i1]);
- float ej = quadricError(vertex_quadrics[remap[j0]], vertex_positions[j1]);
+ // result_error is quadratic; we need to remap it back to linear
+ if (out_result_error)
+- *out_result_error = sqrtf(result_error) * error_scale;
++ *out_result_error = sqrtf(vertex_error) * error_scale;
-+ float dei = ei, dej = ej;
-+
- if (attribute_count)
- {
- ei += quadricError(attribute_quadrics[remap[i0]], &attribute_gradients[remap[i0] * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
-@@ -951,6 +955,7 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
- c.v0 = ei <= ej ? i0 : j0;
- c.v1 = ei <= ej ? i1 : j1;
- c.error = ei <= ej ? ei : ej;
-+ c.distance_error = ei <= ej ? dei : dej;
- }
+ return result_count;
}
-
-@@ -1097,7 +1102,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
- triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
- edge_collapses++;
-
-- result_error = result_error < c.error ? c.error : result_error;
-+ result_error = result_error < c.distance_error ? c.distance_error : result_error;
- }
-
- #if TRACE
diff --git a/thirdparty/meshoptimizer/quantization.cpp b/thirdparty/meshoptimizer/quantization.cpp
index 09a314d602..149835f501 100644
--- a/thirdparty/meshoptimizer/quantization.cpp
+++ b/thirdparty/meshoptimizer/quantization.cpp
@@ -3,9 +3,15 @@
#include <assert.h>
+union FloatBits
+{
+ float f;
+ unsigned int ui;
+};
+
unsigned short meshopt_quantizeHalf(float v)
{
- union { float f; unsigned int ui; } u = {v};
+ FloatBits u = {v};
unsigned int ui = u.ui;
int s = (ui >> 16) & 0x8000;
@@ -30,7 +36,7 @@ float meshopt_quantizeFloat(float v, int N)
{
assert(N >= 0 && N <= 23);
- union { float f; unsigned int ui; } u = {v};
+ FloatBits u = {v};
unsigned int ui = u.ui;
const int mask = (1 << (23 - N)) - 1;
@@ -64,7 +70,7 @@ float meshopt_dequantizeHalf(unsigned short h)
// 112 is an exponent bias fixup; since we already applied it once, applying it twice converts 31 to 255
r += (em >= (31 << 10)) ? (112 << 23) : 0;
- union { float f; unsigned int ui; } u;
+ FloatBits u;
u.ui = s | r;
return u.f;
}
diff --git a/thirdparty/meshoptimizer/simplifier.cpp b/thirdparty/meshoptimizer/simplifier.cpp
index 6f8b0e520e..8a7072fe4e 100644
--- a/thirdparty/meshoptimizer/simplifier.cpp
+++ b/thirdparty/meshoptimizer/simplifier.cpp
@@ -111,10 +111,12 @@ struct PositionHasher
{
const float* vertex_positions;
size_t vertex_stride_float;
+ const unsigned int* sparse_remap;
size_t hash(unsigned int index) const
{
- const unsigned int* key = reinterpret_cast<const unsigned int*>(vertex_positions + index * vertex_stride_float);
+ unsigned int ri = sparse_remap ? sparse_remap[index] : index;
+ const unsigned int* key = reinterpret_cast<const unsigned int*>(vertex_positions + ri * vertex_stride_float);
// scramble bits to make sure that integer coordinates have entropy in lower bits
unsigned int x = key[0] ^ (key[0] >> 17);
@@ -127,7 +129,25 @@ struct PositionHasher
bool equal(unsigned int lhs, unsigned int rhs) const
{
- return memcmp(vertex_positions + lhs * vertex_stride_float, vertex_positions + rhs * vertex_stride_float, sizeof(float) * 3) == 0;
+ unsigned int li = sparse_remap ? sparse_remap[lhs] : lhs;
+ unsigned int ri = sparse_remap ? sparse_remap[rhs] : rhs;
+
+ return memcmp(vertex_positions + li * vertex_stride_float, vertex_positions + ri * vertex_stride_float, sizeof(float) * 3) == 0;
+ }
+};
+
+struct RemapHasher
+{
+ unsigned int* remap;
+
+ size_t hash(unsigned int id) const
+ {
+ return id * 0x5bd1e995;
+ }
+
+ bool equal(unsigned int lhs, unsigned int rhs) const
+ {
+ return remap[lhs] == rhs;
}
};
@@ -167,9 +187,9 @@ static T* hashLookup2(T* table, size_t buckets, const Hash& hash, const T& key,
return NULL;
}
-static void buildPositionRemap(unsigned int* remap, unsigned int* wedge, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, meshopt_Allocator& allocator)
+static void buildPositionRemap(unsigned int* remap, unsigned int* wedge, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const unsigned int* sparse_remap, meshopt_Allocator& allocator)
{
- PositionHasher hasher = {vertex_positions_data, vertex_positions_stride / sizeof(float)};
+ PositionHasher hasher = {vertex_positions_data, vertex_positions_stride / sizeof(float), sparse_remap};
size_t table_size = hashBuckets2(vertex_count);
unsigned int* table = allocator.allocate<unsigned int>(table_size);
@@ -205,6 +225,57 @@ static void buildPositionRemap(unsigned int* remap, unsigned int* wedge, const f
allocator.deallocate(table);
}
+static unsigned int* buildSparseRemap(unsigned int* indices, size_t index_count, size_t vertex_count, size_t* out_vertex_count, meshopt_Allocator& allocator)
+{
+ // use a bit set to compute the precise number of unique vertices
+ unsigned char* filter = allocator.allocate<unsigned char>((vertex_count + 7) / 8);
+ memset(filter, 0, (vertex_count + 7) / 8);
+
+ size_t unique = 0;
+ for (size_t i = 0; i < index_count; ++i)
+ {
+ unsigned int index = indices[i];
+ assert(index < vertex_count);
+
+ unique += (filter[index / 8] & (1 << (index % 8))) == 0;
+ filter[index / 8] |= 1 << (index % 8);
+ }
+
+ unsigned int* remap = allocator.allocate<unsigned int>(unique);
+ size_t offset = 0;
+
+ // temporary map dense => sparse; we allocate it last so that we can deallocate it
+ size_t revremap_size = hashBuckets2(unique);
+ unsigned int* revremap = allocator.allocate<unsigned int>(revremap_size);
+ memset(revremap, -1, revremap_size * sizeof(unsigned int));
+
+ // fill remap, using revremap as a helper, and rewrite indices in the same pass
+ RemapHasher hasher = {remap};
+
+ for (size_t i = 0; i < index_count; ++i)
+ {
+ unsigned int index = indices[i];
+
+ unsigned int* entry = hashLookup2(revremap, revremap_size, hasher, index, ~0u);
+
+ if (*entry == ~0u)
+ {
+ remap[offset] = index;
+ *entry = unsigned(offset);
+ offset++;
+ }
+
+ indices[i] = *entry;
+ }
+
+ allocator.deallocate(revremap);
+
+ assert(offset == unique);
+ *out_vertex_count = unique;
+
+ return remap;
+}
+
enum VertexKind
{
Kind_Manifold, // not on an attribute seam, not on any boundary
@@ -217,14 +288,14 @@ enum VertexKind
};
// manifold vertices can collapse onto anything
-// border/seam vertices can only be collapsed onto border/seam respectively
+// border/seam vertices can collapse onto border/seam respectively, or locked
// complex vertices can collapse onto complex/locked
// a rule of thumb is that collapsing kind A into kind B preserves the kind B in the target vertex
// for example, while we could collapse Complex into Manifold, this would mean the target vertex isn't Manifold anymore
const unsigned char kCanCollapse[Kind_Count][Kind_Count] = {
{1, 1, 1, 1, 1},
- {0, 1, 0, 0, 0},
- {0, 0, 1, 0, 0},
+ {0, 1, 0, 0, 1},
+ {0, 0, 1, 0, 1},
{0, 0, 0, 1, 1},
{0, 0, 0, 0, 0},
};
@@ -252,7 +323,7 @@ static bool hasEdge(const EdgeAdjacency& adjacency, unsigned int a, unsigned int
return false;
}
-static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned int* loopback, size_t vertex_count, const EdgeAdjacency& adjacency, const unsigned int* remap, const unsigned int* wedge, unsigned int options)
+static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned int* loopback, size_t vertex_count, const EdgeAdjacency& adjacency, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_lock, const unsigned int* sparse_remap, unsigned int options)
{
memset(loop, -1, vertex_count * sizeof(unsigned int));
memset(loopback, -1, vertex_count * sizeof(unsigned int));
@@ -331,7 +402,7 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
if (openiv != ~0u && openiv != i && openov != ~0u && openov != i &&
openiw != ~0u && openiw != w && openow != ~0u && openow != w)
{
- if (remap[openiv] == remap[openow] && remap[openov] == remap[openiw])
+ if (remap[openiv] == remap[openow] && remap[openov] == remap[openiw] && remap[openiv] != remap[openov])
{
result[i] = Kind_Seam;
}
@@ -362,6 +433,18 @@ static void classifyVertices(unsigned char* result, unsigned int* loop, unsigned
}
}
+ if (vertex_lock)
+ {
+ // vertex_lock may lock any wedge, not just the primary vertex, so we need to lock the primary vertex and relock any wedges
+ for (size_t i = 0; i < vertex_count; ++i)
+ if (vertex_lock[sparse_remap ? sparse_remap[i] : i])
+ result[remap[i]] = Kind_Locked;
+
+ for (size_t i = 0; i < vertex_count; ++i)
+ if (result[remap[i]] == Kind_Locked)
+ result[i] = Kind_Locked;
+ }
+
if (options & meshopt_SimplifyLockBorder)
for (size_t i = 0; i < vertex_count; ++i)
if (result[i] == Kind_Border)
@@ -378,7 +461,7 @@ struct Vector3
float x, y, z;
};
-static float rescalePositions(Vector3* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride)
+static float rescalePositions(Vector3* result, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const unsigned int* sparse_remap = NULL)
{
size_t vertex_stride_float = vertex_positions_stride / sizeof(float);
@@ -387,7 +470,8 @@ static float rescalePositions(Vector3* result, const float* vertex_positions_dat
for (size_t i = 0; i < vertex_count; ++i)
{
- const float* v = vertex_positions_data + i * vertex_stride_float;
+ unsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);
+ const float* v = vertex_positions_data + ri * vertex_stride_float;
if (result)
{
@@ -426,22 +510,25 @@ static float rescalePositions(Vector3* result, const float* vertex_positions_dat
return extent;
}
-static void rescaleAttributes(float* result, const float* vertex_attributes_data, size_t vertex_count, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count)
+static void rescaleAttributes(float* result, const float* vertex_attributes_data, size_t vertex_count, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned int* attribute_remap, const unsigned int* sparse_remap)
{
size_t vertex_attributes_stride_float = vertex_attributes_stride / sizeof(float);
for (size_t i = 0; i < vertex_count; ++i)
{
+ unsigned int ri = sparse_remap ? sparse_remap[i] : unsigned(i);
+
for (size_t k = 0; k < attribute_count; ++k)
{
- float a = vertex_attributes_data[i * vertex_attributes_stride_float + k];
+ unsigned int rk = attribute_remap[k];
+ float a = vertex_attributes_data[ri * vertex_attributes_stride_float + rk];
- result[i * attribute_count + k] = a * attribute_weights[k];
+ result[i * attribute_count + k] = a * attribute_weights[rk];
}
}
}
-static const size_t kMaxAttributes = 16;
+static const size_t kMaxAttributes = 32;
struct Quadric
{
@@ -476,8 +563,6 @@ struct Collapse
float error;
unsigned int errorui;
};
-
- float distance_error;
};
static float normalize(Vector3& v)
@@ -520,7 +605,7 @@ static void quadricAdd(QuadricGrad* G, const QuadricGrad* R, size_t attribute_co
}
}
-static float quadricError(const Quadric& Q, const Vector3& v)
+static float quadricEval(const Quadric& Q, const Vector3& v)
{
float rx = Q.b0;
float ry = Q.b1;
@@ -543,6 +628,12 @@ static float quadricError(const Quadric& Q, const Vector3& v)
r += ry * v.y;
r += rz * v.z;
+ return r;
+}
+
+static float quadricError(const Quadric& Q, const Vector3& v)
+{
+ float r = quadricEval(Q, v);
float s = Q.w == 0.f ? 0.f : 1.f / Q.w;
return fabsf(r) * s;
@@ -550,26 +641,7 @@ static float quadricError(const Quadric& Q, const Vector3& v)
static float quadricError(const Quadric& Q, const QuadricGrad* G, size_t attribute_count, const Vector3& v, const float* va)
{
- float rx = Q.b0;
- float ry = Q.b1;
- float rz = Q.b2;
-
- rx += Q.a10 * v.y;
- ry += Q.a21 * v.z;
- rz += Q.a20 * v.x;
-
- rx *= 2;
- ry *= 2;
- rz *= 2;
-
- rx += Q.a00 * v.x;
- ry += Q.a11 * v.y;
- rz += Q.a22 * v.z;
-
- float r = Q.c;
- r += rx * v.x;
- r += ry * v.y;
- r += rz * v.z;
+ float r = quadricEval(Q, v);
// see quadricFromAttributes for general derivation; here we need to add the parts of (eval(pos) - attr)^2 that depend on attr
for (size_t k = 0; k < attribute_count; ++k)
@@ -577,14 +649,11 @@ static float quadricError(const Quadric& Q, const QuadricGrad* G, size_t attribu
float a = va[k];
float g = v.x * G[k].gx + v.y * G[k].gy + v.z * G[k].gz + G[k].gw;
- r += a * a * Q.w;
- r -= 2 * a * g;
+ r += a * (a * Q.w - 2 * g);
}
- // TODO: weight normalization is breaking attribute error somehow
- float s = 1;// Q.w == 0.f ? 0.f : 1.f / Q.w;
-
- return fabsf(r) * s;
+ // note: unlike position error, we do not normalize by Q.w to retain edge scaling as described in quadricFromAttributes
+ return fabsf(r);
}
static void quadricFromPlane(Quadric& Q, float a, float b, float c, float d, float w)
@@ -625,20 +694,24 @@ static void quadricFromTriangle(Quadric& Q, const Vector3& p0, const Vector3& p1
static void quadricFromTriangleEdge(Quadric& Q, const Vector3& p0, const Vector3& p1, const Vector3& p2, float weight)
{
Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
- float length = normalize(p10);
- // p20p = length of projection of p2-p0 onto normalize(p1 - p0)
+ // edge length; keep squared length around for projection correction
+ float lengthsq = p10.x * p10.x + p10.y * p10.y + p10.z * p10.z;
+ float length = sqrtf(lengthsq);
+
+ // p20p = length of projection of p2-p0 onto p1-p0; note that p10 is unnormalized so we need to correct it later
Vector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};
float p20p = p20.x * p10.x + p20.y * p10.y + p20.z * p10.z;
- // normal = altitude of triangle from point p2 onto edge p1-p0
- Vector3 normal = {p20.x - p10.x * p20p, p20.y - p10.y * p20p, p20.z - p10.z * p20p};
- normalize(normal);
+ // perp = perpendicular vector from p2 to line segment p1-p0
+ // note: since p10 is unnormalized we need to correct the projection; we scale p20 instead to take advantage of normalize below
+ Vector3 perp = {p20.x * lengthsq - p10.x * p20p, p20.y * lengthsq - p10.y * p20p, p20.z * lengthsq - p10.z * p20p};
+ normalize(perp);
- float distance = normal.x * p0.x + normal.y * p0.y + normal.z * p0.z;
+ float distance = perp.x * p0.x + perp.y * p0.y + perp.z * p0.z;
// note: the weight is scaled linearly with edge length; this has to match the triangle weight
- quadricFromPlane(Q, normal.x, normal.y, normal.z, -distance, length * weight);
+ quadricFromPlane(Q, perp.x, perp.y, perp.z, -distance, length * weight);
}
static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0, const Vector3& p1, const Vector3& p2, const float* va0, const float* va1, const float* va2, size_t attribute_count)
@@ -651,16 +724,21 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,
Vector3 p10 = {p1.x - p0.x, p1.y - p0.y, p1.z - p0.z};
Vector3 p20 = {p2.x - p0.x, p2.y - p0.y, p2.z - p0.z};
- // weight is scaled linearly with edge length
+ // normal = cross(p1 - p0, p2 - p0)
Vector3 normal = {p10.y * p20.z - p10.z * p20.y, p10.z * p20.x - p10.x * p20.z, p10.x * p20.y - p10.y * p20.x};
- float area = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z);
- float w = sqrtf(area); // TODO this needs more experimentation
+ float area = sqrtf(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z) * 0.5f;
+
+ // quadric is weighted with the square of edge length (= area)
+ // this equalizes the units with the positional error (which, after normalization, is a square of distance)
+ // as a result, a change in weighted attribute of 1 along distance d is approximately equivalent to a change in position of d
+ float w = area;
// we compute gradients using barycentric coordinates; barycentric coordinates can be computed as follows:
// v = (d11 * d20 - d01 * d21) / denom
// w = (d00 * d21 - d01 * d20) / denom
// u = 1 - v - w
// here v0, v1 are triangle edge vectors, v2 is a vector from point to triangle corner, and dij = dot(vi, vj)
+ // note: v2 and d20/d21 can not be evaluated here as v2 is effectively an unknown variable; we need these only as variables for derivation of gradients
const Vector3& v0 = p10;
const Vector3& v1 = p20;
float d00 = v0.x * v0.x + v0.y * v0.y + v0.z * v0.z;
@@ -670,7 +748,7 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,
float denomr = denom == 0 ? 0.f : 1.f / denom;
// precompute gradient factors
- // these are derived by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w and factoring out common factors that are shared between attributes
+ // these are derived by directly computing derivative of eval(pos) = a0 * u + a1 * v + a2 * w and factoring out expressions that are shared between attributes
float gx1 = (d11 * v0.x - d01 * v1.x) * denomr;
float gx2 = (d00 * v1.x - d01 * v0.x) * denomr;
float gy1 = (d11 * v0.y - d01 * v1.y) * denomr;
@@ -695,6 +773,7 @@ static void quadricFromAttributes(Quadric& Q, QuadricGrad* G, const Vector3& p0,
// quadric encodes (eval(pos)-attr)^2; this means that the resulting expansion needs to compute, for example, pos.x * pos.y * K
// since quadrics already encode factors for pos.x * pos.y, we can accumulate almost everything in basic quadric fields
+ // note: for simplicity we scale all factors by weight here instead of outside the loop
Q.a00 += w * (gx * gx);
Q.a11 += w * (gy * gy);
Q.a22 += w * (gz * gz);
@@ -782,7 +861,7 @@ static void fillEdgeQuadrics(Quadric* vertex_quadrics, const unsigned int* indic
}
}
-static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const float* vertex_attributes, size_t attribute_count, const unsigned int* remap)
+static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, const unsigned int* indices, size_t index_count, const Vector3* vertex_positions, const float* vertex_attributes, size_t attribute_count)
{
for (size_t i = 0; i < index_count; i += 3)
{
@@ -794,14 +873,13 @@ static void fillAttributeQuadrics(Quadric* attribute_quadrics, QuadricGrad* attr
QuadricGrad G[kMaxAttributes];
quadricFromAttributes(QA, G, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], &vertex_attributes[i0 * attribute_count], &vertex_attributes[i1 * attribute_count], &vertex_attributes[i2 * attribute_count], attribute_count);
- // TODO: This blends together attribute weights across attribute discontinuities, which is probably not a great idea
- quadricAdd(attribute_quadrics[remap[i0]], QA);
- quadricAdd(attribute_quadrics[remap[i1]], QA);
- quadricAdd(attribute_quadrics[remap[i2]], QA);
+ quadricAdd(attribute_quadrics[i0], QA);
+ quadricAdd(attribute_quadrics[i1], QA);
+ quadricAdd(attribute_quadrics[i2], QA);
- quadricAdd(&attribute_gradients[remap[i0] * attribute_count], G, attribute_count);
- quadricAdd(&attribute_gradients[remap[i1] * attribute_count], G, attribute_count);
- quadricAdd(&attribute_gradients[remap[i2] * attribute_count], G, attribute_count);
+ quadricAdd(&attribute_gradients[i0 * attribute_count], G, attribute_count);
+ quadricAdd(&attribute_gradients[i1 * attribute_count], G, attribute_count);
+ quadricAdd(&attribute_gradients[i2 * attribute_count], G, attribute_count);
}
}
@@ -815,7 +893,13 @@ static bool hasTriangleFlip(const Vector3& a, const Vector3& b, const Vector3& c
Vector3 nbc = {eb.y * ec.z - eb.z * ec.y, eb.z * ec.x - eb.x * ec.z, eb.x * ec.y - eb.y * ec.x};
Vector3 nbd = {eb.y * ed.z - eb.z * ed.y, eb.z * ed.x - eb.x * ed.z, eb.x * ed.y - eb.y * ed.x};
- return nbc.x * nbd.x + nbc.y * nbd.y + nbc.z * nbd.z <= 0;
+ float ndp = nbc.x * nbd.x + nbc.y * nbd.y + nbc.z * nbd.z;
+ float abc = nbc.x * nbc.x + nbc.y * nbc.y + nbc.z * nbc.z;
+ float abd = nbd.x * nbd.x + nbd.y * nbd.y + nbd.z * nbd.z;
+
+ // scale is cos(angle); somewhat arbitrarily set to ~75 degrees
+ // note that the "pure" check is ndp <= 0 (90 degree cutoff) but that allows flipping through a series of close-to-90 collapses
+ return ndp <= 0.25f * sqrtf(abc * abd);
}
static bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vector3* vertex_positions, const unsigned int* collapse_remap, unsigned int i0, unsigned int i1)
@@ -840,7 +924,13 @@ static bool hasTriangleFlips(const EdgeAdjacency& adjacency, const Vector3* vert
// early-out when at least one triangle flips due to a collapse
if (hasTriangleFlip(vertex_positions[a], vertex_positions[b], v0, v1))
+ {
+#if TRACE >= 2
+ printf("edge block %d -> %d: flip welded %d %d %d\n", i0, i1, a, i0, b);
+#endif
+
return true;
+ }
}
return false;
@@ -864,7 +954,7 @@ static size_t boundEdgeCollapses(const EdgeAdjacency& adjacency, size_t vertex_c
return (index_count - dual_count / 2) + 3;
}
-static size_t pickEdgeCollapses(Collapse* collapses, size_t collapse_capacity, const unsigned int* indices, size_t index_count, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop)
+static size_t pickEdgeCollapses(Collapse* collapses, size_t collapse_capacity, const unsigned int* indices, size_t index_count, const unsigned int* remap, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback)
{
size_t collapse_count = 0;
@@ -904,6 +994,16 @@ static size_t pickEdgeCollapses(Collapse* collapses, size_t collapse_capacity, c
if (k0 == k1 && (k0 == Kind_Border || k0 == Kind_Seam) && loop[i0] != i1)
continue;
+ if (k0 == Kind_Locked || k1 == Kind_Locked)
+ {
+ // the same check as above, but for border/seam -> locked collapses
+ // loop[] and loopback[] track half edges so we only need to check one of them
+ if ((k0 == Kind_Border || k0 == Kind_Seam) && loop[i0] != i1)
+ continue;
+ if ((k1 == Kind_Border || k1 == Kind_Seam) && loopback[i1] != i0)
+ continue;
+ }
+
// edge can be collapsed in either direction - we will pick the one with minimum error
// note: we evaluate error later during collapse ranking, here we just tag the edge as bidirectional
if (kCanCollapse[k0][k1] & kCanCollapse[k1][k0])
@@ -943,34 +1043,52 @@ static void rankEdgeCollapses(Collapse* collapses, size_t collapse_count, const
float ei = quadricError(vertex_quadrics[remap[i0]], vertex_positions[i1]);
float ej = quadricError(vertex_quadrics[remap[j0]], vertex_positions[j1]);
- float dei = ei, dej = ej;
+#if TRACE >= 3
+ float di = ei, dj = ej;
+#endif
if (attribute_count)
{
- ei += quadricError(attribute_quadrics[remap[i0]], &attribute_gradients[remap[i0] * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
- ej += quadricError(attribute_quadrics[remap[j0]], &attribute_gradients[remap[j0] * attribute_count], attribute_count, vertex_positions[j1], &vertex_attributes[j1 * attribute_count]);
+ // note: ideally we would evaluate max/avg of attribute errors for seam edges, but it's not clear if it's worth the extra cost
+ ei += quadricError(attribute_quadrics[i0], &attribute_gradients[i0 * attribute_count], attribute_count, vertex_positions[i1], &vertex_attributes[i1 * attribute_count]);
+ ej += quadricError(attribute_quadrics[j0], &attribute_gradients[j0 * attribute_count], attribute_count, vertex_positions[j1], &vertex_attributes[j1 * attribute_count]);
}
// pick edge direction with minimal error
c.v0 = ei <= ej ? i0 : j0;
c.v1 = ei <= ej ? i1 : j1;
c.error = ei <= ej ? ei : ej;
- c.distance_error = ei <= ej ? dei : dej;
+
+#if TRACE >= 3
+ if (i0 == j0) // c.bidi has been overwritten
+ printf("edge eval %d -> %d: error %f (pos %f, attr %f)\n", c.v0, c.v1,
+ sqrtf(c.error), sqrtf(ei <= ej ? di : dj), sqrtf(ei <= ej ? ei - di : ej - dj));
+ else
+ printf("edge eval %d -> %d: error %f (pos %f, attr %f); reverse %f (pos %f, attr %f)\n", c.v0, c.v1,
+ sqrtf(ei <= ej ? ei : ej), sqrtf(ei <= ej ? di : dj), sqrtf(ei <= ej ? ei - di : ej - dj),
+ sqrtf(ei <= ej ? ej : ei), sqrtf(ei <= ej ? dj : di), sqrtf(ei <= ej ? ej - dj : ei - di));
+#endif
}
}
static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapses, size_t collapse_count)
{
- const int sort_bits = 11;
+ // we use counting sort to order collapses by error; since the exact sort order is not as critical,
+ // only top 12 bits of exponent+mantissa (8 bits of exponent and 4 bits of mantissa) are used.
+ // to avoid excessive stack usage, we clamp the exponent range as collapses with errors much higher than 1 are not useful.
+ const unsigned int sort_bits = 12;
+ const unsigned int sort_bins = 2048 + 512; // exponent range [-127, 32)
// fill histogram for counting sort
- unsigned int histogram[1 << sort_bits];
+ unsigned int histogram[sort_bins];
memset(histogram, 0, sizeof(histogram));
for (size_t i = 0; i < collapse_count; ++i)
{
// skip sign bit since error is non-negative
- unsigned int key = (collapses[i].errorui << 1) >> (32 - sort_bits);
+ unsigned int error = collapses[i].errorui;
+ unsigned int key = (error << 1) >> (32 - sort_bits);
+ key = key < sort_bins ? key : sort_bins - 1;
histogram[key]++;
}
@@ -978,7 +1096,7 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse
// compute offsets based on histogram data
size_t histogram_sum = 0;
- for (size_t i = 0; i < 1 << sort_bits; ++i)
+ for (size_t i = 0; i < sort_bins; ++i)
{
size_t count = histogram[i];
histogram[i] = unsigned(histogram_sum);
@@ -991,13 +1109,15 @@ static void sortEdgeCollapses(unsigned int* sort_order, const Collapse* collapse
for (size_t i = 0; i < collapse_count; ++i)
{
// skip sign bit since error is non-negative
- unsigned int key = (collapses[i].errorui << 1) >> (32 - sort_bits);
+ unsigned int error = collapses[i].errorui;
+ unsigned int key = (error << 1) >> (32 - sort_bits);
+ key = key < sort_bins ? key : sort_bins - 1;
sort_order[histogram[key]++] = unsigned(i);
}
}
-static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, Quadric* vertex_quadrics, Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, size_t attribute_count, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error)
+static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char* collapse_locked, const Collapse* collapses, size_t collapse_count, const unsigned int* collapse_order, const unsigned int* remap, const unsigned int* wedge, const unsigned char* vertex_kind, const unsigned int* loop, const unsigned int* loopback, const Vector3* vertex_positions, const EdgeAdjacency& adjacency, size_t triangle_collapse_goal, float error_limit, float& result_error)
{
size_t edge_collapses = 0;
size_t triangle_collapses = 0;
@@ -1007,7 +1127,7 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
size_t edge_collapse_goal = triangle_collapse_goal / 2;
#if TRACE
- size_t stats[4] = {};
+ size_t stats[7] = {};
#endif
for (size_t i = 0; i < collapse_count; ++i)
@@ -1017,10 +1137,16 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
TRACESTATS(0);
if (c.error > error_limit)
+ {
+ TRACESTATS(4);
break;
+ }
if (triangle_collapses >= triangle_collapse_goal)
+ {
+ TRACESTATS(5);
break;
+ }
// we limit the error in each pass based on the error of optimal last collapse; since many collapses will be locked
// as they will share vertices with other successfull collapses, we need to increase the acceptable error by some factor
@@ -1028,8 +1154,11 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
// on average, each collapse is expected to lock 6 other collapses; to avoid degenerate passes on meshes with odd
// topology, we only abort if we got over 1/6 collapses accordingly.
- if (c.error > error_goal && triangle_collapses > triangle_collapse_goal / 6)
+ if (c.error > error_goal && c.error > result_error && triangle_collapses > triangle_collapse_goal / 6)
+ {
+ TRACESTATS(6);
break;
+ }
unsigned int i0 = c.v0;
unsigned int i1 = c.v1;
@@ -1037,6 +1166,8 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
unsigned int r0 = remap[i0];
unsigned int r1 = remap[i1];
+ unsigned char kind = vertex_kind[i0];
+
// we don't collapse vertices that had source or target vertex involved in a collapse
// it's important to not move the vertices twice since it complicates the tracking/remapping logic
// it's important to not move other vertices towards a moved vertex to preserve error since we don't re-rank collapses mid-pass
@@ -1055,35 +1186,39 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
continue;
}
+#if TRACE >= 2
+ printf("edge commit %d -> %d: kind %d->%d, error %f\n", i0, i1, vertex_kind[i0], vertex_kind[i1], sqrtf(c.error));
+#endif
+
assert(collapse_remap[r0] == r0);
assert(collapse_remap[r1] == r1);
- quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
-
- if (attribute_count)
- {
- quadricAdd(attribute_quadrics[r1], attribute_quadrics[r0]);
- quadricAdd(&attribute_gradients[r1 * attribute_count], &attribute_gradients[r0 * attribute_count], attribute_count);
- }
-
- if (vertex_kind[i0] == Kind_Complex)
+ if (kind == Kind_Complex)
{
+ // remap all vertices in the complex to the target vertex
unsigned int v = i0;
do
{
- collapse_remap[v] = r1;
+ collapse_remap[v] = i1;
v = wedge[v];
} while (v != i0);
}
- else if (vertex_kind[i0] == Kind_Seam)
+ else if (kind == Kind_Seam)
{
- // remap v0 to v1 and seam pair of v0 to seam pair of v1
+ // for seam collapses we need to move the seam pair together; this is a bit tricky to compute since we need to rely on edge loops as target vertex may be locked (and thus have more than two wedges)
unsigned int s0 = wedge[i0];
- unsigned int s1 = wedge[i1];
+ unsigned int s1 = loop[i0] == i1 ? loopback[s0] : loop[s0];
+ assert(s0 != i0 && wedge[s0] == i0);
+ assert(s1 != ~0u && remap[s1] == r1);
+
+ // additional asserts to verify that the seam pair is consistent
+ assert(kind != vertex_kind[i1] || s1 == wedge[i1]);
+ assert(loop[i0] == i1 || loopback[i0] == i1);
+ assert(loop[s0] == s1 || loopback[s0] == s1);
- assert(s0 != i0 && s1 != i1);
- assert(wedge[s0] == i0 && wedge[s1] == i1);
+ // note: this should never happen due to the assertion above, but when disabled if we ever hit this case we'll get a memory safety issue; for now play it safe
+ s1 = (s1 != ~0u) ? s1 : wedge[i1];
collapse_remap[i0] = i1;
collapse_remap[s0] = s1;
@@ -1095,27 +1230,63 @@ static size_t performEdgeCollapses(unsigned int* collapse_remap, unsigned char*
collapse_remap[i0] = i1;
}
+ // note: we technically don't need to lock r1 if it's a locked vertex, as it can't move and its quadric won't be used
+ // however, this results in slightly worse error on some meshes because the locked collapses get an unfair advantage wrt scheduling
collapse_locked[r0] = 1;
collapse_locked[r1] = 1;
// border edges collapse 1 triangle, other edges collapse 2 or more
- triangle_collapses += (vertex_kind[i0] == Kind_Border) ? 1 : 2;
+ triangle_collapses += (kind == Kind_Border) ? 1 : 2;
edge_collapses++;
- result_error = result_error < c.distance_error ? c.distance_error : result_error;
+ result_error = result_error < c.error ? c.error : result_error;
}
#if TRACE
- float error_goal_perfect = edge_collapse_goal < collapse_count ? collapses[collapse_order[edge_collapse_goal]].error : 0.f;
+ float error_goal_last = edge_collapse_goal < collapse_count ? 1.5f * collapses[collapse_order[edge_collapse_goal]].error : FLT_MAX;
+ float error_goal_limit = error_goal_last < error_limit ? error_goal_last : error_limit;
- printf("removed %d triangles, error %e (goal %e); evaluated %d/%d collapses (done %d, skipped %d, invalid %d)\n",
- int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_perfect),
- int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2]));
+ printf("removed %d triangles, error %e (goal %e); evaluated %d/%d collapses (done %d, skipped %d, invalid %d); %s\n",
+ int(triangle_collapses), sqrtf(result_error), sqrtf(error_goal_limit),
+ int(stats[0]), int(collapse_count), int(edge_collapses), int(stats[1]), int(stats[2]),
+ stats[4] ? "error limit" : (stats[5] ? "count limit" : (stats[6] ? "error goal" : "out of collapses")));
#endif
return edge_collapses;
}
+static void updateQuadrics(const unsigned int* collapse_remap, size_t vertex_count, Quadric* vertex_quadrics, Quadric* attribute_quadrics, QuadricGrad* attribute_gradients, size_t attribute_count, const Vector3* vertex_positions, const unsigned int* remap, float& vertex_error)
+{
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ if (collapse_remap[i] == i)
+ continue;
+
+ unsigned int i0 = unsigned(i);
+ unsigned int i1 = collapse_remap[i];
+
+ unsigned int r0 = remap[i0];
+ unsigned int r1 = remap[i1];
+
+ // ensure we only update vertex_quadrics once: primary vertex must be moved if any wedge is moved
+ if (i0 == r0)
+ quadricAdd(vertex_quadrics[r1], vertex_quadrics[r0]);
+
+ if (attribute_count)
+ {
+ quadricAdd(attribute_quadrics[i1], attribute_quadrics[i0]);
+ quadricAdd(&attribute_gradients[i1 * attribute_count], &attribute_gradients[i0 * attribute_count], attribute_count);
+
+ if (i0 == r0)
+ {
+ // when attributes are used, distance error needs to be recomputed as collapses don't track it; it is safe to do this after the quadric adjustment
+ float derr = quadricError(vertex_quadrics[r0], vertex_positions[r1]);
+ vertex_error = vertex_error < derr ? derr : vertex_error;
+ }
+ }
+ }
+}
+
static size_t remapIndexBuffer(unsigned int* indices, size_t index_count, const unsigned int* collapse_remap)
{
size_t write = 0;
@@ -1147,15 +1318,179 @@ static void remapEdgeLoops(unsigned int* loop, size_t vertex_count, const unsign
{
for (size_t i = 0; i < vertex_count; ++i)
{
+ // note: this is a no-op for vertices that were remapped
+ // ideally we would clear the loop entries for those for consistency, even though they aren't going to be used
+ // however, the remapping process needs loop information for remapped vertices, so this would require a separate pass
if (loop[i] != ~0u)
{
unsigned int l = loop[i];
unsigned int r = collapse_remap[l];
// i == r is a special case when the seam edge is collapsed in a direction opposite to where loop goes
- loop[i] = (i == r) ? loop[l] : r;
+ if (i == r)
+ loop[i] = (loop[l] != ~0u) ? collapse_remap[loop[l]] : ~0u;
+ else
+ loop[i] = r;
+ }
+ }
+}
+
+static unsigned int follow(unsigned int* parents, unsigned int index)
+{
+ while (index != parents[index])
+ {
+ unsigned int parent = parents[index];
+ parents[index] = parents[parent];
+ index = parent;
+ }
+
+ return index;
+}
+
+static size_t buildComponents(unsigned int* components, size_t vertex_count, const unsigned int* indices, size_t index_count, const unsigned int* remap)
+{
+ for (size_t i = 0; i < vertex_count; ++i)
+ components[i] = unsigned(i);
+
+ // compute a unique (but not sequential!) index for each component via union-find
+ for (size_t i = 0; i < index_count; i += 3)
+ {
+ static const int next[4] = {1, 2, 0, 1};
+
+ for (int e = 0; e < 3; ++e)
+ {
+ unsigned int i0 = indices[i + e];
+ unsigned int i1 = indices[i + next[e]];
+
+ unsigned int r0 = remap[i0];
+ unsigned int r1 = remap[i1];
+
+ r0 = follow(components, r0);
+ r1 = follow(components, r1);
+
+ // merge components with larger indices into components with smaller indices
+ // this guarantees that the root of the component is always the one with the smallest index
+ if (r0 != r1)
+ components[r0 < r1 ? r1 : r0] = r0 < r1 ? r0 : r1;
}
}
+
+ // make sure each element points to the component root *before* we renumber the components
+ for (size_t i = 0; i < vertex_count; ++i)
+ if (remap[i] == i)
+ components[i] = follow(components, unsigned(i));
+
+ unsigned int next_component = 0;
+
+ // renumber components using sequential indices
+ // a sequential pass is sufficient because component root always has the smallest index
+ // note: it is unsafe to use follow() in this pass because we're replacing component links with sequential indices inplace
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ if (remap[i] == i)
+ {
+ unsigned int root = components[i];
+ assert(root <= i); // make sure we already computed the component for non-roots
+ components[i] = (root == i) ? next_component++ : components[root];
+ }
+ else
+ {
+ assert(remap[i] < i); // make sure we already computed the component
+ components[i] = components[remap[i]];
+ }
+ }
+
+ return next_component;
+}
+
+static void measureComponents(float* component_errors, size_t component_count, const unsigned int* components, const Vector3* vertex_positions, size_t vertex_count)
+{
+ memset(component_errors, 0, component_count * 4 * sizeof(float));
+
+ // compute approximate sphere center for each component as an average
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ unsigned int c = components[i];
+ assert(components[i] < component_count);
+
+ Vector3 v = vertex_positions[i]; // copy avoids aliasing issues
+
+ component_errors[c * 4 + 0] += v.x;
+ component_errors[c * 4 + 1] += v.y;
+ component_errors[c * 4 + 2] += v.z;
+ component_errors[c * 4 + 3] += 1; // weight
+ }
+
+ // complete the center computation, and reinitialize [3] as a radius
+ for (size_t i = 0; i < component_count; ++i)
+ {
+ float w = component_errors[i * 4 + 3];
+ float iw = w == 0.f ? 0.f : 1.f / w;
+
+ component_errors[i * 4 + 0] *= iw;
+ component_errors[i * 4 + 1] *= iw;
+ component_errors[i * 4 + 2] *= iw;
+ component_errors[i * 4 + 3] = 0; // radius
+ }
+
+ // compute squared radius for each component
+ for (size_t i = 0; i < vertex_count; ++i)
+ {
+ unsigned int c = components[i];
+
+ float dx = vertex_positions[i].x - component_errors[c * 4 + 0];
+ float dy = vertex_positions[i].y - component_errors[c * 4 + 1];
+ float dz = vertex_positions[i].z - component_errors[c * 4 + 2];
+ float r = dx * dx + dy * dy + dz * dz;
+
+ component_errors[c * 4 + 3] = component_errors[c * 4 + 3] < r ? r : component_errors[c * 4 + 3];
+ }
+
+ // we've used the output buffer as scratch space, so we need to move the results to proper indices
+ for (size_t i = 0; i < component_count; ++i)
+ {
+#if TRACE >= 2
+ printf("component %d: center %f %f %f, error %e\n", int(i),
+ component_errors[i * 4 + 0], component_errors[i * 4 + 1], component_errors[i * 4 + 2], sqrtf(component_errors[i * 4 + 3]));
+#endif
+ // note: we keep the squared error to make it match quadric error metric
+ component_errors[i] = component_errors[i * 4 + 3];
+ }
+}
+
+static size_t pruneComponents(unsigned int* indices, size_t index_count, const unsigned int* components, const float* component_errors, size_t component_count, float error_cutoff, float& nexterror)
+{
+ size_t write = 0;
+
+ for (size_t i = 0; i < index_count; i += 3)
+ {
+ unsigned int c = components[indices[i]];
+ assert(c == components[indices[i + 1]] && c == components[indices[i + 2]]);
+
+ if (component_errors[c] > error_cutoff)
+ {
+ indices[write + 0] = indices[i + 0];
+ indices[write + 1] = indices[i + 1];
+ indices[write + 2] = indices[i + 2];
+ write += 3;
+ }
+ }
+
+#if TRACE
+ size_t pruned_components = 0;
+ for (size_t i = 0; i < component_count; ++i)
+ pruned_components += (component_errors[i] >= nexterror && component_errors[i] <= error_cutoff);
+
+ printf("pruned %d triangles in %d components (goal %e)\n", int((index_count - write) / 3), int(pruned_components), sqrtf(error_cutoff));
+#endif
+
+ // update next error with the smallest error of the remaining components for future pruning
+ nexterror = FLT_MAX;
+ for (size_t i = 0; i < component_count; ++i)
+ if (component_errors[i] > error_cutoff)
+ nexterror = nexterror > component_errors[i] ? component_errors[i] : nexterror;
+
+ return write;
}
struct CellHasher
@@ -1310,7 +1645,7 @@ static void fillCellQuadrics(Quadric* cell_quadrics, const unsigned int* indices
unsigned int c1 = vertex_cells[i1];
unsigned int c2 = vertex_cells[i2];
- bool single_cell = (c0 == c1) & (c0 == c2);
+ int single_cell = (c0 == c1) & (c0 == c2);
Quadric Q;
quadricFromTriangle(Q, vertex_positions[i0], vertex_positions[i1], vertex_positions[i2], single_cell ? 3.f : 1.f);
@@ -1330,7 +1665,7 @@ static void fillCellQuadrics(Quadric* cell_quadrics, const unsigned int* indices
static void fillCellReservoirs(Reservoir* cell_reservoirs, size_t cell_count, const Vector3* vertex_positions, const float* vertex_colors, size_t vertex_colors_stride, size_t vertex_count, const unsigned int* vertex_cells)
{
- static const float dummy_color[] = { 0.f, 0.f, 0.f };
+ static const float dummy_color[] = {0.f, 0.f, 0.f};
size_t vertex_colors_stride_float = vertex_colors_stride / sizeof(float);
@@ -1385,7 +1720,7 @@ static void fillCellRemap(unsigned int* cell_remap, float* cell_errors, size_t c
static void fillCellRemap(unsigned int* cell_remap, float* cell_errors, size_t cell_count, const unsigned int* vertex_cells, const Reservoir* cell_reservoirs, const Vector3* vertex_positions, const float* vertex_colors, size_t vertex_colors_stride, float color_weight, size_t vertex_count)
{
- static const float dummy_color[] = { 0.f, 0.f, 0.f };
+ static const float dummy_color[] = {0.f, 0.f, 0.f};
size_t vertex_colors_stride_float = vertex_colors_stride / sizeof(float);
@@ -1466,14 +1801,13 @@ static float interpolate(float y, float x0, float y0, float x1, float y1, float
} // namespace meshopt
-#ifndef NDEBUG
-// Note: this is only exposed for debug visualization purposes; do *not* use these in debug builds
-MESHOPTIMIZER_API unsigned char* meshopt_simplifyDebugKind = NULL;
-MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoop = NULL;
-MESHOPTIMIZER_API unsigned int* meshopt_simplifyDebugLoopBack = NULL;
-#endif
+// Note: this is only exposed for debug visualization purposes; do *not* use
+enum
+{
+ meshopt_SimplifyInternalDebug = 1 << 30
+};
-size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
+size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
{
using namespace meshopt;
@@ -1481,30 +1815,41 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
assert(vertex_positions_stride >= 12 && vertex_positions_stride <= 256);
assert(vertex_positions_stride % sizeof(float) == 0);
assert(target_index_count <= index_count);
- assert((options & ~(meshopt_SimplifyLockBorder)) == 0);
+ assert(target_error >= 0);
+ assert((options & ~(meshopt_SimplifyLockBorder | meshopt_SimplifySparse | meshopt_SimplifyErrorAbsolute | meshopt_SimplifyPrune | meshopt_SimplifyInternalDebug)) == 0);
assert(vertex_attributes_stride >= attribute_count * sizeof(float) && vertex_attributes_stride <= 256);
assert(vertex_attributes_stride % sizeof(float) == 0);
assert(attribute_count <= kMaxAttributes);
+ for (size_t i = 0; i < attribute_count; ++i)
+ assert(attribute_weights[i] >= 0);
meshopt_Allocator allocator;
unsigned int* result = destination;
+ if (result != indices)
+ memcpy(result, indices, index_count * sizeof(unsigned int));
+
+ // build an index remap and update indices/vertex_count to minimize the subsequent work
+ // note: as a consequence, errors will be computed relative to the subset extent
+ unsigned int* sparse_remap = NULL;
+ if (options & meshopt_SimplifySparse)
+ sparse_remap = buildSparseRemap(result, index_count, vertex_count, &vertex_count, allocator);
// build adjacency information
EdgeAdjacency adjacency = {};
prepareEdgeAdjacency(adjacency, index_count, vertex_count, allocator);
- updateEdgeAdjacency(adjacency, indices, index_count, vertex_count, NULL);
+ updateEdgeAdjacency(adjacency, result, index_count, vertex_count, NULL);
// build position remap that maps each vertex to the one with identical position
unsigned int* remap = allocator.allocate<unsigned int>(vertex_count);
unsigned int* wedge = allocator.allocate<unsigned int>(vertex_count);
- buildPositionRemap(remap, wedge, vertex_positions_data, vertex_count, vertex_positions_stride, allocator);
+ buildPositionRemap(remap, wedge, vertex_positions_data, vertex_count, vertex_positions_stride, sparse_remap, allocator);
// classify vertices; vertex kind determines collapse rules, see kCanCollapse
unsigned char* vertex_kind = allocator.allocate<unsigned char>(vertex_count);
unsigned int* loop = allocator.allocate<unsigned int>(vertex_count);
unsigned int* loopback = allocator.allocate<unsigned int>(vertex_count);
- classifyVertices(vertex_kind, loop, loopback, vertex_count, adjacency, remap, wedge, options);
+ classifyVertices(vertex_kind, loop, loopback, vertex_count, adjacency, remap, wedge, vertex_lock, sparse_remap, options);
#if TRACE
size_t unique_positions = 0;
@@ -1522,14 +1867,23 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
#endif
Vector3* vertex_positions = allocator.allocate<Vector3>(vertex_count);
- rescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride);
+ float vertex_scale = rescalePositions(vertex_positions, vertex_positions_data, vertex_count, vertex_positions_stride, sparse_remap);
float* vertex_attributes = NULL;
if (attribute_count)
{
+ unsigned int attribute_remap[kMaxAttributes];
+
+ // remap attributes to only include ones with weight > 0 to minimize memory/compute overhead for quadrics
+ size_t attributes_used = 0;
+ for (size_t i = 0; i < attribute_count; ++i)
+ if (attribute_weights[i] > 0)
+ attribute_remap[attributes_used++] = unsigned(i);
+
+ attribute_count = attributes_used;
vertex_attributes = allocator.allocate<float>(vertex_count * attribute_count);
- rescaleAttributes(vertex_attributes, vertex_attributes_data, vertex_count, vertex_attributes_stride, attribute_weights, attribute_count);
+ rescaleAttributes(vertex_attributes, vertex_attributes_data, vertex_count, vertex_attributes_stride, attribute_weights, attribute_count, attribute_remap, sparse_remap);
}
Quadric* vertex_quadrics = allocator.allocate<Quadric>(vertex_count);
@@ -1547,14 +1901,33 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
memset(attribute_gradients, 0, vertex_count * attribute_count * sizeof(QuadricGrad));
}
- fillFaceQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap);
- fillEdgeQuadrics(vertex_quadrics, indices, index_count, vertex_positions, remap, vertex_kind, loop, loopback);
+ fillFaceQuadrics(vertex_quadrics, result, index_count, vertex_positions, remap);
+ fillEdgeQuadrics(vertex_quadrics, result, index_count, vertex_positions, remap, vertex_kind, loop, loopback);
if (attribute_count)
- fillAttributeQuadrics(attribute_quadrics, attribute_gradients, indices, index_count, vertex_positions, vertex_attributes, attribute_count, remap);
+ fillAttributeQuadrics(attribute_quadrics, attribute_gradients, result, index_count, vertex_positions, vertex_attributes, attribute_count);
- if (result != indices)
- memcpy(result, indices, index_count * sizeof(unsigned int));
+ unsigned int* components = NULL;
+ float* component_errors = NULL;
+ size_t component_count = 0;
+ float component_nexterror = 0;
+
+ if (options & meshopt_SimplifyPrune)
+ {
+ components = allocator.allocate<unsigned int>(vertex_count);
+ component_count = buildComponents(components, vertex_count, result, index_count, remap);
+
+ component_errors = allocator.allocate<float>(component_count * 4); // overallocate for temporary use inside measureComponents
+ measureComponents(component_errors, component_count, components, vertex_positions, vertex_count);
+
+ component_nexterror = FLT_MAX;
+ for (size_t i = 0; i < component_count; ++i)
+ component_nexterror = component_nexterror > component_errors[i] ? component_errors[i] : component_nexterror;
+
+#if TRACE
+ printf("components: %d (min error %e)\n", int(component_count), sqrtf(component_nexterror));
+#endif
+ }
#if TRACE
size_t pass_count = 0;
@@ -1569,22 +1942,28 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
size_t result_count = index_count;
float result_error = 0;
+ float vertex_error = 0;
// target_error input is linear; we need to adjust it to match quadricError units
- float error_limit = target_error * target_error;
+ float error_scale = (options & meshopt_SimplifyErrorAbsolute) ? vertex_scale : 1.f;
+ float error_limit = (target_error * target_error) / (error_scale * error_scale);
while (result_count > target_index_count)
{
// note: throughout the simplification process adjacency structure reflects welded topology for result-in-progress
updateEdgeAdjacency(adjacency, result, result_count, vertex_count, remap);
- size_t edge_collapse_count = pickEdgeCollapses(edge_collapses, collapse_capacity, result, result_count, remap, vertex_kind, loop);
+ size_t edge_collapse_count = pickEdgeCollapses(edge_collapses, collapse_capacity, result, result_count, remap, vertex_kind, loop, loopback);
assert(edge_collapse_count <= collapse_capacity);
// no edges can be collapsed any more due to topology restrictions
if (edge_collapse_count == 0)
break;
+#if TRACE
+ printf("pass %d:%c", int(pass_count++), TRACE >= 2 ? '\n' : ' ');
+#endif
+
rankEdgeCollapses(edge_collapses, edge_collapse_count, vertex_positions, vertex_attributes, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, remap);
sortEdgeCollapses(collapse_order, edge_collapses, edge_collapse_count);
@@ -1596,16 +1975,17 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
memset(collapse_locked, 0, vertex_count);
-#if TRACE
- printf("pass %d: ", int(pass_count++));
-#endif
-
- size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error);
+ size_t collapses = performEdgeCollapses(collapse_remap, collapse_locked, edge_collapses, edge_collapse_count, collapse_order, remap, wedge, vertex_kind, loop, loopback, vertex_positions, adjacency, triangle_collapse_goal, error_limit, result_error);
// no edges can be collapsed any more due to hitting the error limit or triangle collapse limit
if (collapses == 0)
break;
+ updateQuadrics(collapse_remap, vertex_count, vertex_quadrics, attribute_quadrics, attribute_gradients, attribute_count, vertex_positions, remap, vertex_error);
+
+ // updateQuadrics will update vertex error if we use attributes, but if we don't then result_error and vertex_error are equivalent
+ vertex_error = attribute_count == 0 ? result_error : vertex_error;
+
remapEdgeLoops(loop, vertex_count, collapse_remap);
remapEdgeLoops(loopback, vertex_count, collapse_remap);
@@ -1613,38 +1993,74 @@ size_t meshopt_simplifyEdge(unsigned int* destination, const unsigned int* indic
assert(new_count < result_count);
result_count = new_count;
+
+ if ((options & meshopt_SimplifyPrune) && result_count > target_index_count && component_nexterror <= vertex_error)
+ result_count = pruneComponents(result, result_count, components, component_errors, component_count, vertex_error, component_nexterror);
}
+ // we're done with the regular simplification but we're still short of the target; try pruning more aggressively towards error_limit
+ while ((options & meshopt_SimplifyPrune) && result_count > target_index_count && component_nexterror <= error_limit)
+ {
#if TRACE
- printf("result: %d triangles, error: %e; total %d passes\n", int(result_count), sqrtf(result_error), int(pass_count));
+ printf("pass %d: cleanup; ", int(pass_count++));
#endif
-#ifndef NDEBUG
- if (meshopt_simplifyDebugKind)
- memcpy(meshopt_simplifyDebugKind, vertex_kind, vertex_count);
+ float component_cutoff = component_nexterror * 1.5f < error_limit ? component_nexterror * 1.5f : error_limit;
- if (meshopt_simplifyDebugLoop)
- memcpy(meshopt_simplifyDebugLoop, loop, vertex_count * sizeof(unsigned int));
+ // track maximum error in eligible components as we are increasing resulting error
+ float component_maxerror = 0;
+ for (size_t i = 0; i < component_count; ++i)
+ if (component_errors[i] > component_maxerror && component_errors[i] <= component_cutoff)
+ component_maxerror = component_errors[i];
- if (meshopt_simplifyDebugLoopBack)
- memcpy(meshopt_simplifyDebugLoopBack, loopback, vertex_count * sizeof(unsigned int));
+ size_t new_count = pruneComponents(result, result_count, components, component_errors, component_count, component_cutoff, component_nexterror);
+ if (new_count == result_count)
+ break;
+
+ result_count = new_count;
+ result_error = result_error < component_maxerror ? component_maxerror : result_error;
+ vertex_error = vertex_error < component_maxerror ? component_maxerror : vertex_error;
+ }
+
+#if TRACE
+ printf("result: %d triangles, error: %e; total %d passes\n", int(result_count / 3), sqrtf(result_error), int(pass_count));
#endif
+ // if debug visualization data is requested, fill it instead of index data; for simplicity, this doesn't work with sparsity
+ if ((options & meshopt_SimplifyInternalDebug) && !sparse_remap)
+ {
+ assert(Kind_Count <= 8 && vertex_count < (1 << 28)); // 3 bit kind, 1 bit loop
+
+ for (size_t i = 0; i < result_count; i += 3)
+ {
+ unsigned int a = result[i + 0], b = result[i + 1], c = result[i + 2];
+
+ result[i + 0] |= (vertex_kind[a] << 28) | (unsigned(loop[a] == b || loopback[b] == a) << 31);
+ result[i + 1] |= (vertex_kind[b] << 28) | (unsigned(loop[b] == c || loopback[c] == b) << 31);
+ result[i + 2] |= (vertex_kind[c] << 28) | (unsigned(loop[c] == a || loopback[a] == c) << 31);
+ }
+ }
+
+ // convert resulting indices back into the dense space of the larger mesh
+ if (sparse_remap)
+ for (size_t i = 0; i < result_count; ++i)
+ result[i] = sparse_remap[result[i]];
+
// result_error is quadratic; we need to remap it back to linear
if (out_result_error)
- *out_result_error = sqrtf(result_error);
+ *out_result_error = sqrtf(vertex_error) * error_scale;
return result_count;
}
size_t meshopt_simplify(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
{
- return meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, NULL, 0, NULL, 0, target_index_count, target_error, options, out_result_error);
+ return meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, NULL, 0, NULL, 0, NULL, target_index_count, target_error, options, out_result_error);
}
-size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
+size_t meshopt_simplifyWithAttributes(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, const float* vertex_attributes_data, size_t vertex_attributes_stride, const float* attribute_weights, size_t attribute_count, const unsigned char* vertex_lock, size_t target_index_count, float target_error, unsigned int options, float* out_result_error)
{
- return meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, vertex_attributes_data, vertex_attributes_stride, attribute_weights, attribute_count, target_index_count, target_error, options, out_result_error);
+ return meshopt_simplifyEdge(destination, indices, index_count, vertex_positions_data, vertex_count, vertex_positions_stride, vertex_attributes_data, vertex_attributes_stride, attribute_weights, attribute_count, vertex_lock, target_index_count, target_error, options, out_result_error);
}
size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* indices, size_t index_count, const float* vertex_positions_data, size_t vertex_count, size_t vertex_positions_stride, size_t target_index_count, float target_error, float* out_result_error)
@@ -1697,19 +2113,19 @@ size_t meshopt_simplifySloppy(unsigned int* destination, const unsigned int* ind
// we clamp the prediction of the grid size to make sure that the search converges
int grid_size = next_grid_size;
- grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
+ grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid ? max_grid - 1 : grid_size);
computeVertexIds(vertex_ids, vertex_positions, vertex_count, grid_size);
size_t triangles = countTriangles(vertex_ids, indices, index_count);
#if TRACE
printf("pass %d (%s): grid size %d, triangles %d, %s\n",
- pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
+ pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses ? "lerp" : "binary"),
grid_size, int(triangles),
(triangles <= target_index_count / 3) ? "under" : "over");
#endif
- float tip = interpolate(float(target_index_count / 3), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles));
+ float tip = interpolate(float(size_t(target_index_count / 3)), float(min_grid), float(min_triangles), float(grid_size), float(triangles), float(max_grid), float(max_triangles));
if (triangles <= target_index_count / 3)
{
@@ -1829,14 +2245,14 @@ size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_pos
// we clamp the prediction of the grid size to make sure that the search converges
int grid_size = next_grid_size;
- grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid) ? max_grid - 1 : grid_size;
+ grid_size = (grid_size <= min_grid) ? min_grid + 1 : (grid_size >= max_grid ? max_grid - 1 : grid_size);
computeVertexIds(vertex_ids, vertex_positions, vertex_count, grid_size);
size_t vertices = countVertexCells(table, table_size, vertex_ids, vertex_count);
#if TRACE
printf("pass %d (%s): grid size %d, vertices %d, %s\n",
- pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses) ? "lerp" : "binary",
+ pass, (pass == 0) ? "guess" : (pass <= kInterpolationPasses ? "lerp" : "binary"),
grid_size, int(vertices),
(vertices <= target_vertex_count) ? "under" : "over");
#endif
@@ -1881,7 +2297,10 @@ size_t meshopt_simplifyPoints(unsigned int* destination, const float* vertex_pos
unsigned int* cell_remap = allocator.allocate<unsigned int>(cell_count);
float* cell_errors = allocator.allocate<float>(cell_count);
- fillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_reservoirs, vertex_positions, vertex_colors, vertex_colors_stride, color_weight * color_weight, vertex_count);
+ // we scale the color weight to bring it to the same scale as position so that error addition makes sense
+ float color_weight_scaled = color_weight * (min_grid == 1 ? 1.f : 1.f / (min_grid - 1));
+
+ fillCellRemap(cell_remap, cell_errors, cell_count, vertex_cells, cell_reservoirs, vertex_positions, vertex_colors, vertex_colors_stride, color_weight_scaled * color_weight_scaled, vertex_count);
// copy results to the output
assert(cell_count <= target_vertex_count);
diff --git a/thirdparty/meshoptimizer/stripifier.cpp b/thirdparty/meshoptimizer/stripifier.cpp
index 8ce17ef3dc..4043195aeb 100644
--- a/thirdparty/meshoptimizer/stripifier.cpp
+++ b/thirdparty/meshoptimizer/stripifier.cpp
@@ -10,15 +10,15 @@
namespace meshopt
{
-static unsigned int findStripFirst(const unsigned int buffer[][3], unsigned int buffer_size, const unsigned int* valence)
+static unsigned int findStripFirst(const unsigned int buffer[][3], unsigned int buffer_size, const unsigned char* valence)
{
unsigned int index = 0;
unsigned int iv = ~0u;
for (size_t i = 0; i < buffer_size; ++i)
{
- unsigned int va = valence[buffer[i][0]], vb = valence[buffer[i][1]], vc = valence[buffer[i][2]];
- unsigned int v = (va < vb && va < vc) ? va : (vb < vc) ? vb : vc;
+ unsigned char va = valence[buffer[i][0]], vb = valence[buffer[i][1]], vc = valence[buffer[i][2]];
+ unsigned int v = (va < vb && va < vc) ? va : (vb < vc ? vb : vc);
if (v < iv)
{
@@ -71,8 +71,9 @@ size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices,
size_t strip_size = 0;
// compute vertex valence; this is used to prioritize starting triangle for strips
- unsigned int* valence = allocator.allocate<unsigned int>(vertex_count);
- memset(valence, 0, vertex_count * sizeof(unsigned int));
+ // note: we use 8-bit counters for performance; for outlier vertices the valence is incorrect but that just affects the heuristic
+ unsigned char* valence = allocator.allocate<unsigned char>(vertex_count);
+ memset(valence, 0, vertex_count);
for (size_t i = 0; i < index_count; ++i)
{
@@ -151,7 +152,7 @@ size_t meshopt_stripify(unsigned int* destination, const unsigned int* indices,
{
// if we didn't find anything, we need to find the next new triangle
// we use a heuristic to maximize the strip length
- unsigned int i = findStripFirst(buffer, buffer_size, &valence[0]);
+ unsigned int i = findStripFirst(buffer, buffer_size, valence);
unsigned int a = buffer[i][0], b = buffer[i][1], c = buffer[i][2];
// ordered removal from the buffer
diff --git a/thirdparty/meshoptimizer/vcacheoptimizer.cpp b/thirdparty/meshoptimizer/vcacheoptimizer.cpp
index d4b08ba340..e4ecc71d36 100644
--- a/thirdparty/meshoptimizer/vcacheoptimizer.cpp
+++ b/thirdparty/meshoptimizer/vcacheoptimizer.cpp
@@ -195,9 +195,8 @@ void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned
TriangleAdjacency adjacency = {};
buildTriangleAdjacency(adjacency, indices, index_count, vertex_count, allocator);
- // live triangle counts
- unsigned int* live_triangles = allocator.allocate<unsigned int>(vertex_count);
- memcpy(live_triangles, adjacency.counts, vertex_count * sizeof(unsigned int));
+ // live triangle counts; note, we alias adjacency.counts as we remove triangles after emitting them so the counts always match
+ unsigned int* live_triangles = adjacency.counts;
// emitted flags
unsigned char* emitted_flags = allocator.allocate<unsigned char>(face_count);
@@ -261,20 +260,16 @@ void meshopt_optimizeVertexCacheTable(unsigned int* destination, const unsigned
unsigned int index = cache[i];
cache_new[cache_write] = index;
- cache_write += (index != a && index != b && index != c);
+ cache_write += (index != a) & (index != b) & (index != c);
}
unsigned int* cache_temp = cache;
cache = cache_new, cache_new = cache_temp;
cache_count = cache_write > cache_size ? cache_size : cache_write;
- // update live triangle counts
- live_triangles[a]--;
- live_triangles[b]--;
- live_triangles[c]--;
-
// remove emitted triangle from adjacency data
// this makes sure that we spend less time traversing these lists on subsequent iterations
+ // live triangle counts are updated as a byproduct of these adjustments
for (size_t k = 0; k < 3; ++k)
{
unsigned int index = indices[current_triangle * 3 + k];
diff --git a/thirdparty/meshoptimizer/vertexcodec.cpp b/thirdparty/meshoptimizer/vertexcodec.cpp
index 8ab0662d88..1dbd2e35f8 100644
--- a/thirdparty/meshoptimizer/vertexcodec.cpp
+++ b/thirdparty/meshoptimizer/vertexcodec.cpp
@@ -90,6 +90,14 @@
#include <wasm_simd128.h>
#endif
+#ifndef TRACE
+#define TRACE 0
+#endif
+
+#if TRACE
+#include <stdio.h>
+#endif
+
#ifdef SIMD_WASM
#define wasmx_splat_v32x4(v, i) wasm_i32x4_shuffle(v, v, i, i, i, i)
#define wasmx_unpacklo_v8x16(a, b) wasm_i8x16_shuffle(a, b, 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23)
@@ -135,6 +143,19 @@ inline unsigned char unzigzag8(unsigned char v)
return -(v & 1) ^ (v >> 1);
}
+#if TRACE
+struct Stats
+{
+ size_t size;
+ size_t header; // bytes for header
+ size_t bitg[4]; // bytes for bit groups
+ size_t bitc[8]; // bit consistency: how many bits are shared between all bytes in a group
+};
+
+static Stats* bytestats = NULL;
+static Stats vertexstats[256];
+#endif
+
static bool encodeBytesGroupZero(const unsigned char* buffer)
{
for (size_t i = 0; i < kByteGroupSize; ++i)
@@ -245,7 +266,7 @@ static unsigned char* encodeBytes(unsigned char* data, unsigned char* data_end,
}
}
- int bitslog2 = (best_bits == 1) ? 0 : (best_bits == 2) ? 1 : (best_bits == 4) ? 2 : 3;
+ int bitslog2 = (best_bits == 1) ? 0 : (best_bits == 2 ? 1 : (best_bits == 4 ? 2 : 3));
assert((1 << bitslog2) == best_bits);
size_t header_offset = i / kByteGroupSize;
@@ -256,8 +277,16 @@ static unsigned char* encodeBytes(unsigned char* data, unsigned char* data_end,
assert(data + best_size == next);
data = next;
+
+#if TRACE
+ bytestats->bitg[bitslog2] += best_size;
+#endif
}
+#if TRACE
+ bytestats->header += header_size;
+#endif
+
return data;
}
@@ -286,9 +315,31 @@ static unsigned char* encodeVertexBlock(unsigned char* data, unsigned char* data
vertex_offset += vertex_size;
}
+#if TRACE
+ const unsigned char* olddata = data;
+ bytestats = &vertexstats[k];
+
+ for (size_t ig = 0; ig < vertex_count; ig += kByteGroupSize)
+ {
+ unsigned char last = (ig == 0) ? last_vertex[k] : vertex_data[vertex_size * (ig - 1) + k];
+ unsigned char delta = 0xff;
+
+ for (size_t i = ig; i < ig + kByteGroupSize && i < vertex_count; ++i)
+ delta &= ~(vertex_data[vertex_size * i + k] ^ last);
+
+ for (int j = 0; j < 8; ++j)
+ bytestats->bitc[j] += (vertex_count - ig < kByteGroupSize ? vertex_count - ig : kByteGroupSize) * ((delta >> j) & 1);
+ }
+#endif
+
data = encodeBytes(data, data_end, buffer, (vertex_count + kByteGroupSize - 1) & ~(kByteGroupSize - 1));
if (!data)
return NULL;
+
+#if TRACE
+ bytestats = NULL;
+ vertexstats[k].size += data - olddata;
+#endif
}
memcpy(last_vertex, &vertex_data[vertex_size * (vertex_count - 1)], vertex_size);
@@ -383,6 +434,7 @@ static const unsigned char* decodeVertexBlock(const unsigned char* data, const u
unsigned char transposed[kVertexBlockSizeBytes];
size_t vertex_count_aligned = (vertex_count + kByteGroupSize - 1) & ~(kByteGroupSize - 1);
+ assert(vertex_count <= vertex_count_aligned);
for (size_t k = 0; k < vertex_size; ++k)
{
@@ -1095,6 +1147,10 @@ size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, con
assert(vertex_size > 0 && vertex_size <= 256);
assert(vertex_size % 4 == 0);
+#if TRACE
+ memset(vertexstats, 0, sizeof(vertexstats));
+#endif
+
const unsigned char* vertex_data = static_cast<const unsigned char*>(vertices);
unsigned char* data = buffer;
@@ -1147,6 +1203,30 @@ size_t meshopt_encodeVertexBuffer(unsigned char* buffer, size_t buffer_size, con
assert(data >= buffer + tail_size);
assert(data <= buffer + buffer_size);
+#if TRACE
+ size_t total_size = data - buffer;
+
+ for (size_t k = 0; k < vertex_size; ++k)
+ {
+ const Stats& vsk = vertexstats[k];
+
+ printf("%2d: %7d bytes [%4.1f%%] %.1f bpv", int(k), int(vsk.size), double(vsk.size) / double(total_size) * 100, double(vsk.size) / double(vertex_count) * 8);
+
+ size_t total_k = vsk.header + vsk.bitg[0] + vsk.bitg[1] + vsk.bitg[2] + vsk.bitg[3];
+
+ printf(" |\thdr [%5.1f%%] bitg 1-3 [%4.1f%% %4.1f%% %4.1f%%]",
+ double(vsk.header) / double(total_k) * 100, double(vsk.bitg[1]) / double(total_k) * 100,
+ double(vsk.bitg[2]) / double(total_k) * 100, double(vsk.bitg[3]) / double(total_k) * 100);
+
+ printf(" |\tbitc [%3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%% %3.0f%%]",
+ double(vsk.bitc[0]) / double(vertex_count) * 100, double(vsk.bitc[1]) / double(vertex_count) * 100,
+ double(vsk.bitc[2]) / double(vertex_count) * 100, double(vsk.bitc[3]) / double(vertex_count) * 100,
+ double(vsk.bitc[4]) / double(vertex_count) * 100, double(vsk.bitc[5]) / double(vertex_count) * 100,
+ double(vsk.bitc[6]) / double(vertex_count) * 100, double(vsk.bitc[7]) / double(vertex_count) * 100);
+ printf("\n");
+ }
+#endif
+
return data - buffer;
}
@@ -1246,3 +1326,4 @@ int meshopt_decodeVertexBuffer(void* destination, size_t vertex_count, size_t ve
#undef SIMD_WASM
#undef SIMD_FALLBACK
#undef SIMD_TARGET
+#undef SIMD_LATENCYOPT
diff --git a/thirdparty/meshoptimizer/vertexfilter.cpp b/thirdparty/meshoptimizer/vertexfilter.cpp
index 4b5f444f04..1f456bb9de 100644
--- a/thirdparty/meshoptimizer/vertexfilter.cpp
+++ b/thirdparty/meshoptimizer/vertexfilter.cpp
@@ -807,7 +807,7 @@ inline int optlog2(float v)
u.f = v;
// +1 accounts for implicit 1. in mantissa; denormalized numbers will end up clamped to min_exp by calling code
- return u.ui == 0 ? 0 : int((u.ui >> 23) & 0xff) - 127 + 1;
+ return v == 0 ? 0 : int((u.ui >> 23) & 0xff) - 127 + 1;
}
// optimized variant of ldexp
@@ -1010,6 +1010,20 @@ void meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, in
component_exp[j] = (min_exp < e) ? e : min_exp;
}
}
+ else if (mode == meshopt_EncodeExpClamped)
+ {
+ for (size_t j = 0; j < stride_float; ++j)
+ {
+ int e = optlog2(v[j]);
+
+ component_exp[j] = (0 < e) ? e : 0;
+ }
+ }
+ else
+ {
+ // the code below assumes component_exp is initialized outside of the loop
+ assert(mode == meshopt_EncodeExpSharedComponent);
+ }
for (size_t j = 0; j < stride_float; ++j)
{
@@ -1020,7 +1034,6 @@ void meshopt_encodeFilterExp(void* destination_, size_t count, size_t stride, in
// compute renormalized rounded mantissa for each component
int mmask = (1 << 24) - 1;
-
int m = int(v[j] * optexp2(-exp) + (v[j] >= 0 ? 0.5f : -0.5f));
d[j] = (m & mmask) | (unsigned(exp) << 24);