summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/godot_cpp_test.yml10
-rw-r--r--.github/workflows/windows_builds.yml11
-rw-r--r--SConstruct15
-rw-r--r--core/SCsub2
-rw-r--r--core/debugger/remote_debugger_peer.cpp3
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_interface.h2
-rw-r--r--core/extension/gdextension_manager.cpp93
-rw-r--r--core/extension/gdextension_manager.h3
-rw-r--r--core/io/file_access.cpp111
-rw-r--r--core/io/file_access.h8
-rw-r--r--core/io/file_access_compressed.cpp39
-rw-r--r--core/io/file_access_compressed.h3
-rw-r--r--core/io/file_access_encrypted.cpp46
-rw-r--r--core/io/file_access_encrypted.h2
-rw-r--r--core/io/file_access_memory.cpp18
-rw-r--r--core/io/file_access_memory.h3
-rw-r--r--core/io/file_access_pack.cpp15
-rw-r--r--core/io/file_access_pack.h4
-rw-r--r--core/io/file_access_zip.cpp8
-rw-r--r--core/io/file_access_zip.h3
-rw-r--r--core/io/image.cpp2
-rw-r--r--core/io/json.cpp748
-rw-r--r--core/io/json.h3
-rw-r--r--core/io/packed_data_container.h2
-rw-r--r--core/io/plist.cpp4
-rw-r--r--core/io/resource.cpp99
-rw-r--r--core/io/resource_importer.cpp23
-rw-r--r--core/io/resource_importer.h7
-rw-r--r--core/io/resource_loader.cpp64
-rw-r--r--core/math/math_funcs.h3
-rw-r--r--core/object/class_db.cpp26
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/object/object.cpp27
-rw-r--r--core/object/object.h7
-rw-r--r--core/object/worker_thread_pool.cpp39
-rw-r--r--core/os/os.h6
-rw-r--r--core/string/string_name.cpp3
-rw-r--r--core/string/translation_server.cpp5
-rw-r--r--core/variant/callable.cpp2
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/AnimationMixer.xml14
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml2
-rw-r--r--doc/classes/AudioStreamPlayer3D.xml2
-rw-r--r--doc/classes/AudioStreamWAV.xml19
-rw-r--r--doc/classes/CPUParticles3D.xml4
-rw-r--r--doc/classes/EditorContextMenuPlugin.xml49
-rw-r--r--doc/classes/EditorExportPlatform.xml206
-rw-r--r--doc/classes/EditorExportPlatformExtension.xml282
-rw-r--r--doc/classes/EditorExportPlugin.xml12
-rw-r--r--doc/classes/EditorExportPreset.xml186
-rw-r--r--doc/classes/EditorInterface.xml12
-rw-r--r--doc/classes/EditorPlugin.xml43
-rw-r--r--doc/classes/EditorResourcePreviewGenerator.xml4
-rw-r--r--doc/classes/EditorUndoRedoManager.xml15
-rw-r--r--doc/classes/GDExtensionManager.xml14
-rw-r--r--doc/classes/JSON.xml20
-rw-r--r--doc/classes/JavaClass.xml26
-rw-r--r--doc/classes/JavaClassWrapper.xml9
-rw-r--r--doc/classes/JavaObject.xml21
-rw-r--r--doc/classes/ParticleProcessMaterial.xml4
-rw-r--r--doc/classes/ProjectSettings.xml4
-rw-r--r--doc/classes/PropertyTweener.xml1
-rw-r--r--doc/classes/RenderingServer.xml8
-rw-r--r--doc/classes/ResourceImporterWAV.xml22
-rw-r--r--doc/classes/ScriptEditor.xml6
-rw-r--r--doc/classes/SpriteFrames.xml8
-rw-r--r--doc/classes/TileData.xml55
-rw-r--r--doc/classes/Tweener.xml2
-rw-r--r--doc/classes/VehicleWheel3D.xml12
-rw-r--r--drivers/gles3/shaders/stdlib_inc.glsl18
-rw-r--r--drivers/gles3/storage/config.cpp2
-rw-r--r--drivers/gles3/storage/light_storage.h2
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp1
-rw-r--r--drivers/gles3/storage/texture_storage.cpp22
-rw-r--r--drivers/metal/metal_objects.h1
-rw-r--r--drivers/metal/metal_objects.mm8
-rw-r--r--drivers/metal/rendering_context_driver_metal.h145
-rw-r--r--drivers/metal/rendering_context_driver_metal.mm99
-rw-r--r--drivers/unix/file_access_unix.cpp93
-rw-r--r--drivers/unix/file_access_unix.h8
-rw-r--r--drivers/unix/file_access_unix_pipe.cpp25
-rw-r--r--drivers/unix/file_access_unix_pipe.h2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp11
-rw-r--r--drivers/windows/dir_access_windows.cpp6
-rw-r--r--drivers/windows/file_access_windows.cpp165
-rw-r--r--drivers/windows/file_access_windows.h8
-rw-r--r--drivers/windows/file_access_windows_pipe.cpp24
-rw-r--r--drivers/windows/file_access_windows_pipe.h2
-rw-r--r--editor/animation_bezier_editor.cpp8
-rw-r--r--editor/animation_track_editor.cpp244
-rw-r--r--editor/animation_track_editor.h11
-rw-r--r--editor/debugger/editor_debugger_node.cpp2
-rw-r--r--editor/debugger/editor_debugger_tree.cpp16
-rw-r--r--editor/debugger/editor_debugger_tree.h1
-rw-r--r--editor/editor_audio_buses.cpp8
-rw-r--r--editor/editor_data.cpp135
-rw-r--r--editor/editor_data.h30
-rw-r--r--editor/editor_file_system.cpp223
-rw-r--r--editor/editor_file_system.h7
-rw-r--r--editor/editor_help_search.cpp549
-rw-r--r--editor/editor_help_search.h44
-rw-r--r--editor/editor_interface.compat.inc48
-rw-r--r--editor/editor_interface.cpp18
-rw-r--r--editor/editor_interface.h13
-rw-r--r--editor/editor_node.cpp56
-rw-r--r--editor/editor_properties_vector.cpp8
-rw-r--r--editor/editor_resource_picker.cpp18
-rw-r--r--editor/editor_resource_picker.h2
-rw-r--r--editor/editor_resource_preview.cpp4
-rw-r--r--editor/editor_run_native.cpp10
-rw-r--r--editor/editor_undo_redo_manager.cpp3
-rw-r--r--editor/editor_undo_redo_manager.h2
-rw-r--r--editor/export/editor_export.cpp18
-rw-r--r--editor/export/editor_export.h2
-rw-r--r--editor/export/editor_export_platform.cpp264
-rw-r--r--editor/export/editor_export_platform.h105
-rw-r--r--editor/export/editor_export_platform_extension.cpp317
-rw-r--r--editor/export/editor_export_platform_extension.h149
-rw-r--r--editor/export/editor_export_platform_pc.cpp6
-rw-r--r--editor/export/editor_export_platform_pc.h8
-rw-r--r--editor/export/editor_export_plugin.cpp13
-rw-r--r--editor/export/editor_export_plugin.h1
-rw-r--r--editor/export/editor_export_preset.cpp42
-rw-r--r--editor/export/editor_export_preset.h7
-rw-r--r--editor/export/export_template_manager.cpp106
-rw-r--r--editor/export/export_template_manager.h2
-rw-r--r--editor/filesystem_dock.cpp12
-rw-r--r--editor/icons/MemberConstructor.svg1
-rw-r--r--editor/icons/MemberOperator.svg1
-rw-r--r--editor/icons/SnapKeys.svg1
-rw-r--r--editor/icons/SnapTimeline.svg1
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp2
-rw-r--r--editor/import/3d/resource_importer_scene.cpp25
-rw-r--r--editor/import/dynamic_font_import_settings.cpp16
-rw-r--r--editor/import/resource_importer_layered_texture.cpp2
-rw-r--r--editor/import/resource_importer_wav.cpp3
-rw-r--r--editor/import_dock.cpp2
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp155
-rw-r--r--editor/plugins/animation_player_editor_plugin.h2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp18
-rw-r--r--editor/plugins/editor_context_menu_plugin.cpp65
-rw-r--r--editor/plugins/editor_context_menu_plugin.h70
-rw-r--r--editor/plugins/editor_plugin.cpp31
-rw-r--r--editor/plugins/editor_plugin.h16
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp12
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.h2
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp4
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp11
-rw-r--r--editor/plugins/path_2d_editor_plugin.cpp26
-rw-r--r--editor/plugins/path_2d_editor_plugin.h1
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp134
-rw-r--r--editor/plugins/script_editor_plugin.h1
-rw-r--r--editor/plugins/script_text_editor.cpp29
-rw-r--r--editor/plugins/shader_editor_plugin.cpp2
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp182
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h8
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp44
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h2
-rw-r--r--editor/plugins/text_editor.cpp14
-rw-r--r--editor/plugins/texture_editor_plugin.cpp2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp19
-rw-r--r--editor/plugins/theme_editor_plugin.h1
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp42
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp6
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp77
-rw-r--r--editor/property_selector.cpp17
-rw-r--r--editor/register_editor_types.cpp5
-rw-r--r--editor/scene_tree_dock.cpp34
-rw-r--r--editor/themes/editor_theme_manager.cpp6
-rw-r--r--main/main.cpp12
-rw-r--r--methods.py27
-rw-r--r--misc/extension_api_validation/4.3-stable.expected17
-rw-r--r--modules/betsy/bc6h.glsl96
-rw-r--r--modules/betsy/image_compress_betsy.cpp13
-rw-r--r--modules/fbx/fbx_document.cpp9
-rw-r--r--modules/gdscript/gdscript.cpp66
-rw-r--r--modules/gdscript/gdscript.h7
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp4
-rw-r--r--modules/gdscript/gdscript_parser.cpp5
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd25
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd193
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd159
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.gd40
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.out65
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd18
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml7
-rw-r--r--modules/gltf/extensions/gltf_document_extension.cpp50
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp2
-rw-r--r--modules/gltf/extensions/physics/gltf_physics_shape.cpp2
-rw-r--r--modules/gltf/gltf_document.cpp109
-rw-r--r--modules/gltf/gltf_document.h2
-rw-r--r--modules/gltf/tests/test_gltf_extras.h165
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp13
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp32
-rw-r--r--modules/minimp3/audio_stream_mp3.h2
-rw-r--r--modules/mono/csharp_script.cpp2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs60
-rw-r--r--modules/mono/editor/hostfxr_resolver.cpp2
-rw-r--r--modules/mono/godotsharp_dirs.cpp2
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp4
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp79
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.cpp79
-rw-r--r--modules/navigation/3d/nav_mesh_queries_3d.cpp715
-rw-r--r--modules/navigation/3d/nav_mesh_queries_3d.h54
-rw-r--r--modules/navigation/nav_map.cpp617
-rw-r--r--modules/navigation/nav_map.h14
-rw-r--r--modules/navigation/nav_region.cpp78
-rw-r--r--modules/navigation/nav_utils.h172
-rw-r--r--modules/noise/noise_texture_2d.cpp3
-rw-r--r--modules/noise/noise_texture_3d.cpp3
-rw-r--r--modules/noise/tests/test_noise_texture_2d.h4
-rw-r--r--modules/noise/tests/test_noise_texture_3d.h4
-rw-r--r--modules/regex/tests/test_regex.h50
-rw-r--r--modules/text_server_adv/text_server_adv.cpp720
-rw-r--r--modules/text_server_adv/text_server_adv.h13
-rw-r--r--modules/text_server_fb/text_server_fb.cpp656
-rw-r--r--modules/text_server_fb/text_server_fb.h13
-rw-r--r--modules/upnp/upnp.cpp12
-rw-r--r--modules/zip/zip_packer.cpp2
-rw-r--r--modules/zip/zip_reader.cpp2
-rw-r--r--platform/android/api/api.cpp30
-rw-r--r--platform/android/api/java_class_wrapper.h49
-rw-r--r--platform/android/api/jni_singleton.h5
-rw-r--r--platform/android/display_server_android.cpp8
-rw-r--r--platform/android/export/export_plugin.cpp26
-rw-r--r--platform/android/export/export_plugin.h8
-rw-r--r--platform/android/file_access_android.cpp96
-rw-r--r--platform/android/file_access_android.h9
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp62
-rw-r--r--platform/android/file_access_filesystem_jandroid.h8
-rw-r--r--platform/android/java/editor/build.gradle2
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt4
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt28
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt114
-rw-r--r--platform/android/java_class_wrapper.cpp295
-rw-r--r--platform/android/java_godot_lib_jni.cpp11
-rw-r--r--platform/android/java_godot_wrapper.cpp20
-rw-r--r--platform/android/java_godot_wrapper.h5
-rw-r--r--platform/android/jni_utils.cpp22
-rw-r--r--platform/android/jni_utils.h2
-rw-r--r--platform/ios/export/export_plugin.cpp16
-rw-r--r--platform/ios/export/export_plugin.h6
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp9
-rw-r--r--platform/linuxbsd/export/export_plugin.h4
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp10
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp1
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp1
-rw-r--r--platform/macos/display_server_macos.mm1
-rw-r--r--platform/macos/export/export_plugin.cpp9
-rw-r--r--platform/macos/export/export_plugin.h4
-rw-r--r--platform/web/export/export_plugin.cpp9
-rw-r--r--platform/web/export/export_plugin.h6
-rw-r--r--platform/windows/crash_handler_windows_seh.cpp2
-rw-r--r--platform/windows/detect.py93
-rw-r--r--platform/windows/display_server_windows.cpp3
-rw-r--r--platform/windows/export/export_plugin.cpp11
-rw-r--r--platform/windows/export/export_plugin.h6
-rw-r--r--platform/windows/windows_utils.cpp6
-rw-r--r--scene/2d/animated_sprite_2d.cpp4
-rw-r--r--scene/2d/audio_stream_player_2d.cpp9
-rw-r--r--scene/2d/parallax_2d.cpp18
-rw-r--r--scene/2d/parallax_2d.h1
-rw-r--r--scene/2d/tile_map_layer.cpp99
-rw-r--r--scene/2d/tile_map_layer.h2
-rw-r--r--scene/3d/audio_stream_player_3d.cpp9
-rw-r--r--scene/3d/cpu_particles_3d.cpp29
-rw-r--r--scene/3d/cpu_particles_3d.h3
-rw-r--r--scene/3d/mesh_instance_3d.cpp4
-rw-r--r--scene/3d/physical_bone_simulator_3d.cpp49
-rw-r--r--scene/3d/physical_bone_simulator_3d.h1
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp10
-rw-r--r--scene/3d/physics/vehicle_body_3d.h4
-rw-r--r--scene/3d/sprite_3d.cpp6
-rw-r--r--scene/3d/voxelizer.h3
-rw-r--r--scene/animation/animation_mixer.cpp129
-rw-r--r--scene/animation/animation_mixer.h1
-rw-r--r--scene/animation/animation_tree.cpp7
-rw-r--r--scene/animation/animation_tree.h2
-rw-r--r--scene/animation/tween.cpp3
-rw-r--r--scene/audio/audio_stream_player.cpp9
-rw-r--r--scene/gui/rich_text_label.cpp10
-rw-r--r--scene/gui/tree.cpp19
-rw-r--r--scene/main/viewport.cpp3
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp2
-rw-r--r--scene/resources/2d/navigation_polygon.h3
-rw-r--r--scene/resources/2d/tile_set.compat.inc2
-rw-r--r--scene/resources/2d/tile_set.cpp168
-rw-r--r--scene/resources/2d/tile_set.h16
-rw-r--r--scene/resources/3d/fog_material.cpp3
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp2
-rw-r--r--scene/resources/3d/sky_material.cpp11
-rw-r--r--scene/resources/animation.h18
-rw-r--r--scene/resources/audio_stream_wav.cpp25
-rw-r--r--scene/resources/audio_stream_wav.h2
-rw-r--r--scene/resources/material.cpp2
-rw-r--r--scene/resources/mesh.cpp1
-rw-r--r--scene/resources/navigation_mesh.h5
-rw-r--r--scene/resources/particle_process_material.cpp32
-rw-r--r--scene/resources/particle_process_material.h4
-rw-r--r--scene/resources/resource_format_text.cpp15
-rw-r--r--scene/resources/resource_format_text.h12
-rw-r--r--scene/resources/shader.cpp2
-rw-r--r--scene/resources/sprite_frames.cpp7
-rw-r--r--scene/resources/sprite_frames.h1
-rw-r--r--scene/resources/surface_tool.cpp8
-rw-r--r--servers/audio/effects/audio_effect_record.cpp12
-rw-r--r--servers/display_server.cpp12
-rw-r--r--servers/navigation/navigation_globals.h66
-rw-r--r--servers/navigation_server_3d.cpp15
-rw-r--r--servers/rendering/renderer_rd/effects/taa.cpp4
-rw-r--r--servers/rendering/renderer_rd/environment/sky.cpp1
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl4
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl5
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl61
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp20
-rw-r--r--servers/rendering/renderer_scene_cull.cpp2
-rw-r--r--servers/rendering/rendering_device.h4
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h2
-rw-r--r--servers/text_server.cpp4
-rw-r--r--servers/xr_server.cpp4
-rw-r--r--tests/core/io/test_http_client.h2
-rw-r--r--tests/core/io/test_json_native.h160
-rw-r--r--tests/core/object/test_object.h25
-rw-r--r--tests/core/string/test_string.h31
-rw-r--r--tests/scene/test_audio_stream_wav.h2
-rw-r--r--tests/scene/test_path_2d.h2
-rw-r--r--tests/scene/test_path_3d.h2
-rw-r--r--tests/scene/test_primitives.h6
-rw-r--r--tests/servers/test_navigation_server_3d.h160
-rw-r--r--tests/test_main.cpp1
-rw-r--r--thirdparty/README.md4
-rw-r--r--thirdparty/libwebp/patches/godot-clang-cl-fix.patch19
-rw-r--r--thirdparty/libwebp/src/dsp/cpu.h4
-rw-r--r--thirdparty/mbedtls/include/mbedtls/bignum.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/build_info.h30
-rw-r--r--thirdparty/mbedtls/include/mbedtls/check_config.h14
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h29
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h16
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_psa.h6
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ctr_drbg.h33
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecdh.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecp.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/mbedtls_config.h64
-rw-r--r--thirdparty/mbedtls/include/mbedtls/pk.h60
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ssl.h145
-rw-r--r--thirdparty/mbedtls/include/psa/crypto.h188
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h51
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_extra.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_struct.h28
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_types.h26
-rw-r--r--thirdparty/mbedtls/library/bignum.c31
-rw-r--r--thirdparty/mbedtls/library/bignum_core.c163
-rw-r--r--thirdparty/mbedtls/library/bignum_core.h70
-rw-r--r--thirdparty/mbedtls/library/bignum_internal.h50
-rw-r--r--thirdparty/mbedtls/library/block_cipher.c4
-rw-r--r--thirdparty/mbedtls/library/cipher.c3
-rw-r--r--thirdparty/mbedtls/library/common.h16
-rw-r--r--thirdparty/mbedtls/library/ctr_drbg.c128
-rw-r--r--thirdparty/mbedtls/library/entropy.c4
-rw-r--r--thirdparty/mbedtls/library/entropy_poll.c4
-rw-r--r--thirdparty/mbedtls/library/error.c2
-rw-r--r--thirdparty/mbedtls/library/lmots.c8
-rw-r--r--thirdparty/mbedtls/library/lms.c8
-rw-r--r--thirdparty/mbedtls/library/md.c6
-rw-r--r--thirdparty/mbedtls/library/net_sockets.c2
-rw-r--r--thirdparty/mbedtls/library/nist_kw.c4
-rw-r--r--thirdparty/mbedtls/library/pem.c4
-rw-r--r--thirdparty/mbedtls/library/pk.c46
-rw-r--r--thirdparty/mbedtls/library/platform_util.c2
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_core.h128
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h9
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_random_impl.h17
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_rsa.h14
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_slot_management.h79
-rw-r--r--thirdparty/mbedtls/library/rsa.c3
-rw-r--r--thirdparty/mbedtls/library/sha256.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_cookie.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_debug_helpers_generated.c2
-rw-r--r--thirdparty/mbedtls/library/ssl_misc.h96
-rw-r--r--thirdparty/mbedtls/library/ssl_msg.c39
-rw-r--r--thirdparty/mbedtls/library/ssl_ticket.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_tls.c617
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_client.c6
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_server.c25
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_client.c7
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_generic.c169
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_server.c29
-rw-r--r--thirdparty/mbedtls/library/version_features.c3
-rw-r--r--thirdparty/mbedtls/library/x509_crt.c2
-rw-r--r--thirdparty/mbedtls/library/x509write_crt.c4
-rw-r--r--thirdparty/mbedtls/library/x509write_csr.c4
-rw-r--r--thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff20
-rw-r--r--thirdparty/mbedtls/patches/no-flexible-arrays.diff132
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch96
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp8
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h1
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp6
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h12
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp19
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp5
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp16
-rw-r--r--thirdparty/thorvg/src/renderer/tvgLoadModule.h3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgScene.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h19
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh18
461 files changed, 11559 insertions, 5272 deletions
diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml
index e1c37bbc1e..e3223c799b 100644
--- a/.github/workflows/godot_cpp_test.yml
+++ b/.github/workflows/godot_cpp_test.yml
@@ -56,3 +56,13 @@ jobs:
cd godot-cpp/test
scons target=template_debug dev_build=yes
cd ../..
+
+ gdextension-c-compile:
+ runs-on: "ubuntu-20.04"
+ name: "Check GDExtension header with a C compiler"
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: "Run C compiler on gdextension_interface.h"
+ run: |
+ gcc -c core/extension/gdextension_interface.h
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 90629204e6..0c21576517 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -30,6 +30,14 @@ jobs:
# Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: "./bin/godot.windows.editor.x86_64.exe"
+ artifact: true
+
+ - name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes)
+ cache-name: windows-editor-clang
+ target: editor
+ tests: true
+ sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes
+ bin: ./bin/godot.windows.editor.x86_64.llvm.exe
- name: Template (target=template_release)
cache-name: windows-template
@@ -37,6 +45,7 @@ jobs:
tests: true
sconsflags: debug_symbols=no tests=yes
bin: "./bin/godot.windows.template_release.x86_64.console.exe"
+ artifact: true
steps:
- uses: actions/checkout@v4
@@ -84,10 +93,12 @@ jobs:
continue-on-error: true
- name: Prepare artifact
+ if: ${{ matrix.artifact }}
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
- name: Upload artifact
+ if: ${{ matrix.artifact }}
uses: ./.github/actions/upload-artifact
with:
name: ${{ matrix.cache-name }}
diff --git a/SConstruct b/SConstruct
index e1c943bd45..0297cd6e61 100644
--- a/SConstruct
+++ b/SConstruct
@@ -803,7 +803,7 @@ elif env.msvc:
env.Append(CXXFLAGS=["/EHsc"])
# Configure compiler warnings
-if env.msvc: # MSVC
+if env.msvc and not methods.using_clang(env): # MSVC
if env["warnings"] == "no":
env.Append(CCFLAGS=["/w"])
else:
@@ -853,8 +853,11 @@ else: # GCC, Clang
# for putting them in `Set` or `Map`. We don't mind about unreliable ordering.
common_warnings += ["-Wno-ordered-compare-function-pointers"]
+ # clang-cl will interpret `-Wall` as `-Weverything`, workaround with compatibility cast
+ W_ALL = "-Wall" if not env.msvc else "-W3"
+
if env["warnings"] == "extra":
- env.Append(CCFLAGS=["-Wall", "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
env.Append(CXXFLAGS=["-Wctor-dtor-privacy", "-Wnon-virtual-dtor"])
if methods.using_gcc(env):
env.Append(
@@ -876,9 +879,9 @@ else: # GCC, Clang
elif methods.using_clang(env) or methods.using_emcc(env):
env.Append(CCFLAGS=["-Wimplicit-fallthrough"])
elif env["warnings"] == "all":
- env.Append(CCFLAGS=["-Wall"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL] + common_warnings)
elif env["warnings"] == "moderate":
- env.Append(CCFLAGS=["-Wall", "-Wno-unused"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wno-unused"] + common_warnings)
else: # 'no'
env.Append(CCFLAGS=["-w"])
@@ -1032,7 +1035,9 @@ if env["vsproj"]:
if env["compiledb"]:
if env.scons_version < (4, 0, 0):
# Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
- print_error("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
+ print_error(
+ "The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version
+ )
Exit(255)
env.Tool("compilation_db")
diff --git a/core/SCsub b/core/SCsub
index 1bd4eae16c..c8267ae960 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -140,7 +140,7 @@ if env["builtin_zstd"]:
"decompress/zstd_decompress_block.c",
"decompress/zstd_decompress.c",
]
- if env["platform"] in ["android", "ios", "linuxbsd", "macos"]:
+ if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64":
# Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h
thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S")
thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources]
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 21a9014626..9dca47a0b4 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() {
Error err = decode_variant(var, buf, in_pos, &read);
ERR_CONTINUE(read != in_pos || err != OK);
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
- mutex.lock();
+ MutexLock lock(mutex);
in_queue.push_back(var);
- mutex.unlock();
}
}
}
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index c9e609cddc..d4b50facb2 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -675,15 +675,13 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String
}
Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
- ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader.");
+ ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader.");
loader = p_loader;
- String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+ Error err = loader->open_library(p_path);
- Error err = loader->open_library(abs_path);
-
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index a5a0fc906a..0ebe86d0a7 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1299,7 +1299,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object,
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
- Callable::CallError error;
+ Callable::CallError error; // TODO: Check `error`?
memnew_placement(r_return, Variant);
*(Variant *)r_return = o->callp(method, args, p_argument_count, error);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index cac76d39bd..9057e04bf3 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -268,7 +268,7 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance
typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata);
-typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, bool p_notify_postinitialize);
+typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize);
typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index eeae6b1996..01efe0d96e 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -32,14 +32,18 @@
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_library_loader.h"
+#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/object/script_language.h"
-GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) {
+GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
if (level >= 0) { // Already initialized up to some level.
- int32_t minimum_level = p_extension->get_minimum_library_initialization_level();
- if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
- return LOAD_STATUS_NEEDS_RESTART;
+ int32_t minimum_level = 0;
+ if (!p_first_load) {
+ minimum_level = p_extension->get_minimum_library_initialization_level();
+ if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
+ return LOAD_STATUS_NEEDS_RESTART;
+ }
}
// Initialize up to current level.
for (int32_t i = minimum_level; i <= level; i++) {
@@ -51,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons
gdextension_class_icon_paths[kv.key] = kv.value;
}
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is loaded so GDScript can register new class names.
+ emit_signal("extension_loaded", p_extension);
+#endif
+
return LOAD_STATUS_OK;
}
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is unloading so GDScript can unregister class names.
+ emit_signal("extension_unloading", p_extension);
+#endif
+
if (level >= 0) { // Already initialized up to some level.
// Deinitialize down from current level.
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
@@ -89,7 +103,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(co
return LOAD_STATUS_FAILED;
}
- LoadStatus status = _load_extension_internal(extension);
+ LoadStatus status = _load_extension_internal(extension, true);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -135,7 +149,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
return LOAD_STATUS_FAILED;
}
- status = _load_extension_internal(extension);
+ status = _load_extension_internal(extension, false);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -274,6 +288,71 @@ void GDExtensionManager::reload_extensions() {
#endif
}
+bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
+ Vector<String> extensions_added;
+ Vector<String> extensions_removed;
+
+ for (const String &E : p_extensions) {
+ if (!is_extension_loaded(E)) {
+ extensions_added.push_back(E);
+ }
+ }
+
+ Vector<String> loaded_extensions = get_loaded_extensions();
+ for (const String &loaded_extension : loaded_extensions) {
+ if (!p_extensions.has(loaded_extension)) {
+ // The extension may not have a .gdextension file.
+ if (!FileAccess::exists(loaded_extension)) {
+ extensions_removed.push_back(loaded_extension);
+ }
+ }
+ }
+
+ String extension_list_config_file = GDExtension::get_extension_list_config_file();
+ if (p_extensions.size()) {
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Extensions were added or removed.
+ Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
+ for (const String &E : p_extensions) {
+ f->store_line(E);
+ }
+ }
+ } else {
+ if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
+ // Extensions were removed.
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ da->remove(extension_list_config_file);
+ }
+ }
+
+ bool needs_restart = false;
+ for (const String &extension : extensions_added) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+ for (const String &extension : extensions_removed) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+#ifdef TOOLS_ENABLED
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
+ emit_signal("extensions_reloaded");
+
+ // Reload all scripts to clear out old references.
+ callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
+ }
+#endif
+
+ return needs_restart;
+}
+
GDExtensionManager *GDExtensionManager::get_singleton() {
return singleton;
}
@@ -294,6 +373,8 @@ void GDExtensionManager::_bind_methods() {
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
+ ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
+ ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
}
GDExtensionManager *GDExtensionManager::singleton = nullptr;
diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h
index b488189604..39a600474c 100644
--- a/core/extension/gdextension_manager.h
+++ b/core/extension/gdextension_manager.h
@@ -54,7 +54,7 @@ public:
};
private:
- LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension);
+ LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
#ifdef TOOLS_ENABLED
@@ -85,6 +85,7 @@ public:
void load_extensions();
void reload_extensions();
+ bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
GDExtensionManager();
~GDExtensionManager();
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index c857d54925..d919243e6b 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -223,59 +223,44 @@ String FileAccess::fix_path(const String &p_path) const {
}
/* these are all implemented for ease of porting, then can later be optimized */
+uint8_t FileAccess::get_8() const {
+ uint8_t data = 0;
+ get_buffer(&data, sizeof(uint8_t));
-uint16_t FileAccess::get_16() const {
- uint16_t res;
- uint8_t a, b;
+ return data;
+}
- a = get_8();
- b = get_8();
+uint16_t FileAccess::get_16() const {
+ uint16_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP16(data);
}
- res = b;
- res <<= 8;
- res |= a;
-
- return res;
+ return data;
}
uint32_t FileAccess::get_32() const {
- uint32_t res;
- uint16_t a, b;
-
- a = get_16();
- b = get_16();
+ uint32_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP32(data);
}
- res = b;
- res <<= 16;
- res |= a;
-
- return res;
+ return data;
}
uint64_t FileAccess::get_64() const {
- uint64_t res;
- uint32_t a, b;
-
- a = get_32();
- b = get_32();
+ uint64_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP64(data);
}
- res = b;
- res <<= 32;
- res |= a;
-
- return res;
+ return data;
}
float FileAccess::get_float() const {
@@ -465,17 +450,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const {
return text;
}
-uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
-
- uint64_t i = 0;
- for (i = 0; i < p_length && !eof_reached(); i++) {
- p_dst[i] = get_8();
- }
-
- return i;
-}
-
Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
Vector<uint8_t> data;
@@ -488,7 +462,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
uint8_t *w = data.ptrw();
- int64_t len = get_buffer(&w[0], p_length);
+ int64_t len = get_buffer(w, p_length);
if (len < p_length) {
data.resize(len);
@@ -512,46 +486,32 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const {
return s;
}
-void FileAccess::store_16(uint16_t p_dest) {
- uint8_t a, b;
-
- a = p_dest & 0xFF;
- b = p_dest >> 8;
+void FileAccess::store_8(uint8_t p_dest) {
+ store_buffer(&p_dest, sizeof(uint8_t));
+}
+void FileAccess::store_16(uint16_t p_dest) {
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP16(p_dest);
}
- store_8(a);
- store_8(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t));
}
void FileAccess::store_32(uint32_t p_dest) {
- uint16_t a, b;
-
- a = p_dest & 0xFFFF;
- b = p_dest >> 16;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP32(p_dest);
}
- store_16(a);
- store_16(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t));
}
void FileAccess::store_64(uint64_t p_dest) {
- uint32_t a, b;
-
- a = p_dest & 0xFFFFFFFF;
- b = p_dest >> 32;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP64(p_dest);
}
- store_32(a);
- store_32(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t));
}
void FileAccess::store_real(real_t p_real) {
@@ -708,22 +668,11 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_
store_line(line);
}
-void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) {
- ERR_FAIL_COND(!p_src && p_length > 0);
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
-}
-
void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
uint64_t len = p_buffer.size();
- if (len == 0) {
- return;
- }
-
const uint8_t *r = p_buffer.ptr();
- store_buffer(&r[0], len);
+ store_buffer(r, len);
}
void FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 2ab84db4b6..2f4d1a8604 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -137,7 +137,7 @@ public:
virtual bool eof_reached() const = 0; ///< reading passed EOF
- virtual uint8_t get_8() const = 0; ///< get a byte
+ virtual uint8_t get_8() const; ///< get a byte
virtual uint16_t get_16() const; ///< get 16 bits uint
virtual uint32_t get_32() const; ///< get 32 bits uint
virtual uint64_t get_64() const; ///< get 64 bits uint
@@ -148,7 +148,7 @@ public:
Variant get_var(bool p_allow_objects = false) const;
- virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes
+ virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children.
Vector<uint8_t> get_buffer(int64_t p_length) const;
virtual String get_line() const;
virtual String get_token() const;
@@ -168,7 +168,7 @@ public:
virtual Error resize(int64_t p_length) = 0;
virtual void flush() = 0;
- virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
+ virtual void store_8(uint8_t p_dest); ///< store a byte
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
virtual void store_32(uint32_t p_dest); ///< store 32 bits uint
virtual void store_64(uint64_t p_dest); ///< store 64 bits uint
@@ -184,7 +184,7 @@ public:
virtual void store_pascal_string(const String &p_string);
virtual String get_pascal_string();
- virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children.
void store_buffer(const Vector<uint8_t> &p_buffer);
void store_var(const Variant &p_var, bool p_full_objects = false);
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 0f00bd292c..3602baf8c5 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -260,38 +260,6 @@ bool FileAccessCompressed::eof_reached() const {
}
}
-uint8_t FileAccessCompressed::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
-
- if (at_end) {
- read_eof = true;
- return 0;
- }
-
- uint8_t ret = read_ptr[read_pos];
-
- read_pos++;
- if (read_pos >= read_block_size) {
- read_block++;
-
- if (read_block < read_block_count) {
- //read another block of compressed data
- f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
- int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
- ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt.");
- read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
- read_pos = 0;
-
- } else {
- read_block--;
- at_end = true;
- }
- }
-
- return ret;
-}
-
uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
@@ -341,12 +309,13 @@ void FileAccessCompressed::flush() {
// compressed files keep data in memory till close()
}
-void FileAccessCompressed::store_8(uint8_t p_dest) {
+void FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
- WRITE_FIT(1);
- write_ptr[write_pos++] = p_dest;
+ WRITE_FIT(p_length);
+ memcpy(write_ptr + write_pos, p_src, p_length);
+ write_pos += p_length;
}
bool FileAccessCompressed::file_exists(const String &p_name) {
diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h
index f706c82f8e..ea9837dd03 100644
--- a/core/io/file_access_compressed.h
+++ b/core/io/file_access_compressed.h
@@ -83,14 +83,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index b689f5b628..13d1e0c8fc 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -37,7 +37,7 @@
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
- ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
+ ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
@@ -162,7 +162,7 @@ void FileAccessEncrypted::_close() {
}
bool FileAccessEncrypted::is_open() const {
- return file != nullptr;
+ return file.is_valid();
}
String FileAccessEncrypted::get_path() const {
@@ -206,26 +206,13 @@ bool FileAccessEncrypted::eof_reached() const {
return eofed;
}
-uint8_t FileAccessEncrypted::get_8() const {
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
- if (pos >= get_length()) {
- eofed = true;
- return 0;
- }
-
- uint8_t b = data[pos];
- pos++;
- return b;
-}
-
uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
uint64_t to_copy = MIN(p_length, get_length() - pos);
- for (uint64_t i = 0; i < to_copy; i++) {
- p_dst[i] = data[pos++];
- }
+ memcpy(p_dst, data.ptr() + pos, to_copy);
+ pos += to_copy;
if (to_copy < p_length) {
eofed = true;
@@ -242,17 +229,12 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length)
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
ERR_FAIL_COND(!p_src && p_length > 0);
- if (pos < get_length()) {
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
- } else if (pos == get_length()) {
+ if (pos + p_length >= get_length()) {
data.resize(pos + p_length);
- for (uint64_t i = 0; i < p_length; i++) {
- data.write[pos + i] = p_src[i];
- }
- pos += p_length;
}
+
+ memcpy(data.ptrw() + pos, p_src, p_length);
+ pos += p_length;
}
void FileAccessEncrypted::flush() {
@@ -261,18 +243,6 @@ void FileAccessEncrypted::flush() {
// encrypted files keep data in memory till close()
}
-void FileAccessEncrypted::store_8(uint8_t p_dest) {
- ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
-
- if (pos < get_length()) {
- data.write[pos] = p_dest;
- pos++;
- } else if (pos == get_length()) {
- data.push_back(p_dest);
- pos++;
- }
-}
-
bool FileAccessEncrypted::file_exists(const String &p_name) {
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
if (fa.is_null()) {
diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h
index 42afe49a5e..5f8c803d60 100644
--- a/core/io/file_access_encrypted.h
+++ b/core/io/file_access_encrypted.h
@@ -73,14 +73,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 9521a4f666..1541a5ed4a 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -122,16 +122,6 @@ bool FileAccessMemory::eof_reached() const {
return pos >= length;
}
-uint8_t FileAccessMemory::get_8() const {
- uint8_t ret = 0;
- if (pos < length) {
- ret = data[pos];
- }
- ++pos;
-
- return ret;
-}
-
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(data, -1);
@@ -157,16 +147,12 @@ void FileAccessMemory::flush() {
ERR_FAIL_NULL(data);
}
-void FileAccessMemory::store_8(uint8_t p_byte) {
- ERR_FAIL_NULL(data);
- ERR_FAIL_COND(pos >= length);
- data[pos++] = p_byte;
-}
-
void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND(!p_src && p_length > 0);
+
uint64_t left = length - pos;
uint64_t write = MIN(p_length, left);
+
if (write < p_length) {
WARN_PRINT("Writing less data than requested");
}
diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h
index e9fbc26d75..39e1528d97 100644
--- a/core/io/file_access_memory.h
+++ b/core/io/file_access_memory.h
@@ -55,15 +55,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 02bf0a6039..eec27ce0aa 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -313,17 +313,6 @@ bool FileAccessPack::eof_reached() const {
return eof;
}
-uint8_t FileAccessPack::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- if (pos >= pf.size) {
- eof = true;
- return 0;
- }
-
- pos++;
- return f->get_8();
-}
-
uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -366,10 +355,6 @@ void FileAccessPack::flush() {
ERR_FAIL();
}
-void FileAccessPack::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 594ac8f089..595a36bca4 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -169,8 +169,6 @@ public:
virtual bool eof_reached() const override;
- virtual uint8_t get_8() const override;
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual void set_big_endian(bool p_big_endian) override;
@@ -179,8 +177,6 @@ public:
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override;
-
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override;
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index c0d1afc8e1..b33b7b35c3 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const {
return at_eof;
}
-uint8_t FileAccessZip::get_8() const {
- uint8_t ret = 0;
- get_buffer(&ret, 1);
- return ret;
-}
-
uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(zfile, -1);
@@ -328,7 +322,7 @@ void FileAccessZip::flush() {
ERR_FAIL();
}
-void FileAccessZip::store_8(uint8_t p_dest) {
+void FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h
index 88b63e93e2..1e11e050df 100644
--- a/core/io/file_access_zip.h
+++ b/core/io/file_access_zip.h
@@ -95,14 +95,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/image.cpp b/core/io/image.cpp
index f6065d984b..fcbe483e38 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -4225,7 +4225,7 @@ Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool
result["root_mean_squared"] = INFINITY;
result["peak_snr"] = 0.0f;
- ERR_FAIL_NULL_V(p_compared_image, result);
+ ERR_FAIL_COND_V(p_compared_image.is_null(), result);
Error err = OK;
Ref<Image> compared_image = duplicate(true);
if (compared_image->is_compressed()) {
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 61051727c1..664ff7857b 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -588,10 +588,756 @@ void JSON::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line);
ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message);
+ ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false));
+
ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary.
}
-////
+#define GDTYPE "__gdtype"
+#define VALUES "values"
+#define PASS_ARG p_allow_classes, p_allow_scripts
+
+Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_variant.get_type()) {
+ case Variant::NIL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::BOOL: {
+ return p_variant;
+ } break;
+ case Variant::INT: {
+ return p_variant;
+ } break;
+ case Variant::FLOAT: {
+ return p_variant;
+ } break;
+ case Variant::STRING: {
+ return p_variant;
+ } break;
+ case Variant::VECTOR2: {
+ Dictionary d;
+ Vector2 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR2I: {
+ Dictionary d;
+ Vector2i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2: {
+ Dictionary d;
+ Rect2 r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2I: {
+ Dictionary d;
+ Rect2i r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3: {
+ Dictionary d;
+ Vector3 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3I: {
+ Dictionary d;
+ Vector3i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM2D: {
+ Dictionary d;
+ Transform2D t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["origin"] = from_native(t[2]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4: {
+ Dictionary d;
+ Vector4 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4I: {
+ Dictionary d;
+ Vector4i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PLANE: {
+ Dictionary d;
+ Plane p = p_variant;
+ d["normal"] = from_native(p.normal);
+ d["d"] = p.d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::QUATERNION: {
+ Dictionary d;
+ Quaternion q = p_variant;
+ Array values;
+ values.push_back(q.x);
+ values.push_back(q.y);
+ values.push_back(q.z);
+ values.push_back(q.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::AABB: {
+ Dictionary d;
+ AABB aabb = p_variant;
+ d["position"] = from_native(aabb.position);
+ d["size"] = from_native(aabb.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::BASIS: {
+ Dictionary d;
+ Basis t = p_variant;
+ d["x"] = from_native(t.get_column(0));
+ d["y"] = from_native(t.get_column(1));
+ d["z"] = from_native(t.get_column(2));
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Dictionary d;
+ Transform3D t = p_variant;
+ d["basis"] = from_native(t.basis);
+ d["origin"] = from_native(t.origin);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PROJECTION: {
+ Dictionary d;
+ Projection t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["z"] = from_native(t[2]);
+ d["w"] = from_native(t[3]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::COLOR: {
+ Dictionary d;
+ Color c = p_variant;
+ Array values;
+ values.push_back(c.r);
+ values.push_back(c.g);
+ values.push_back(c.b);
+ values.push_back(c.a);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::STRING_NAME: {
+ Dictionary d;
+ d["name"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::NODE_PATH: {
+ Dictionary d;
+ d["path"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RID: {
+ Dictionary d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::OBJECT: {
+ Object *obj = p_variant.get_validated_object();
+
+ if (p_allow_classes && obj) {
+ Dictionary d;
+ List<PropertyInfo> property_list;
+ obj->get_property_list(&property_list);
+
+ d["type"] = obj->get_class();
+ Dictionary p;
+ for (const PropertyInfo &P : property_list) {
+ if (P.usage & PROPERTY_USAGE_STORAGE) {
+ if (P.name == "script" && !p_allow_scripts) {
+ continue;
+ }
+ p[P.name] = from_native(obj->get(P.name), PASS_ARG);
+ }
+ }
+ d["properties"] = p;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } else {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ }
+ } break;
+ case Variant::CALLABLE:
+ case Variant::SIGNAL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_variant;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ bool all_strings = true;
+ for (const Variant &K : keys) {
+ if (K.get_type() != Variant::STRING) {
+ all_strings = false;
+ break;
+ }
+ }
+
+ if (all_strings) {
+ Dictionary ret_dict;
+ for (const Variant &K : keys) {
+ ret_dict[K] = from_native(d[K], PASS_ARG);
+ }
+ return ret_dict;
+ } else {
+ Dictionary ret;
+ Array pairs;
+ for (const Variant &K : keys) {
+ Dictionary pair;
+ pair["key"] = from_native(K, PASS_ARG);
+ pair["value"] = from_native(d[K], PASS_ARG);
+ pairs.push_back(pair);
+ }
+ ret["pairs"] = pairs;
+ ret[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return ret;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_variant;
+ Array ret;
+ for (int i = 0; i < arr.size(); i++) {
+ ret.push_back(from_native(arr[i], PASS_ARG));
+ }
+ return ret;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ Dictionary d;
+ PackedByteArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ Dictionary d;
+ PackedInt32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ Dictionary d;
+ PackedInt64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ Dictionary d;
+ PackedFloat32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ Dictionary d;
+ PackedFloat64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+ Dictionary d;
+ PackedStringArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ Dictionary d;
+ PackedVector2Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector2 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ Dictionary d;
+ PackedVector3Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector3 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_COLOR_ARRAY: {
+ Dictionary d;
+ PackedColorArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Color v = arr[i];
+ values.push_back(v.r);
+ values.push_back(v.g);
+ values.push_back(v.b);
+ values.push_back(v.a);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ Dictionary d;
+ PackedVector4Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector4 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type())));
+ } break;
+ }
+
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+}
+
+Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_json.get_type()) {
+ case Variant::BOOL: {
+ return p_json;
+ } break;
+ case Variant::INT: {
+ return p_json;
+ } break;
+ case Variant::FLOAT: {
+ return p_json;
+ } break;
+ case Variant::STRING: {
+ return p_json;
+ } break;
+ case Variant::STRING_NAME: {
+ return p_json;
+ } break;
+ case Variant::CALLABLE: {
+ return p_json;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_json;
+ if (d.has(GDTYPE)) {
+ // Specific Godot Variant types serialized to JSON.
+ String type = d[GDTYPE];
+ if (type == Variant::get_type_name(Variant::VECTOR2)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2 v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR2I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2i v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::RECT2)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2 r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::RECT2I)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2i r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform2D t;
+ t[0] = to_native(d["x"]);
+ t[1] = to_native(d["y"]);
+ t[2] = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::PLANE)) {
+ ERR_FAIL_COND_V(!d.has("normal"), Variant());
+ ERR_FAIL_COND_V(!d.has("d"), Variant());
+ Plane p;
+ p.normal = to_native(d["normal"]);
+ p.d = d["d"];
+ return p;
+ } else if (type == Variant::get_type_name(Variant::QUATERNION)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Quaternion v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::AABB)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ AABB r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::BASIS)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ Basis b;
+ b.set_column(0, to_native(d["x"]));
+ b.set_column(1, to_native(d["y"]));
+ b.set_column(2, to_native(d["z"]));
+ return b;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) {
+ ERR_FAIL_COND_V(!d.has("basis"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform3D t;
+ t.basis = to_native(d["basis"]);
+ t.origin = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::PROJECTION)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ ERR_FAIL_COND_V(!d.has("w"), Variant());
+ Projection p;
+ p[0] = to_native(d["x"]);
+ p[1] = to_native(d["y"]);
+ p[2] = to_native(d["z"]);
+ p[3] = to_native(d["w"]);
+ return p;
+ } else if (type == Variant::get_type_name(Variant::COLOR)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Color c;
+ c.r = values[0];
+ c.g = values[1];
+ c.b = values[2];
+ c.a = values[3];
+ return c;
+ } else if (type == Variant::get_type_name(Variant::NODE_PATH)) {
+ ERR_FAIL_COND_V(!d.has("path"), Variant());
+ NodePath np = d["path"];
+ return np;
+ } else if (type == Variant::get_type_name(Variant::STRING_NAME)) {
+ ERR_FAIL_COND_V(!d.has("name"), Variant());
+ StringName s = d["name"];
+ return s;
+ } else if (type == Variant::get_type_name(Variant::OBJECT)) {
+ ERR_FAIL_COND_V(!d.has("type"), Variant());
+ ERR_FAIL_COND_V(!d.has("properties"), Variant());
+
+ ERR_FAIL_COND_V(!p_allow_classes, Variant());
+
+ String obj_type = d["type"];
+ bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script");
+ ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant());
+ Object *obj = ClassDB::instantiate(obj_type);
+ ERR_FAIL_NULL_V(obj, Variant());
+
+ Dictionary p = d["properties"];
+
+ List<Variant> keys;
+ p.get_key_list(&keys);
+
+ for (const Variant &K : keys) {
+ String property = K;
+ Variant value = to_native(p[K], PASS_ARG);
+ obj->set(property, value);
+ }
+
+ Variant v(obj);
+
+ return v;
+ } else if (type == Variant::get_type_name(Variant::DICTIONARY)) {
+ ERR_FAIL_COND_V(!d.has("pairs"), Variant());
+ Array pairs = d["pairs"];
+ Dictionary r;
+ for (int i = 0; i < pairs.size(); i++) {
+ Dictionary p = pairs[i];
+ ERR_CONTINUE(!p.has("key"));
+ ERR_CONTINUE(!p.has("value"));
+ r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]);
+ }
+ return r;
+ } else if (type == Variant::get_type_name(Variant::ARRAY)) {
+ ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE));
+ } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedByteArray pbarr;
+ pbarr.resize(values.size());
+ for (int i = 0; i < pbarr.size(); i++) {
+ pbarr.write[i] = values[i];
+ }
+ return pbarr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedStringArray arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 2 != 0, Variant());
+ PackedVector2Array arr;
+ arr.resize(values.size() / 2);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 3 != 0, Variant());
+ PackedVector3Array arr;
+ arr.resize(values.size() / 3);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedColorArray arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedVector4Array arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else {
+ return Variant();
+ }
+ } else {
+ // Regular dictionary with string keys.
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ Dictionary r;
+ for (const Variant &K : keys) {
+ r[K] = to_native(d[K], PASS_ARG);
+ }
+ return r;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_json;
+ Array ret;
+ ret.resize(arr.size());
+ for (int i = 0; i < arr.size(); i++) {
+ ret[i] = to_native(arr[i], PASS_ARG);
+ }
+ return ret;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type())));
+ return Variant();
+ }
+ }
+
+ return Variant();
+}
+
+#undef GDTYPE
+#undef VALUES
+#undef PASS_ARG
////////////
diff --git a/core/io/json.h b/core/io/json.h
index 801fa29b4b..67b5e09afa 100644
--- a/core/io/json.h
+++ b/core/io/json.h
@@ -94,6 +94,9 @@ public:
void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; }
+
+ static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false);
+ static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false);
};
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h
index cc9996101e..f4ffa09022 100644
--- a/core/io/packed_data_container.h
+++ b/core/io/packed_data_container.h
@@ -36,7 +36,7 @@
class PackedDataContainer : public Resource {
GDCLASS(PackedDataContainer, Resource);
- enum {
+ enum : uint32_t {
TYPE_DICT = 0xFFFFFFFF,
TYPE_ARRAY = 0xFFFFFFFE,
};
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
index 86737609bf..8d91e6dec2 100644
--- a/core/io/plist.cpp
+++ b/core/io/plist.cpp
@@ -814,7 +814,7 @@ bool PList::load_string(const String &p_string, String &r_err_out) {
}
PackedByteArray PList::save_asn1() const {
- if (root == nullptr) {
+ if (root.is_null()) {
ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node.");
}
size_t size = root->get_asn1_size(1);
@@ -848,7 +848,7 @@ PackedByteArray PList::save_asn1() const {
}
String PList::save_text() const {
- if (root == nullptr) {
+ if (root.is_null()) {
ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node.");
}
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 598c99c188..ff12dc5851 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
p_take_over = false; // Can't take over an empty path
}
- ResourceCache::lock.lock();
+ {
+ MutexLock lock(ResourceCache::lock);
- if (!path_cache.is_empty()) {
- ResourceCache::resources.erase(path_cache);
- }
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources.erase(path_cache);
+ }
- path_cache = "";
+ path_cache = "";
- Ref<Resource> existing = ResourceCache::get_ref(p_path);
+ Ref<Resource> existing = ResourceCache::get_ref(p_path);
- if (existing.is_valid()) {
- if (p_take_over) {
- existing->path_cache = String();
- ResourceCache::resources.erase(p_path);
- } else {
- ResourceCache::lock.unlock();
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ if (existing.is_valid()) {
+ if (p_take_over) {
+ existing->path_cache = String();
+ ResourceCache::resources.erase(p_path);
+ } else {
+ ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ }
}
- }
- path_cache = p_path;
+ path_cache = p_path;
- if (!path_cache.is_empty()) {
- ResourceCache::resources[path_cache] = this;
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources[path_cache] = this;
+ }
}
- ResourceCache::lock.unlock();
_resource_path_changed();
}
@@ -486,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
if (p_remapped) {
ResourceLoader::remapped_list.add(&remapped_list);
} else {
ResourceLoader::remapped_list.remove(&remapped_list);
}
-
- ResourceCache::lock.unlock();
}
#ifdef TOOLS_ENABLED
@@ -564,14 +562,13 @@ Resource::~Resource() {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
// Only unregister from the cache if this is the actual resource listed there.
// (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.)
HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache);
if (likely(E && E->value == this)) {
ResourceCache::resources.remove(E);
}
- ResourceCache::lock.unlock();
}
HashMap<String, Resource *> ResourceCache::resources;
@@ -600,18 +597,20 @@ void ResourceCache::clear() {
}
bool ResourceCache::has(const String &p_path) {
- lock.lock();
+ Resource **res = nullptr;
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
- if (res && (*res)->get_reference_count() == 0) {
- // This resource is in the process of being deleted, ignore its existence.
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
- }
+ res = resources.getptr(p_path);
- lock.unlock();
+ if (res && (*res)->get_reference_count() == 0) {
+ // This resource is in the process of being deleted, ignore its existence.
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
+ }
if (!res) {
return false;
@@ -622,28 +621,27 @@ bool ResourceCache::has(const String &p_path) {
Ref<Resource> ResourceCache::get_ref(const String &p_path) {
Ref<Resource> ref;
- lock.lock();
-
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
+ Resource **res = resources.getptr(p_path);
- if (res) {
- ref = Ref<Resource>(*res);
- }
+ if (res) {
+ ref = Ref<Resource>(*res);
+ }
- if (res && !ref.is_valid()) {
- // This resource is in the process of being deleted, ignore its existence
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
+ if (res && !ref.is_valid()) {
+ // This resource is in the process of being deleted, ignore its existence
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
}
- lock.unlock();
-
return ref;
}
void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
- lock.lock();
+ MutexLock mutex_lock(lock);
LocalVector<String> to_remove;
@@ -663,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
for (const String &E : to_remove) {
resources.erase(E);
}
-
- lock.unlock();
}
int ResourceCache::get_cached_resource_count() {
- lock.lock();
- int rc = resources.size();
- lock.unlock();
-
- return rc;
+ MutexLock mutex_lock(lock);
+ return resources.size();
}
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index b4c43abe00..a572dd562e 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -35,6 +35,8 @@
#include "core/os/os.h"
#include "core/variant/variant_parser.h"
+ResourceFormatImporterLoadOnStartup ResourceImporter::load_on_startup = nullptr;
+
bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const {
return p_a->get_importer_name() < p_b->get_importer_name();
}
@@ -137,6 +139,20 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
}
Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
+#ifdef TOOLS_ENABLED
+ // When loading a resource on startup, we use the load_on_startup callback,
+ // which executes the loading in the EditorFileSystem. It can reimport
+ // the resource and retry the load, allowing the resource to be loaded
+ // even if it is not yet imported.
+ if (ResourceImporter::load_on_startup != nullptr) {
+ return ResourceImporter::load_on_startup(this, p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
+ }
+#endif
+
+ return load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, false);
+}
+
+Ref<Resource> ResourceFormatImporter::load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors) {
PathAndType pat;
Error err = _get_path_and_type(p_path, pat);
@@ -148,6 +164,13 @@ Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p
return Ref<Resource>();
}
+ if (p_silence_errors) {
+ // Note: Some importers do not create files in the .godot folder, so we need to check if the path is empty.
+ if (!pat.path.is_empty() && !FileAccess::exists(pat.path)) {
+ return Ref<Resource>();
+ }
+ }
+
Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress);
#ifdef TOOLS_ENABLED
diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h
index 7b1806c3d2..6ea5d0972a 100644
--- a/core/io/resource_importer.h
+++ b/core/io/resource_importer.h
@@ -35,6 +35,9 @@
#include "core/io/resource_saver.h"
class ResourceImporter;
+class ResourceFormatImporter;
+
+typedef Ref<Resource> (*ResourceFormatImporterLoadOnStartup)(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
class ResourceFormatImporter : public ResourceFormatLoader {
struct PathAndType {
@@ -60,6 +63,7 @@ class ResourceFormatImporter : public ResourceFormatLoader {
public:
static ResourceFormatImporter *get_singleton() { return singleton; }
virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
+ Ref<Resource> load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors);
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override;
virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override;
@@ -94,6 +98,7 @@ public:
String get_import_base_path(const String &p_for_file) const;
Error get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const;
+
ResourceFormatImporter();
};
@@ -104,6 +109,8 @@ protected:
static void _bind_methods();
public:
+ static ResourceFormatImporterLoadOnStartup load_on_startup;
+
virtual String get_importer_name() const = 0;
virtual String get_visible_name() const = 0;
virtual void get_recognized_extensions(List<String> *p_extensions) const = 0;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 3e809ae762..7cf101b0de 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -227,28 +227,27 @@ void ResourceFormatLoader::_bind_methods() {
// This should be robust enough to be called redundantly without issues.
void ResourceLoader::LoadToken::clear() {
- thread_load_mutex.lock();
-
WorkerThreadPool::TaskID task_to_await = 0;
- // User-facing tokens shouldn't be deleted until completely claimed.
- DEV_ASSERT(user_rc == 0 && user_path.is_empty());
-
- if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
- DEV_ASSERT(thread_load_tasks.has(local_path));
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
- if (load_task.task_id && !load_task.awaited) {
- task_to_await = load_task.task_id;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ // User-facing tokens shouldn't be deleted until completely claimed.
+ DEV_ASSERT(user_rc == 0 && user_path.is_empty());
+
+ if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
+ DEV_ASSERT(thread_load_tasks.has(local_path));
+ ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ if (load_task.task_id && !load_task.awaited) {
+ task_to_await = load_task.task_id;
+ }
+ // Removing a task which is still in progress would be catastrophic.
+ // Tokens must be alive until the task thread function is done.
+ DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
+ thread_load_tasks.erase(local_path);
+ local_path.clear();
}
- // Removing a task which is still in progress would be catastrophic.
- // Tokens must be alive until the task thread function is done.
- DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
- thread_load_tasks.erase(local_path);
- local_path.clear();
}
- thread_load_mutex.unlock();
-
// If task is unused, await it here, locally, now the token data is consistent.
if (task_to_await) {
PREPARE_FOR_WTP_WAIT
@@ -265,7 +264,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
load_nesting++;
if (load_paths_stack.size()) {
- thread_load_mutex.lock();
+ MutexLock thread_load_lock(thread_load_mutex);
const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1);
HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
// Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
@@ -273,7 +272,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
if (E && !is_remapped_load) {
E->value.sub_tasks.insert(p_original_path);
}
- thread_load_mutex.unlock();
}
load_paths_stack.push_back(original_path);
@@ -318,13 +316,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
void ResourceLoader::_run_load_task(void *p_userdata) {
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
- thread_load_mutex.lock();
- if (cleaning_tasks) {
- load_task.status = THREAD_LOAD_FAILED;
- thread_load_mutex.unlock();
- return;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ if (cleaning_tasks) {
+ load_task.status = THREAD_LOAD_FAILED;
+ return;
+ }
}
- thread_load_mutex.unlock();
// Thread-safe either if it's the current thread or a brand new one.
CallQueue *own_mq_override = nullptr;
@@ -1170,17 +1168,17 @@ String ResourceLoader::path_remap(const String &p_path) {
}
void ResourceLoader::reload_translation_remaps() {
- ResourceCache::lock.lock();
-
List<Resource *> to_reload;
- SelfList<Resource> *E = remapped_list.first();
- while (E) {
- to_reload.push_back(E->self());
- E = E->next();
- }
+ {
+ MutexLock lock(ResourceCache::lock);
+ SelfList<Resource> *E = remapped_list.first();
- ResourceCache::lock.unlock();
+ while (E) {
+ to_reload.push_back(E->self());
+ E = E->next();
+ }
+ }
//now just make sure to not delete any of these resources while changing locale..
while (to_reload.front()) {
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index fd53ed28fd..1afc5f4bbb 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -105,6 +105,9 @@ public:
static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); }
static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); }
+ static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); }
+ static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); }
+
static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); }
static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); }
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 5c793a676f..a65411629f 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -181,7 +181,7 @@ public:
return 0;
}
- static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, bool p_notify_postinitialize) {
+ static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
// Find the closest native parent, that isn't a runtime class.
@@ -192,7 +192,7 @@ public:
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
// Construct a placeholder.
- Object *obj = native_parent->creation_func(p_notify_postinitialize);
+ Object *obj = native_parent->creation_func(static_cast<bool>(p_notify_postinitialize));
// ClassDB::set_object_extension_instance() won't be called for placeholders.
// We need need to make sure that all the things it would have done (even if
@@ -271,6 +271,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) {
p_classes->sort_custom<StringName::AlphCompare>();
}
+
+void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) {
+ OBJTYPE_RLOCK;
+
+ for (const KeyValue<StringName, ClassInfo> &E : classes) {
+ if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) {
+ continue;
+ }
+ if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) {
+ continue;
+ }
+ p_classes->push_back(E.key);
+ }
+
+ p_classes->sort_custom<StringName::AlphCompare>();
+}
#endif
void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) {
@@ -1632,14 +1648,16 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia
Variant index = psg->index;
const Variant *arg[1] = { &index };
Callable::CallError ce;
- r_value = p_object->callp(psg->getter, arg, 1, ce);
+ const Variant value = p_object->callp(psg->getter, arg, 1, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
} else {
Callable::CallError ce;
if (psg->_getptr) {
r_value = psg->_getptr->call(p_object, nullptr, 0, ce);
} else {
- r_value = p_object->callp(psg->getter, nullptr, 0, ce);
+ const Variant value = p_object->callp(psg->getter, nullptr, 0, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
}
}
return true;
diff --git a/core/object/class_db.h b/core/object/class_db.h
index d6a95b58e2..620092a6c4 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -285,6 +285,7 @@ public:
static void get_class_list(List<StringName> *p_classes);
#ifdef TOOLS_ENABLED
static void get_extensions_class_list(List<StringName> *p_classes);
+ static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes);
static ObjectGDExtension *get_placeholder_extension(const StringName &p_class);
#endif
static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 1e06dc8e35..4be1dc4b34 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -746,7 +746,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
}
Callable::CallError ce;
- Variant ret = callp(p_method, argptrs, p_args.size(), ce);
+ const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
}
@@ -787,7 +787,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
if (script_instance) {
ret = script_instance->callp(p_method, p_args, p_argcount, r_error);
- //force jumptable
+ // Force jump table.
switch (r_error.error) {
case Callable::CallError::CALL_OK:
return ret;
@@ -1023,6 +1023,14 @@ void Object::remove_meta(const StringName &p_name) {
set_meta(p_name, Variant());
}
+void Object::merge_meta_from(const Object *p_src) {
+ List<StringName> meta_keys;
+ p_src->get_meta_list(&meta_keys);
+ for (const StringName &key : meta_keys) {
+ set_meta(key, p_src->get_meta(key));
+ }
+}
+
TypedArray<Dictionary> Object::_get_property_list_bind() const {
List<PropertyInfo> lpi;
get_property_list(&lpi);
@@ -1904,7 +1912,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens
void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
void *binding = nullptr;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
binding = _instance_bindings[i].binding;
@@ -1935,14 +1943,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi
_instance_binding_count++;
}
- _instance_binding_mutex.unlock();
-
return binding;
}
bool Object::has_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
found = true;
@@ -1950,14 +1956,12 @@ bool Object::has_instance_binding(void *p_token) {
}
}
- _instance_binding_mutex.unlock();
-
return found;
}
void Object::free_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (!found && _instance_bindings[i].token == p_token) {
if (_instance_bindings[i].free_callback) {
@@ -1976,7 +1980,6 @@ void Object::free_instance_binding(void *p_token) {
if (found) {
_instance_binding_count--;
}
- _instance_binding_mutex.unlock();
}
#ifdef TOOLS_ENABLED
@@ -2294,7 +2297,7 @@ void ObjectDB::cleanup() {
// Ensure calling the native classes because if a leaked instance has a script
// that overrides any of those methods, it'd not be OK to call them at this point,
// now the scripting languages have already been terminated.
- MethodBind *node_get_name = ClassDB::get_method("Node", "get_name");
+ MethodBind *node_get_path = ClassDB::get_method("Node", "get_path");
MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path");
Callable::CallError call_error;
@@ -2304,7 +2307,7 @@ void ObjectDB::cleanup() {
String extra_info;
if (obj->is_class("Node")) {
- extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error));
+ extra_info = " - Node path: " + String(node_get_path->call(obj, nullptr, 0, call_error));
}
if (obj->is_class("Resource")) {
extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error));
diff --git a/core/object/object.h b/core/object/object.h
index ba6b309542..bc3f663baf 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -667,7 +667,7 @@ protected:
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
bool can_die = true;
if (_instance_bindings) {
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].reference_callback) {
if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) {
@@ -675,7 +675,6 @@ protected:
}
}
}
- _instance_binding_mutex.unlock();
}
return can_die;
}
@@ -868,7 +867,8 @@ public:
argptrs[i] = &args[i];
}
Callable::CallError cerr;
- return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ const Variant ret = callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ return (cerr.error == Callable::CallError::CALL_OK) ? ret : Variant();
}
void notification(int p_notification, bool p_reversed = false);
@@ -895,6 +895,7 @@ public:
MTVIRTUAL void remove_meta(const StringName &p_name);
MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const;
MTVIRTUAL void get_meta_list(List<StringName> *p_list) const;
+ MTVIRTUAL void merge_meta_from(const Object *p_src);
#ifdef TOOLS_ENABLED
void set_edited(bool p_edited);
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index 25ad3bf964..fe7bbd474c 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) {
if (finished_users == max_users) {
// Get rid of the group, because nobody else is using it.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(p_task->group);
- task_mutex.unlock();
}
// For groups, tasks get rid of themselves.
@@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo
}
bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Task *const *taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task
}
- bool completed = (*taskp)->completed;
- task_mutex.unlock();
-
- return completed;
+ return (*taskp)->completed;
}
Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
@@ -522,10 +517,9 @@ void WorkerThreadPool::yield() {
}
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
Task **taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_MSG("Invalid Task ID.");
}
Task *task = *taskp;
@@ -534,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
// This avoids a race condition where a task is created and yield-over called before it's processed.
task->pending_notify_yield_over = true;
}
- task_mutex.unlock();
return;
}
@@ -542,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
td.yield_is_over = true;
td.signaled = true;
td.cond_var.notify_one();
-
- task_mutex.unlock();
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
@@ -601,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act
}
uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(0, "Invalid Group ID");
}
- uint32_t elements = (*groupp)->completed_index.get();
- task_mutex.unlock();
- return elements;
+ return (*groupp)->completed_index.get();
}
bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Group ID");
}
- bool completed = (*groupp)->completed.is_set();
- task_mutex.unlock();
- return completed;
+ return (*groupp)->completed.is_set();
}
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
@@ -644,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
if (finished_users == max_users) {
// All tasks using this group are gone (finished before the group), so clear the group too.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(group);
- task_mutex.unlock();
}
}
- task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
+ MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
groups.erase(p_group);
- task_mutex.unlock();
#endif
}
@@ -704,6 +687,8 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio)
max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
+ print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads));
+
threads.resize(p_thread_count);
for (uint32_t i = 0; i < threads.size(); i++) {
diff --git a/core/os/os.h b/core/os/os.h
index be8820eba9..e6ce527720 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -111,9 +111,6 @@ protected:
virtual void initialize() = 0;
virtual void initialize_joypads() = 0;
- void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; }
- void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; }
-
void set_display_driver_id(int p_display_driver_id) { _display_driver_id = p_display_driver_id; }
virtual void set_main_loop(MainLoop *p_main_loop) = 0;
@@ -131,6 +128,9 @@ public:
static OS *get_singleton();
+ void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; }
+ void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; }
+
String get_current_rendering_driver_name() const { return _current_rendering_driver_name; }
String get_current_rendering_method() const { return _current_rendering_method; }
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 5d59d65f92..28077fc8c5 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -191,11 +191,10 @@ StringName::StringName(const StringName &p_name) {
}
void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
- mutex.lock();
+ MutexLock lock(mutex);
if (*ptr == StringName()) {
*ptr = StringName(p_name, true);
}
- mutex.unlock();
}
StringName::StringName(const char *p_name, bool p_static) {
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 6e784881d0..4ac79ad10a 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -284,6 +284,11 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ if (p_locale_a == p_locale_b) {
+ // Exact match.
+ return 10;
+ }
+
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 667aae879c..9dff5c1e91 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -112,7 +112,7 @@ Error Callable::rpcp(int p_id, const Variant **p_arguments, int p_argcount, Call
argptrs[i + 2] = p_arguments[i];
}
- CallError tmp;
+ CallError tmp; // TODO: Check `tmp`?
Error err = (Error)obj->callp(SNAME("rpc_id"), argptrs, argcount, tmp).operator int64_t();
r_call_error.error = Callable::CallError::CALL_OK;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 24b30112bd..186643b024 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -2113,7 +2113,7 @@ Variant::operator ::RID() const {
}
#endif
Callable::CallError ce;
- Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
+ const Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) {
return ret;
}
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 63f5947280..49b8e34f8f 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -90,7 +90,7 @@
<param index="0" name="from" type="float" />
<param index="1" name="to" type="float" />
<description>
- Returns the difference between the two angles, in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise.
+ Returns the difference between the two angles (in radians), in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise.
</description>
</method>
<method name="asin">
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index dc1bee4336..d762ffa5a6 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -376,7 +376,19 @@
</constant>
<constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete">
Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree].
- If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ If a value track has un-interpolatable type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ Un-interpolatable type list:
+ - [constant @GlobalScope.TYPE_NIL]
+ - [constant @GlobalScope.TYPE_NODE_PATH]
+ - [constant @GlobalScope.TYPE_RID]
+ - [constant @GlobalScope.TYPE_OBJECT]
+ - [constant @GlobalScope.TYPE_CALLABLE]
+ - [constant @GlobalScope.TYPE_SIGNAL]
+ - [constant @GlobalScope.TYPE_DICTIONARY]
+ - [constant @GlobalScope.TYPE_PACKED_BYTE_ARRAY]
+ [constant @GlobalScope.TYPE_BOOL] and [constant @GlobalScope.TYPE_INT] are treated as [constant @GlobalScope.TYPE_FLOAT] during blending and rounded when the result is retrieved.
+ It is same for arrays and vectors with them such as [constant @GlobalScope.TYPE_PACKED_INT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2I], they are treated as [constant @GlobalScope.TYPE_PACKED_FLOAT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2]. Also note that for arrays, the size is also interpolated.
+ [constant @GlobalScope.TYPE_STRING] and [constant @GlobalScope.TYPE_STRING_NAME] are interpolated between character codes and lengths, but note that there is a difference in algorithm between interpolation between keys and interpolation by blending.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index eecbb05540..93680de21e 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -77,7 +77,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], this node is playing sounds. Setting this property has the same effect as [method play] and [method stop].
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index a3206ba1d6..71d2e1f0db 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -81,7 +81,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index bf02caffb4..a14c53bbfb 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -102,7 +102,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml
index 8a28514ed6..8d882deaee 100644
--- a/doc/classes/AudioStreamWAV.xml
+++ b/doc/classes/AudioStreamWAV.xml
@@ -15,7 +15,7 @@
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
- Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved.
+ Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or Quite OK Audio formats can't be saved.
[b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing.
</description>
</method>
@@ -23,19 +23,20 @@
<members>
<member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()">
Contains the audio data in bytes.
- [b]Note:[/b] This property expects signed PCM8 data. To convert unsigned PCM8 to signed PCM8, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_8_BITS], this property expects signed 8-bit PCM data. To convert from unsigned 8-bit PCM, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_QOA], this property expects data from a full QOA file.
</member>
<member name="format" type="int" setter="set_format" getter="get_format" enum="AudioStreamWAV.Format" default="0">
Audio format. See [enum Format] constants for values.
</member>
<member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0">
- The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop start point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0">
- The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop end point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0">
- The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values.
+ The loop mode. See [enum LoopMode] constants for values.
</member>
<member name="mix_rate" type="int" setter="set_mix_rate" getter="get_mix_rate" default="44100">
The sample rate for mixing this audio. Higher values require more storage space, but result in better quality.
@@ -48,16 +49,16 @@
</members>
<constants>
<constant name="FORMAT_8_BITS" value="0" enum="Format">
- 8-bit audio codec.
+ 8-bit PCM audio codec.
</constant>
<constant name="FORMAT_16_BITS" value="1" enum="Format">
- 16-bit audio codec.
+ 16-bit PCM audio codec.
</constant>
<constant name="FORMAT_IMA_ADPCM" value="2" enum="Format">
- Audio is compressed using IMA ADPCM.
+ Audio is lossily compressed as IMA ADPCM.
</constant>
<constant name="FORMAT_QOA" value="3" enum="Format">
- Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]).
+ Audio is lossily compressed as [url=https://qoaformat.org/]Quite OK Audio[/url].
</constant>
<constant name="LOOP_DISABLED" value="0" enum="LoopMode">
Audio does not loop.
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index 27726ff8a2..d7770f2cd5 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -168,6 +168,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml
new file mode 100644
index 0000000000..7eeee3d7fd
--- /dev/null
+++ b/doc/classes/EditorContextMenuPlugin.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorContextMenuPlugin" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Plugin for adding custom context menus in the editor.
+ </brief_description>
+ <description>
+ [EditorContextMenuPlugin] allows for the addition of custom options in the editor's context menu.
+ Currently, context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_popup_menu" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="paths" type="PackedStringArray" />
+ <description>
+ Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function.
+ </description>
+ </method>
+ <method name="add_context_menu_item">
+ <return type="void" />
+ <param index="0" name="name" type="String" />
+ <param index="1" name="callback" type="Callable" />
+ <param index="2" name="icon" type="Texture2D" default="null" />
+ <param index="3" name="shortcut" type="Shortcut" default="null" />
+ <description>
+ Add custom options to the context menu of the currently specified slot.
+ To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function.
+ [codeblock]
+ func _popup_menu(paths):
+ add_context_menu_item("File Custom options", handle, ICON)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="add_menu_shortcut">
+ <return type="void" />
+ <param index="0" name="shortcut" type="Shortcut" />
+ <param index="1" name="callback" type="Callable" />
+ <description>
+ To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet.
+ Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly.
+ [codeblock]
+ func _init():
+ add_menu_shortcut(SHORTCUT, handle);
+ [/codeblock]
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml
index 0e5de79b25..d4084e84a0 100644
--- a/doc/classes/EditorExportPlatform.xml
+++ b/doc/classes/EditorExportPlatform.xml
@@ -11,11 +11,217 @@
<link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link>
</tutorials>
<methods>
+ <method name="add_message">
+ <return type="void" />
+ <param index="0" name="type" type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="1" name="category" type="String" />
+ <param index="2" name="message" type="String" />
+ <description>
+ Adds a message to the export log that will be displayed when exporting ends.
+ </description>
+ </method>
+ <method name="clear_messages">
+ <return type="void" />
+ <description>
+ Clears the export log.
+ </description>
+ </method>
+ <method name="create_preset">
+ <return type="EditorExportPreset" />
+ <description>
+ Create a new preset for this platform.
+ </description>
+ </method>
+ <method name="export_pack">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a PCK archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a full project at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project_files">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="save_cb" type="Callable" />
+ <param index="3" name="shared_cb" type="Callable" default="Callable()" />
+ <description>
+ Exports project files for the specified preset. This method can be used to implement custom export format, other than PCK and ZIP. One of the callbacks is called for each exported file.
+ [param save_cb] is called for all exported files and have the following arguments: [code]file_path: String[/code], [code]file_data: PackedByteArray[/code], [code]file_index: int[/code], [code]file_count: int[/code], [code]encryption_include_filters: PackedStringArray[/code], [code]encryption_exclude_filters: PackedStringArray[/code], [code]encryption_key: PackedByteArray[/code].
+ [param shared_cb] is called for exported native shared/static libraries and have the following arguments: [code]file_path: String[/code], [code]tags: PackedStringArray[/code], [code]target_folder: String[/code].
+ [b]Note:[/b] [code]file_index[/code] and [code]file_count[/code] are intended for progress tracking only and aren't necesserely unique and precise.
+ </description>
+ </method>
+ <method name="export_zip">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Create a ZIP archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="find_export_template" qualifiers="const">
+ <return type="Dictionary" />
+ <param index="0" name="template_file_name" type="String" />
+ <description>
+ Locates export template for the platform, and returns [Dictionary] with the following keys: [code]path: String[/code] and [code]error: String[/code]. This method is provided for convenience and custom export platforms aren't required to use it or keep export templates stored in the same way official templates are.
+ </description>
+ </method>
+ <method name="gen_export_flags">
+ <return type="PackedStringArray" />
+ <param index="0" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ Generates array of command line arguments for the default export templates for the debug flags and editor settings.
+ </description>
+ </method>
+ <method name="get_current_presets" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns array of [EditorExportPreset]s for this platform.
+ </description>
+ </method>
+ <method name="get_forced_export_files" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of core file names that always should be exported regardless of preset config.
+ </description>
+ </method>
+ <method name="get_message_category" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message category, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of messages in the export log.
+ </description>
+ </method>
+ <method name="get_message_text" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message text, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message type, for the message with [param index].
+ </description>
+ </method>
<method name="get_os_name" qualifiers="const">
<return type="String" />
<description>
Returns the name of the export operating system handled by this [EditorExportPlatform] class, as a friendly string. Possible return values are [code]Windows[/code], [code]Linux[/code], [code]macOS[/code], [code]Android[/code], [code]iOS[/code], and [code]Web[/code].
</description>
</method>
+ <method name="get_worst_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <description>
+ Returns most severe message type currently present in the export log.
+ </description>
+ </method>
+ <method name="save_pack">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="embed" type="bool" default="false" />
+ <description>
+ Saves PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size).
+ </description>
+ </method>
+ <method name="save_zip">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <description>
+ Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ </description>
+ </method>
+ <method name="ssh_push_to_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="scp_args" type="PackedStringArray" />
+ <param index="3" name="src_file" type="String" />
+ <param index="4" name="dst_file" type="String" />
+ <description>
+ Uploads specified file over SCP protocol to the remote host.
+ </description>
+ </method>
+ <method name="ssh_run_on_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_arg" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="output" type="Array" default="[]" />
+ <param index="5" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns command output in the [param output].
+ </description>
+ </method>
+ <method name="ssh_run_on_remote_no_wait" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_args" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns process ID (on the remote host) without waiting for command to finish.
+ </description>
+ </method>
</methods>
+ <constants>
+ <constant name="EXPORT_MESSAGE_NONE" value="0" enum="ExportMessageType">
+ Invalid message type used as the default value when no type is specified.
+ </constant>
+ <constant name="EXPORT_MESSAGE_INFO" value="1" enum="ExportMessageType">
+ Message type for informational messages that have no effect on the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_WARNING" value="2" enum="ExportMessageType">
+ Message type for warning messages that should be addressed but still allow to complete the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_ERROR" value="3" enum="ExportMessageType">
+ Message type for error messages that must be addressed and fail the export.
+ </constant>
+ <constant name="DEBUG_FLAG_DUMB_CLIENT" value="1" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is expected to use remote file system. If set, [method gen_export_flags] will add [code]--remove-fs[/code] and [code]--remote-fs-password[/code] (if password is set in the editor settings) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG" value="2" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remote debug is enabled. If set, [method gen_export_flags] will add [code]--remote-debug[/code] and [code]--breakpoints[/code] (if breakpoints are selected in the script editor or added by the plugin) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST" value="4" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is running on the localhost. If set, [method gen_export_flags] will use [code]localhost[/code] instead of [member EditorSettings.network/debug/remote_host] as remote debugger host.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_COLLISIONS" value="8" enum="DebugFlags" is_bitfield="true">
+ Flag is set if "Visible Collision Shapes" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-collisions[/code] command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_NAVIGATION" value="16" enum="DebugFlags" is_bitfield="true">
+ Flag is set if Visible Navigation" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-navigation[/code] command line arguments to the list.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml
new file mode 100644
index 0000000000..ef589e2f58
--- /dev/null
+++ b/doc/classes/EditorExportPlatformExtension.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformExtension" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Base class for custom [EditorExportPlatform] implementations (plugins).
+ </brief_description>
+ <description>
+ External [EditorExportPlatform] implementations should inherit from this class.
+ To use [EditorExportPlatform], register it using the [method EditorPlugin.add_export_platform] method first.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_can_export" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code], if specified [param preset] is valid and can be exported. Use [method set_config_error] and [method set_config_missing_templates] to set error details.
+ Usual implementation can call [method _has_valid_export_configuration] and [method _has_valid_project_configuration] to determine if export is possible.
+ </description>
+ </method>
+ <method name="_cleanup" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ [b]Optional.[/b]
+ Called by the editor before platform is unregistered.
+ </description>
+ </method>
+ <method name="_export_pack" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Creates a PCK archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type.
+ </description>
+ </method>
+ <method name="_export_project" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Required.[/b]
+ Creates a full project at [param path] for the specified [param preset].
+ This method is called when "Export" button is pressed in the export dialog.
+ This method implementation can call [method EditorExportPlatform.save_pack] or [method EditorExportPlatform.save_zip] to use default PCK/ZIP export process, or calls [method EditorExportPlatform.export_project_files] and implement custom callback for processing each exported file.
+ </description>
+ </method>
+ <method name="_export_zip" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Create a ZIP archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type.
+ </description>
+ </method>
+ <method name="_get_binary_extensions" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of supported binary extensions for the full project export.
+ </description>
+ </method>
+ <method name="_get_debug_protocol" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns protocol used for remote debugging. Default implementation return [code]tcp://[/code].
+ </description>
+ </method>
+ <method name="_get_device_architecture" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns device architecture for one-click deploy.
+ </description>
+ </method>
+ <method name="_get_export_option_visibility" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns visibility for the specified [param preset]. Default implementation return [code]true[/code] for all options.
+ </description>
+ </method>
+ <method name="_get_export_option_warning" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="StringName" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns warning message for the specified [param preset]. Default implementation return empty string for all options.
+ </description>
+ </method>
+ <method name="_get_export_options" qualifiers="virtual const">
+ <return type="Dictionary[]" />
+ <description>
+ [b]Optional.[/b]
+ Returns a property list, as an [Array] of dictionaries. Each [Dictionary] must at least contain the [code]name: StringName[/code] and [code]type: Variant.Type[/code] entries.
+ Additionally, the following keys are supported:
+ - [code]hint: PropertyHint[/code]
+ - [code]hint_string: String[/code]
+ - [code]usage: PropertyUsageFlags[/code]
+ - [code]class_name: StringName[/code]
+ - [code]default_value: Variant[/code], default value of the property.
+ - [code]update_visibility: bool[/code], if set to [code]true[/code], [method _get_export_option_visibility] is called for each property when this property is changed.
+ - [code]required: bool[/code], if set to [code]true[/code], this property warnings are critical, and should be resolved to make export possible. This value is a hint for the [method _has_valid_export_configuration] implementation, and not used by the engine directly.
+ See also [method Object._get_property_list].
+ </description>
+ </method>
+ <method name="_get_logo" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Required.[/b]
+ Returns platform logo displayed in the export dialog, logo should be 32x32 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns export platform name.
+ </description>
+ </method>
+ <method name="_get_option_icon" qualifiers="virtual const">
+ <return type="ImageTexture" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item icon for the specified [param device], icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_option_label" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item label for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_option_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item tooltip for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_options_count" qualifiers="virtual const">
+ <return type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns number one-click deploy devices (or other one-click option displayed in the menu).
+ </description>
+ </method>
+ <method name="_get_options_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns tooltip of the one-click deploy menu button.
+ </description>
+ </method>
+ <method name="_get_os_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns target OS name.
+ </description>
+ </method>
+ <method name="_get_platform_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features.
+ </description>
+ </method>
+ <method name="_get_preset_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features for the specified [param preset].
+ </description>
+ </method>
+ <method name="_get_run_icon" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Optional.[/b]
+ Returns icon of the one-click deploy menu button, icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_has_valid_export_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if export configuration is valid.
+ </description>
+ </method>
+ <method name="_has_valid_project_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if project configuration is valid.
+ </description>
+ </method>
+ <method name="_is_executable" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if specified file is a valid executable (native executable or script) for the target platform.
+ </description>
+ </method>
+ <method name="_poll_export" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if one-click deploy options are changed and editor interface should be updated.
+ </description>
+ </method>
+ <method name="_run" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="device" type="int" />
+ <param index="2" name="debug_flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ This method is called when [param device] one-click deploy menu option is selected.
+ Implementation should export project to a temporary location, upload and run it on the specific [param device], or perform another action associated with the menu item.
+ </description>
+ </method>
+ <method name="_should_update_export_options" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if export options list is changed and presets should be updated.
+ </description>
+ </method>
+ <method name="get_config_error" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="get_config_missing_templates" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_error" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="error_text" type="String" />
+ <description>
+ Sets current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_missing_templates" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="missing_templates" type="bool" />
+ <description>
+ Set to [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 9ef911a68d..42e1968eb0 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -305,6 +305,18 @@
In case of a directory code-sign will error if you place non code object in directory.
</description>
</method>
+ <method name="get_export_platform" qualifiers="const">
+ <return type="EditorExportPlatform" />
+ <description>
+ Returns currently used export platform.
+ </description>
+ </method>
+ <method name="get_export_preset" qualifiers="const">
+ <return type="EditorExportPreset" />
+ <description>
+ Returns currently used export preset.
+ </description>
+ </method>
<method name="get_option" qualifiers="const">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml
new file mode 100644
index 0000000000..bba79364e4
--- /dev/null
+++ b/doc/classes/EditorExportPreset.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPreset" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Export preset configuration.
+ </brief_description>
+ <description>
+ Export preset configuration. Instances of [EditorExportPreset] by editor UI and intended to be used a read-only configuration passed to the [EditorExportPlatform] methods when exporting the project.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="are_advanced_options_enabled" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], is "Advanced" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_custom_features" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns string with a comma separated list of custom features.
+ </description>
+ </method>
+ <method name="get_customized_files" qualifiers="const">
+ <return type="Dictionary" />
+ <description>
+ Returns [Dictionary] of files selected in the "Resources" tab of the export dialog. Dictionary keys are file names and values are export mode - [code]"strip[/code], [code]"keep"[/code], or [code]"remove"[/code]. See also [method get_file_export_mode].
+ </description>
+ </method>
+ <method name="get_customized_files_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of files selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_directory" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK directory encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_pck" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encryption_ex_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_in_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_key" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns PCK encryption key.
+ </description>
+ </method>
+ <method name="get_exclude_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during export.
+ </description>
+ </method>
+ <method name="get_export_filter" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.ExportFilter" />
+ <description>
+ Returns export file filter mode selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_export_path" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export target path.
+ </description>
+ </method>
+ <method name="get_file_export_mode" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.FileExportMode" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="default" type="int" enum="EditorExportPreset.FileExportMode" default="0" />
+ <description>
+ Returns file export mode for the specified file.
+ </description>
+ </method>
+ <method name="get_files_to_export" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of files to export.
+ </description>
+ </method>
+ <method name="get_include_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during export.
+ </description>
+ </method>
+ <method name="get_or_env" qualifiers="const">
+ <return type="Variant" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="env_var" type="String" />
+ <description>
+ Returns export option value or value of environment variable if it is set.
+ </description>
+ </method>
+ <method name="get_preset_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export preset name.
+ </description>
+ </method>
+ <method name="get_script_export_mode" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns script export mode.
+ </description>
+ </method>
+ <method name="get_version" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="windows_version" type="bool" />
+ <description>
+ Returns the preset's version number, or fall back to the [member ProjectSettings.application/config/version] project setting if set to an empty string.
+ If [param windows_version] is [code]true[/code], formats the returned version number to be compatible with Windows executable metadata.
+ </description>
+ </method>
+ <method name="has" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="property" type="StringName" />
+ <description>
+ Returns [code]true[/code] if preset has specified property.
+ </description>
+ </method>
+ <method name="has_export_file">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ Returns [code]true[/code] if specified file is exported.
+ </description>
+ </method>
+ <method name="is_dedicated_server" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if dedicated server export mode is selected in the export dialog.
+ </description>
+ </method>
+ <method name="is_runnable" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if "Runnable" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="EXPORT_ALL_RESOURCES" value="0" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_SCENES" value="1" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_RESOURCES" value="2" enum="ExportFilter">
+ </constant>
+ <constant name="EXCLUDE_SELECTED_RESOURCES" value="3" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_CUSTOMIZED" value="4" enum="ExportFilter">
+ </constant>
+ <constant name="MODE_FILE_NOT_CUSTOMIZED" value="0" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_STRIP" value="1" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_KEEP" value="2" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_REMOVE" value="3" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_TEXT" value="0" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS" value="1" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS_COMPRESSED" value="2" enum="ScriptExportMode">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index 178be439c3..795c5c1c2f 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -117,6 +117,12 @@
[b]Note:[/b] When creating custom editor UI, prefer accessing theme items directly from your GUI nodes using the [code]get_theme_*[/code] methods.
</description>
</method>
+ <method name="get_editor_undo_redo" qualifiers="const">
+ <return type="EditorUndoRedoManager" />
+ <description>
+ Returns the editor's [EditorUndoRedoManager].
+ </description>
+ </method>
<method name="get_editor_viewport_2d" qualifiers="const">
<return type="SubViewport" />
<description>
@@ -299,8 +305,9 @@
<return type="void" />
<param index="0" name="callback" type="Callable" />
<param index="1" name="valid_types" type="StringName[]" default="[]" />
+ <param index="2" name="current_value" type="Node" default="null" />
<description>
- Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types.
+ Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types. If [param current_value] is provided, the Node will be automatically selected in the tree, if it exists.
[b]Example:[/b] Display the node selection dialog as soon as this node is added to the tree for the first time:
[codeblock]
func _ready():
@@ -320,8 +327,9 @@
<param index="0" name="object" type="Object" />
<param index="1" name="callback" type="Callable" />
<param index="2" name="type_filter" type="PackedInt32Array" default="PackedInt32Array()" />
+ <param index="3" name="current_value" type="String" default="&quot;&quot;" />
<description>
- Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values.
+ Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values. If [param current_value] is provided, the property will be selected automatically in the property list, if it exists.
[codeblock]
func _ready():
if Engine.is_editor_hint():
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index ed3233b1ae..b3191e5378 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -401,6 +401,15 @@
Adds a script at [param path] to the Autoload list as [param name].
</description>
</method>
+ <method name="add_context_menu_plugin">
+ <return type="void" />
+ <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <description>
+ Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added.
+ Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel.
+ </description>
+ </method>
<method name="add_control_to_bottom_panel">
<return type="Button" />
<param index="0" name="control" type="Control" />
@@ -455,6 +464,13 @@
Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin].
</description>
</method>
+ <method name="add_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Registers a new [EditorExportPlatform]. Export platforms provides functionality of exporting to the specific platform.
+ </description>
+ </method>
<method name="add_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
@@ -617,6 +633,14 @@
Removes an Autoload [param name] from the list.
</description>
</method>
+ <method name="remove_context_menu_plugin">
+ <return type="void" />
+ <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" />
+ <param index="1" name="plugin" type="EditorContextMenuPlugin" />
+ <description>
+ Removes a context menu plugin from the specified slot.
+ </description>
+ </method>
<method name="remove_control_from_bottom_panel">
<return type="void" />
<param index="0" name="control" type="Control" />
@@ -653,6 +677,13 @@
Removes the debugger plugin with given script from the Debugger.
</description>
</method>
+ <method name="remove_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Removes an export platform registered by [method add_export_platform].
+ </description>
+ </method>
<method name="remove_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
@@ -860,5 +891,17 @@
<constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput">
Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead.
</constant>
+ <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot">
+ Context menu slot for the SceneTree.
+ </constant>
+ <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot">
+ Context menu slot for the FileSystem.
+ </constant>
+ <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot">
+ Context menu slot for the ScriptEditor file list.
+ </constant>
+ <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot">
+ Context menu slot for the FileSystem create submenu.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml
index fcfdbb5c44..9c9b6d11b2 100644
--- a/doc/classes/EditorResourcePreviewGenerator.xml
+++ b/doc/classes/EditorResourcePreviewGenerator.xml
@@ -23,7 +23,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview from a given resource with the specified size. This must always be implemented.
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
@@ -35,7 +35,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate].
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 5ac0d790a2..0f8c69a1bb 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -68,6 +68,21 @@
Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!).
</description>
</method>
+ <method name="clear_history">
+ <return type="void" />
+ <param index="0" name="id" type="int" default="-99" />
+ <param index="1" name="increase_version" type="bool" default="true" />
+ <description>
+ Clears the given undo history. You can clear history for a specific scene, global history, or for all scenes at once if [param id] is [constant INVALID_HISTORY].
+ If [param increase_version] is [code]true[/code], the undo history version will be increased, marking it as unsaved. Useful for operations that modify the scene, but don't support undo.
+ [codeblock]
+ var scene_root = EditorInterface.get_edited_scene_root()
+ var undo_redo = EditorInterface.get_editor_undo_redo()
+ undo_redo.clear_history(undo_redo.get_object_history_id(scene_root))
+ [/codeblock]
+ [b]Note:[/b] If you want to mark an edited scene as unsaved without clearing its history, use [method EditorInterface.mark_scene_as_unsaved] instead.
+ </description>
+ </method>
<method name="commit_action">
<return type="void" />
<param index="0" name="execute" type="bool" default="true" />
diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml
index 211bc023c0..97d2d08752 100644
--- a/doc/classes/GDExtensionManager.xml
+++ b/doc/classes/GDExtensionManager.xml
@@ -56,6 +56,20 @@
</method>
</methods>
<signals>
+ <signal name="extension_loaded">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted after the editor has finished loading a new extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
+ <signal name="extension_unloading">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted before the editor starts unloading an extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
<signal name="extensions_reloaded">
<description>
Emitted after the editor has finished reloading one or more extensions.
diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml
index 8a19aa39bf..fe5fdfa89a 100644
--- a/doc/classes/JSON.xml
+++ b/doc/classes/JSON.xml
@@ -37,6 +37,16 @@
<tutorials>
</tutorials>
<methods>
+ <method name="from_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="variant" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a native engine type to a JSON-compliant dictionary.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
<method name="get_error_line" qualifiers="const">
<return type="int" />
<description>
@@ -123,6 +133,16 @@
[/codeblock]
</description>
</method>
+ <method name="to_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="json" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
</methods>
<members>
<member name="data" type="Variant" setter="set_data" getter="get_data" default="null">
diff --git a/doc/classes/JavaClass.xml b/doc/classes/JavaClass.xml
index ecfcaa8781..9a6c30df10 100644
--- a/doc/classes/JavaClass.xml
+++ b/doc/classes/JavaClass.xml
@@ -1,13 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="JavaClass" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
- Represents an object from the Java Native Interface.
+ Represents a class from the Java Native Interface.
</brief_description>
<description>
- Represents an object from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap].
- [b]Note:[/b] This class only works on Android. For any other build, this class does nothing.
+ Represents a class from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap].
+ [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing.
[b]Note:[/b] This class is not to be confused with [JavaScriptObject].
</description>
<tutorials>
</tutorials>
+ <methods>
+ <method name="get_java_class_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns the Java class name.
+ </description>
+ </method>
+ <method name="get_java_method_list" qualifiers="const">
+ <return type="Dictionary[]" />
+ <description>
+ Returns the object's Java methods and their signatures as an [Array] of dictionaries, in the same format as [method Object.get_method_list].
+ </description>
+ </method>
+ <method name="get_java_parent_class" qualifiers="const">
+ <return type="JavaClass" />
+ <description>
+ Returns a [JavaClass] representing the Java parent class of this class.
+ </description>
+ </method>
+ </methods>
</class>
diff --git a/doc/classes/JavaClassWrapper.xml b/doc/classes/JavaClassWrapper.xml
index 01c3392b04..b43e149e9f 100644
--- a/doc/classes/JavaClassWrapper.xml
+++ b/doc/classes/JavaClassWrapper.xml
@@ -6,6 +6,15 @@
<description>
The JavaClassWrapper singleton provides a way for the Godot application to send and receive data through the [url=https://developer.android.com/training/articles/perf-jni]Java Native Interface[/url] (JNI).
[b]Note:[/b] This singleton is only available in Android builds.
+ [codeblock]
+ var LocalDateTime = JavaClassWrapper.wrap("java.time.LocalDateTime")
+ var DateTimeFormatter = JavaClassWrapper.wrap("java.time.format.DateTimeFormatter")
+
+ var datetime = LocalDateTime.now()
+ var formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")
+
+ print(datetime.format(formatter))
+ [/codeblock]
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/JavaObject.xml b/doc/classes/JavaObject.xml
new file mode 100644
index 0000000000..f38070e7d9
--- /dev/null
+++ b/doc/classes/JavaObject.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="JavaObject" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Represents an object from the Java Native Interface.
+ </brief_description>
+ <description>
+ Represents an object from the Java Native Interface. It can be returned from Java methods called on [JavaClass] or other [JavaObject]s. See [JavaClassWrapper] for an example.
+ [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing.
+ [b]Note:[/b] This class is not to be confused with [JavaScriptObject].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_java_class" qualifiers="const">
+ <return type="JavaClass" />
+ <description>
+ Returns the [JavaClass] that this object is an instance of.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 1502690b45..28c60194c8 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -207,6 +207,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1f31fef5ca..497070fa81 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2909,7 +2909,9 @@
The default compression factor for lossless WebP. Decompression speed is mostly unaffected by the compression factor. Supported values are 0 to 100.
</member>
<member name="rendering/viewport/hdr_2d" type="bool" setter="" getter="" default="false">
- If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow).
+ If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. 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. 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.
+ [b]Note:[/b] This setting will have no effect when using the GL Compatibility renderer as the GL Compatibility renderer always renders in low dynamic range for performance reasons.
+ [b]Note:[/b] This property is only read when the project starts. To toggle HDR 2D at runtime, set [member Viewport.use_hdr_2d] on the root [Viewport].
</member>
<member name="rendering/viewport/transparent_background" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables [member Viewport.transparent_bg] on the root viewport. This allows per-pixel transparency to be effective after also enabling [member display/window/size/transparent] and [member display/window/per_pixel_transparency/allowed].
diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml
index b7aa6947d9..76cf4cbfeb 100644
--- a/doc/classes/PropertyTweener.xml
+++ b/doc/classes/PropertyTweener.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
[PropertyTweener] is used to interpolate a property in an object. See [method Tween.tween_property] for more usage information.
+ The tweener will finish automatically if the target object is freed.
[b]Note:[/b] [method Tween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly.
</description>
<tutorials>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index c81d5d4fab..4cdfba17e9 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -412,6 +412,14 @@
[b]Note:[/b] [param count] is unused and can be left unspecified.
</description>
</method>
+ <method name="canvas_item_attach_skeleton">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <param index="1" name="skeleton" type="RID" />
+ <description>
+ Attaches a skeleton to the [CanvasItem]. Removes the previous skeleton.
+ </description>
+ </method>
<method name="canvas_item_clear">
<return type="void" />
<param index="0" name="item" type="RID" />
diff --git a/doc/classes/ResourceImporterWAV.xml b/doc/classes/ResourceImporterWAV.xml
index 8f118ace03..3caa66d262 100644
--- a/doc/classes/ResourceImporterWAV.xml
+++ b/doc/classes/ResourceImporterWAV.xml
@@ -4,17 +4,18 @@
Imports a WAV audio file for playback.
</brief_description>
<description>
- WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end deviceS.
+ WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end devices.
+ By default, Godot imports WAV files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property.
</description>
<tutorials>
<link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<members>
- <member name="compress/mode" type="int" setter="" getter="" default="0">
+ <member name="compress/mode" type="int" setter="" getter="" default="2">
The compression mode to use on import.
- [b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality.
- [b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3.
- [b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher.
+ - [b]PCM (Uncompressed):[/b] Imports audio data without any form of compression, preserving the highest possible quality. It has the lowest CPU cost, but the highest memory usage.
+ - [b]IMA ADPCM:[/b] Applies fast, lossy compression during import, noticeably decreasing the quality, but with low CPU cost and memory usage. Does not support seeking and only Forward loop mode is supported.
+ - [b][url=https://qoaformat.org/]Quite OK Audio[/url]:[/b] Also applies lossy compression on import, having a slightly higher CPU cost compared to IMA ADPCM, but much higher quality and the lowest memory usage.
</member>
<member name="edit/loop_begin" type="int" setter="" getter="" default="0">
The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file.
@@ -23,11 +24,12 @@
The end loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file. A value of [code]-1[/code] uses the end of the audio file as the end loop point.
</member>
<member name="edit/loop_mode" type="int" setter="" getter="" default="0">
- Controls how audio should loop. This is automatically read from the WAV metadata on import.
- [b]Disabled:[/b] Don't loop audio, even if metadata indicates the file should be played back looping.
- [b]Forward:[/b] Standard audio looping.
- [b]Ping-Pong:[/b] Play audio forward until it's done playing, then play it backward and repeat. This is similar to mirrored texture repeat, but for audio.
- [b]Backward:[/b] Play audio in reverse and loop back to the end when done playing.
+ Controls how audio should loop.
+ - [b]Detect From WAV:[/b] Uses loop information from the WAV metadata.
+ - [b]Disabled:[/b] Don't loop audio, even if the metadata indicates the file playback should loop.
+ - [b]Forward:[/b] Standard audio looping. Plays the audio forward from the beginning to [member edit/loop_end], then returns to [member edit/loop_begin] and repeats.
+ - [b]Ping-Pong:[/b] Plays the audio forward until [member edit/loop_end], then backwards to [member edit/loop_begin], repeating this cycle.
+ - [b]Backward:[/b] Plays the audio backwards from [member edit/loop_end] to [member edit/loop_begin], then repeats.
[b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely.
</member>
<member name="edit/normalize" type="bool" setter="" getter="" default="false">
diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml
index 50adecccf5..5cf077c266 100644
--- a/doc/classes/ScriptEditor.xml
+++ b/doc/classes/ScriptEditor.xml
@@ -10,6 +10,12 @@
<tutorials>
</tutorials>
<methods>
+ <method name="get_breakpoints">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of breakpoints.
+ </description>
+ </method>
<method name="get_current_editor" qualifiers="const">
<return type="ScriptEditorBase" />
<description>
diff --git a/doc/classes/SpriteFrames.xml b/doc/classes/SpriteFrames.xml
index fd8c15c560..b891f4adcd 100644
--- a/doc/classes/SpriteFrames.xml
+++ b/doc/classes/SpriteFrames.xml
@@ -39,6 +39,14 @@
Removes all animations. An empty [code]default[/code] animation will be created.
</description>
</method>
+ <method name="duplicate_animation">
+ <return type="void" />
+ <param index="0" name="anim_from" type="StringName" />
+ <param index="1" name="anim_to" type="StringName" />
+ <description>
+ Duplicates the animation [param anim_from] to a new animation named [param anim_to]. Fails if [param anim_to] already exists, or if [param anim_from] does not exist.
+ </description>
+ </method>
<method name="get_animation_loop" qualifiers="const">
<return type="bool" />
<param index="0" name="anim" type="StringName" />
diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml
index 9468763b6e..f9505124ef 100644
--- a/doc/classes/TileData.xml
+++ b/doc/classes/TileData.xml
@@ -16,6 +16,13 @@
Adds a collision polygon to the tile on the given TileSet physics layer.
</description>
</method>
+ <method name="add_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <description>
+ Adds an occlusion polygon to the tile on the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="get_collision_polygon_one_way_margin" qualifiers="const">
<return type="float" />
<param index="0" name="layer_id" type="int" />
@@ -78,7 +85,7 @@
[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
</description>
</method>
- <method name="get_occluder" qualifiers="const">
+ <method name="get_occluder" qualifiers="const" deprecated="Use [method get_occluder_polygon] instead.">
<return type="OccluderPolygon2D" />
<param index="0" name="layer_id" type="int" />
<param index="1" name="flip_h" type="bool" default="false" />
@@ -89,6 +96,25 @@
[param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon.
</description>
</method>
+ <method name="get_occluder_polygon" qualifiers="const">
+ <return type="OccluderPolygon2D" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <param index="2" name="flip_h" type="bool" default="false" />
+ <param index="3" name="flip_v" type="bool" default="false" />
+ <param index="4" name="transpose" type="bool" default="false" />
+ <description>
+ Returns the occluder polygon at index [param polygon_index] from the TileSet occlusion layer with index [param layer_id].
+ The [param flip_h], [param flip_v], and [param transpose] parameters can be [code]true[/code] to transform the returned polygon.
+ </description>
+ </method>
+ <method name="get_occluder_polygons_count" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="layer_id" type="int" />
+ <description>
+ Returns the number of occluder polygons of the tile in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="get_terrain_peering_bit" qualifiers="const">
<return type="int" />
<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />
@@ -119,6 +145,14 @@
Removes the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
</description>
</method>
+ <method name="remove_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <description>
+ Removes the polygon at index [param polygon_index] for TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="set_collision_polygon_one_way">
<return type="void" />
<param index="0" name="layer_id" type="int" />
@@ -194,7 +228,7 @@
Sets the navigation polygon for the TileSet navigation layer with index [param layer_id].
</description>
</method>
- <method name="set_occluder">
+ <method name="set_occluder" deprecated="Use [method set_occluder_polygon] instead.">
<return type="void" />
<param index="0" name="layer_id" type="int" />
<param index="1" name="occluder_polygon" type="OccluderPolygon2D" />
@@ -202,6 +236,23 @@
Sets the occluder for the TileSet occlusion layer with index [param layer_id].
</description>
</method>
+ <method name="set_occluder_polygon">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygon_index" type="int" />
+ <param index="2" name="polygon" type="OccluderPolygon2D" />
+ <description>
+ Sets the occluder for polygon with index [param polygon_index] in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
+ <method name="set_occluder_polygons_count">
+ <return type="void" />
+ <param index="0" name="layer_id" type="int" />
+ <param index="1" name="polygons_count" type="int" />
+ <description>
+ Sets the occluder polygon count in the TileSet occlusion layer with index [param layer_id].
+ </description>
+ </method>
<method name="set_terrain_peering_bit">
<return type="void" />
<param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" />
diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml
index 65148e875d..88f5f9978c 100644
--- a/doc/classes/Tweener.xml
+++ b/doc/classes/Tweener.xml
@@ -11,7 +11,7 @@
<signals>
<signal name="finished">
<description>
- Emitted when the [Tweener] has just finished its job.
+ Emitted when the [Tweener] has just finished its job or became invalid (e.g. due to a freed object).
</description>
</signal>
</signals>
diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml
index 92b2297bf4..7b4952580c 100644
--- a/doc/classes/VehicleWheel3D.xml
+++ b/doc/classes/VehicleWheel3D.xml
@@ -18,6 +18,18 @@
Returns [code]null[/code] if the wheel is not in contact with a surface, or the contact body is not a [PhysicsBody3D].
</description>
</method>
+ <method name="get_contact_normal" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the normal of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns a vector pointing directly along the suspension axis toward the vehicle in world space.
+ </description>
+ </method>
+ <method name="get_contact_point" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the point of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns the maximum point of the wheel's ray cast in world space, which is defined by [code]wheel_rest_length + wheel_radius[/code].
+ </description>
+ </method>
<method name="get_rpm" qualifiers="const">
<return type="float" />
<description>
diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl
index 029084c34c..f88c218506 100644
--- a/drivers/gles3/shaders/stdlib_inc.glsl
+++ b/drivers/gles3/shaders/stdlib_inc.glsl
@@ -9,19 +9,17 @@
// Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile.
uint float2half(uint f) {
- uint e = f & uint(0x7f800000);
- if (e <= uint(0x38000000)) {
- return uint(0);
- } else {
- return ((f >> uint(16)) & uint(0x8000)) |
- (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) |
- ((f >> uint(13)) & uint(0x03ff));
- }
+ uint b = f + uint(0x00001000);
+ uint e = (b & uint(0x7F800000)) >> 23;
+ uint m = b & uint(0x007FFFFF);
+ return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF);
}
uint half2float(uint h) {
- uint h_e = h & uint(0x7c00);
- return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13)));
+ uint e = (h & uint(0x7C00)) >> uint(10);
+ uint m = (h & uint(0x03FF)) << uint(13);
+ uint v = m >> uint(23);
+ return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000)));
}
uint godot_packHalf2x16(vec2 v) {
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 2b3c19dbb8..d6472c44c1 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -121,7 +121,7 @@ Config::Config() {
#ifdef WEB_ENABLED
msaa_supported = (msaa_max_samples > 0);
#else
- msaa_supported = extensions.has("GL_EXT_framebuffer_multisample");
+ msaa_supported = true;
#endif
#ifndef IOS_ENABLED
#ifdef WEB_ENABLED
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index 81e7220439..ed00dd235f 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -203,7 +203,7 @@ struct LightmapInstance {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index b55a2e0a8a..de82d74aff 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -743,6 +743,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const {
}
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh.");
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 57fe96fb6f..36393dde86 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1389,8 +1389,22 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->alloc_width;
tinfo.height = t->alloc_height;
- tinfo.depth = t->depth;
tinfo.bytes = t->total_data_size;
+
+ switch (t->type) {
+ case Texture::TYPE_3D:
+ tinfo.depth = t->depth;
+ break;
+
+ case Texture::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
@@ -1521,7 +1535,11 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image,
h = MAX(1, h >> 1);
}
- texture->total_data_size = tsize;
+ if (texture->target == GL_TEXTURE_CUBE_MAP || texture->target == GL_TEXTURE_2D_ARRAY) {
+ texture->total_data_size = tsize * texture->layers;
+ } else {
+ texture->total_data_size = tsize;
+ }
texture->stored_cube_sides |= (1 << p_layer);
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index f0a3e85f88..97f33bb1e8 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -227,6 +227,7 @@ public:
id<MTLRenderCommandEncoder> encoder = nil;
id<MTLBuffer> __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD.
MTLIndexType index_type = MTLIndexTypeUInt16;
+ uint32_t index_offset = 0;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> vertex_offsets;
// clang-format off
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index c66b683e23..abdcccf00c 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -717,6 +717,7 @@ void MDCommandBuffer::render_bind_index_buffer(RDD::BufferID p_buffer, RDD::Inde
render.index_buffer = rid::get(p_buffer);
render.index_type = p_format == RDD::IndexBufferFormat::INDEX_BUFFER_FORMAT_UINT16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
+ render.index_offset = p_offset;
}
void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
@@ -729,13 +730,16 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
id<MTLRenderCommandEncoder> enc = render.encoder;
+ uint32_t index_offset = render.index_offset;
+ index_offset += p_first_index * (render.index_type == MTLIndexTypeUInt16 ? sizeof(uint16_t) : sizeof(uint32_t));
+
[enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive
indexCount:p_index_count
indexType:render.index_type
indexBuffer:render.index_buffer
- indexBufferOffset:p_vertex_offset
+ indexBufferOffset:index_offset
instanceCount:p_instance_count
- baseVertex:p_first_index
+ baseVertex:p_vertex_offset
baseInstance:p_first_instance];
}
diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h
index 0363ab111a..7e0b09186d 100644
--- a/drivers/metal/rendering_context_driver_metal.h
+++ b/drivers/metal/rendering_context_driver_metal.h
@@ -33,22 +33,36 @@
#ifdef METAL_ENABLED
-#import "rendering_device_driver_metal.h"
-
#import "servers/rendering/rendering_context_driver.h"
+#import "servers/rendering/rendering_device_driver.h"
#import <CoreGraphics/CGGeometry.h>
+
+#ifdef __OBJC__
+#import "metal_objects.h"
+
#import <Metal/Metal.h>
#import <QuartzCore/CALayer.h>
@class CAMetalLayer;
@protocol CAMetalDrawable;
+#else
+typedef enum MTLPixelFormat {
+ MTLPixelFormatBGRA8Unorm = 80,
+} MTLPixelFormat;
+class MDCommandBuffer;
+#endif
+
class PixelFormats;
class MDResourceCache;
class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver {
protected:
- id<MTLDevice> metal_device = nil;
+#ifdef __OBJC__
+ id<MTLDevice> metal_device = nullptr;
+#else
+ void *metal_device = nullptr;
+#endif
Device device; // There is only one device on Apple Silicon.
public:
@@ -73,12 +87,20 @@ public:
// Platform-specific data for the Windows embedded in this driver.
struct WindowPlatformData {
+#ifdef __OBJC__
CAMetalLayer *__unsafe_unretained layer;
+#else
+ void *layer;
+#endif
};
- class Surface {
+ class API_AVAILABLE(macos(11.0), ios(14.0)) Surface {
protected:
+#ifdef __OBJC__
id<MTLDevice> device;
+#else
+ void *device;
+#endif
public:
uint32_t width = 0;
@@ -86,8 +108,15 @@ public:
DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED;
bool needs_resize = false;
- Surface(id<MTLDevice> p_device) :
- device(p_device) {}
+ Surface(
+#ifdef __OBJC__
+ id<MTLDevice> p_device
+#else
+ void *p_device
+#endif
+ ) :
+ device(p_device) {
+ }
virtual ~Surface() = default;
MTLPixelFormat get_pixel_format() const { return MTLPixelFormatBGRA8Unorm; }
@@ -96,104 +125,14 @@ public:
virtual void present(MDCommandBuffer *p_cmd_buffer) = 0;
};
- class SurfaceLayer : public Surface {
- CAMetalLayer *__unsafe_unretained layer = nil;
- LocalVector<MDFrameBuffer> frame_buffers;
- LocalVector<id<MTLDrawable>> drawables;
- uint32_t rear = -1;
- uint32_t front = 0;
- uint32_t count = 0;
-
- public:
- SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) :
- Surface(p_device), layer(p_layer) {
- layer.allowsNextDrawableTimeout = YES;
- layer.framebufferOnly = YES;
- layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES;
- layer.pixelFormat = get_pixel_format();
- layer.device = p_device;
- }
-
- ~SurfaceLayer() override {
- layer = nil;
- }
-
- Error resize(uint32_t p_desired_framebuffer_count) override final {
- if (width == 0 || height == 0) {
- // Very likely the window is minimized, don't create a swap chain.
- return ERR_SKIP;
- }
-
- CGSize drawableSize = CGSizeMake(width, height);
- CGSize current = layer.drawableSize;
- if (!CGSizeEqualToSize(current, drawableSize)) {
- layer.drawableSize = drawableSize;
- }
-
- // Metal supports a maximum of 3 drawables.
- p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count);
- layer.maximumDrawableCount = p_desired_framebuffer_count;
-
-#if TARGET_OS_OSX
- // Display sync is only supported on macOS.
- switch (vsync_mode) {
- case DisplayServer::VSYNC_MAILBOX:
- case DisplayServer::VSYNC_ADAPTIVE:
- case DisplayServer::VSYNC_ENABLED:
- layer.displaySyncEnabled = YES;
- break;
- case DisplayServer::VSYNC_DISABLED:
- layer.displaySyncEnabled = NO;
- break;
- }
+#ifdef __OBJC__
+ id<MTLDevice>
+#else
+ void *
#endif
- drawables.resize(p_desired_framebuffer_count);
- frame_buffers.resize(p_desired_framebuffer_count);
- for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
- // Reserve space for the drawable texture.
- frame_buffers[i].textures.resize(1);
- }
-
- return OK;
- }
-
- RDD::FramebufferID acquire_next_frame_buffer() override final {
- if (count == frame_buffers.size()) {
- return RDD::FramebufferID();
- }
-
- rear = (rear + 1) % frame_buffers.size();
- count++;
-
- MDFrameBuffer &frame_buffer = frame_buffers[rear];
- frame_buffer.size = Size2i(width, height);
-
- id<CAMetalDrawable> drawable = layer.nextDrawable;
- ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
- drawables[rear] = drawable;
- frame_buffer.textures.write[0] = drawable.texture;
-
- return RDD::FramebufferID(&frame_buffer);
- }
-
- void present(MDCommandBuffer *p_cmd_buffer) override final {
- if (count == 0) {
- return;
- }
-
- // Release texture and drawable.
- frame_buffers[front].textures.write[0] = nil;
- id<MTLDrawable> drawable = drawables[front];
- drawables[front] = nil;
-
- count--;
- front = (front + 1) % frame_buffers.size();
-
- [p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
- }
- };
-
- id<MTLDevice> get_metal_device() const { return metal_device; }
+ get_metal_device() const {
+ return metal_device;
+ }
#pragma mark - Initialization
diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm
index b257d7142a..b97b586352 100644
--- a/drivers/metal/rendering_context_driver_metal.mm
+++ b/drivers/metal/rendering_context_driver_metal.mm
@@ -30,6 +30,8 @@
#import "rendering_context_driver_metal.h"
+#import "rendering_device_driver_metal.h"
+
@protocol MTLDeviceEx <MTLDevice>
#if TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 130300
- (void)setShouldMaximizeConcurrentCompilation:(BOOL)v;
@@ -77,6 +79,103 @@ void RenderingContextDriverMetal::driver_free(RenderingDeviceDriver *p_driver) {
memdelete(p_driver);
}
+class API_AVAILABLE(macos(11.0), ios(14.0)) SurfaceLayer : public RenderingContextDriverMetal::Surface {
+ CAMetalLayer *__unsafe_unretained layer = nil;
+ LocalVector<MDFrameBuffer> frame_buffers;
+ LocalVector<id<MTLDrawable>> drawables;
+ uint32_t rear = -1;
+ uint32_t front = 0;
+ uint32_t count = 0;
+
+public:
+ SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) :
+ Surface(p_device), layer(p_layer) {
+ layer.allowsNextDrawableTimeout = YES;
+ layer.framebufferOnly = YES;
+ layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES;
+ layer.pixelFormat = get_pixel_format();
+ layer.device = p_device;
+ }
+
+ ~SurfaceLayer() override {
+ layer = nil;
+ }
+
+ Error resize(uint32_t p_desired_framebuffer_count) override final {
+ if (width == 0 || height == 0) {
+ // Very likely the window is minimized, don't create a swap chain.
+ return ERR_SKIP;
+ }
+
+ CGSize drawableSize = CGSizeMake(width, height);
+ CGSize current = layer.drawableSize;
+ if (!CGSizeEqualToSize(current, drawableSize)) {
+ layer.drawableSize = drawableSize;
+ }
+
+ // Metal supports a maximum of 3 drawables.
+ p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count);
+ layer.maximumDrawableCount = p_desired_framebuffer_count;
+
+#if TARGET_OS_OSX
+ // Display sync is only supported on macOS.
+ switch (vsync_mode) {
+ case DisplayServer::VSYNC_MAILBOX:
+ case DisplayServer::VSYNC_ADAPTIVE:
+ case DisplayServer::VSYNC_ENABLED:
+ layer.displaySyncEnabled = YES;
+ break;
+ case DisplayServer::VSYNC_DISABLED:
+ layer.displaySyncEnabled = NO;
+ break;
+ }
+#endif
+ drawables.resize(p_desired_framebuffer_count);
+ frame_buffers.resize(p_desired_framebuffer_count);
+ for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
+ // Reserve space for the drawable texture.
+ frame_buffers[i].textures.resize(1);
+ }
+
+ return OK;
+ }
+
+ RDD::FramebufferID acquire_next_frame_buffer() override final {
+ if (count == frame_buffers.size()) {
+ return RDD::FramebufferID();
+ }
+
+ rear = (rear + 1) % frame_buffers.size();
+ count++;
+
+ MDFrameBuffer &frame_buffer = frame_buffers[rear];
+ frame_buffer.size = Size2i(width, height);
+
+ id<CAMetalDrawable> drawable = layer.nextDrawable;
+ ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
+ drawables[rear] = drawable;
+ frame_buffer.textures.write[0] = drawable.texture;
+
+ return RDD::FramebufferID(&frame_buffer);
+ }
+
+ void present(MDCommandBuffer *p_cmd_buffer) override final {
+ if (count == 0) {
+ return;
+ }
+
+ // Release texture and drawable.
+ frame_buffers[front].textures.write[0] = nil;
+ id<MTLDrawable> drawable = drawables[front];
+ drawables[front] = nil;
+
+ count--;
+ front = (front + 1) % frame_buffers.size();
+
+ [p_cmd_buffer->get_command_buffer() presentDrawable:drawable];
+ }
+};
+
RenderingContextDriver::SurfaceID RenderingContextDriverMetal::surface_create(const void *p_platform_data) {
const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data);
Surface *surface = memnew(SurfaceLayer(wpd->layer, metal_device));
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index ea8b42b2e4..32f2d7dd79 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -218,67 +218,13 @@ bool FileAccessUnix::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessUnix::get_8() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
- return b;
-}
-
-uint16_t FileAccessUnix::get_16() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessUnix::get_32() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessUnix::get_64() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -308,41 +254,6 @@ void FileAccessUnix::flush() {
fflush(f);
}
-void FileAccessUnix::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
- ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1);
-}
-
-void FileAccessUnix::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 2, f) != 2);
-}
-
-void FileAccessUnix::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 4, f) != 4);
-}
-
-void FileAccessUnix::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 8, f) != 8);
-}
-
void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index c0286dbff3..76f629f7c2 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -67,20 +67,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp
index 5d9a27ad05..34758e8c7d 100644
--- a/drivers/unix/file_access_unix_pipe.cpp
+++ b/drivers/unix/file_access_unix_pipe.cpp
@@ -125,22 +125,9 @@ String FileAccessUnixPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessUnixPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (::read(fd[0], &b, 1) == 0) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
uint64_t read = ::read(fd[0], p_dst, p_length);
if (read == p_length) {
@@ -155,18 +142,10 @@ Error FileAccessUnixPipe::get_error() const {
return last_error;
}
-void FileAccessUnixPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
- if (::write(fd[1], &p_src, 1) != 1) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
+
if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_WRITE;
} else {
diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h
index 8e7988791b..19acdb5a37 100644
--- a/drivers/unix/file_access_unix_pipe.h
+++ b/drivers/unix/file_access_unix_pipe.h
@@ -65,14 +65,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override { return false; }
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 2ba353868b..4ea46e8214 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -497,6 +497,7 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
_register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false);
+ _register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false);
if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
_register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true);
@@ -1705,6 +1706,16 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
+ VkImageViewASTCDecodeModeEXT decode_mode;
+ if (enabled_device_extension_names.has(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME)) {
+ if (image_view_create_info.format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && image_view_create_info.format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
+ decode_mode.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT;
+ decode_mode.pNext = nullptr;
+ decode_mode.decodeMode = VK_FORMAT_R8G8B8A8_UNORM;
+ image_view_create_info.pNext = &decode_mode;
+ }
+ }
+
VkImageView vk_image_view = VK_NULL_HANDLE;
err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view);
if (err) {
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 1d1f2ce415..f7632842ed 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -71,7 +71,9 @@ struct DirAccessWindowsPrivate {
String DirAccessWindows::fix_path(const String &p_path) const {
String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/"));
-
+ if (r_path.ends_with(":")) {
+ r_path += "/";
+ }
if (r_path.is_relative_path()) {
r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path);
} else if (r_path == ".") {
@@ -304,7 +306,7 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) {
}
}
- return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) != 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
}
}
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index f6f721639c..0243d863f8 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -323,93 +323,9 @@ bool FileAccessWindows::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessWindows::get_8() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
-
- return b;
-}
-
-uint16_t FileAccessWindows::get_16() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessWindows::get_32() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessWindows::get_64() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(f, -1);
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
if (flags == READ_WRITE || flags == WRITE_READ) {
if (prev_op == WRITE) {
@@ -417,8 +333,10 @@ uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const
}
prev_op = READ;
}
+
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -453,77 +371,6 @@ void FileAccessWindows::flush() {
}
}
-void FileAccessWindows::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
- fwrite(&p_dest, 1, 1, f);
-}
-
-void FileAccessWindows::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- fwrite(&p_dest, 1, 2, f);
-}
-
-void FileAccessWindows::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- fwrite(&p_dest, 1, 4, f);
-}
-
-void FileAccessWindows::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- fwrite(&p_dest, 1, 8, f);
-}
-
void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL(f);
ERR_FAIL_COND(!p_src && p_length > 0);
@@ -536,6 +383,7 @@ void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
}
prev_op = WRITE;
}
+
ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != (size_t)p_length);
}
@@ -559,11 +407,10 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
return 0;
}
- String file = p_file;
- if (file.ends_with("/") && file != "/") {
+ String file = fix_path(p_file);
+ if (file.ends_with("\\") && file != "\\") {
file = file.substr(0, file.length() - 1);
}
- file = fix_path(file);
struct _stat st;
int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st);
diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h
index a25bbcfb3a..f458ff9c6c 100644
--- a/drivers/windows/file_access_windows.h
+++ b/drivers/windows/file_access_windows.h
@@ -69,20 +69,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp
index 7902c8e1d8..0c953b14aa 100644
--- a/drivers/windows/file_access_windows_pipe.cpp
+++ b/drivers/windows/file_access_windows_pipe.cpp
@@ -96,22 +96,9 @@ String FileAccessWindowsPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessWindowsPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
DWORD read = -1;
if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
@@ -126,15 +113,6 @@ Error FileAccessWindowsPipe::get_error() const {
return last_error;
}
-void FileAccessWindowsPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
- if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index b885ef78e6..4e9bd036ae 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -64,14 +64,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override { return false; }
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 5196857240..feb5f3d20d 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -719,7 +719,7 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) {
void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
is_filtered = p_filtered;
- if (animation == nullptr) {
+ if (animation.is_null()) {
return;
}
String base_path = animation->track_get_path(selected_track);
@@ -1660,7 +1660,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
switch (p_index) {
case MENU_KEY_INSERT: {
if (animation->get_track_count() > 0) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
time = editor->snap_time(time);
}
while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
@@ -1736,7 +1736,7 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
@@ -1859,7 +1859,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index f4403780c2..95ba301282 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -41,6 +41,7 @@
#include "editor/gui/editor_spin_slider.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
+#include "editor/multi_node_edit.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -120,12 +121,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
float val = p_value;
float prev_val = animation->track_get_key_transition(track, key);
setting = true;
+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS);
undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -177,12 +184,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
}
setting = true;
+
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
Variant prev = animation->track_get_key_value(track, key);
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev);
undo_redo->add_do_method(this, "_update_obj", animation);
undo_redo->add_undo_method(this, "_update_obj", animation);
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
setting = false;
@@ -1540,14 +1553,18 @@ void AnimationTimelineEdit::_notification(int p_what) {
max_digit_width = MAX(digit_width, max_digit_width);
}
const int max_sc = int(Math::ceil(zoomw / scale));
- const int max_sc_width = String::num(max_sc).length() * max_digit_width;
+ const int max_sc_width = String::num(max_sc).length() * Math::ceil(max_digit_width);
+
+ const int min_margin = MAX(text_secondary_margin, text_primary_margin);
while (!step_found) {
int min = max_sc_width;
if (decimals > 0) {
- min += period_width + max_digit_width * decimals;
+ min += Math::ceil(period_width + max_digit_width * decimals);
}
+ min += (min_margin * 2);
+
static const int _multp[3] = { 1, 2, 5 };
for (int i = 0; i < 3; i++) {
step = (_multp[i] * dec);
@@ -1603,10 +1620,11 @@ void AnimationTimelineEdit::_notification(int p_what) {
int sc = int(Math::floor(pos * SC_ADJ));
int prev_sc = int(Math::floor(prev * SC_ADJ));
- bool sub = (sc % SC_ADJ);
if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) {
int scd = sc < 0 ? prev_sc : sc;
+ bool sub = (((scd - (scd % step)) % (dec * 10)) != 0);
+
int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin;
int line_width = sub ? v_line_secondary_width : v_line_primary_width;
Color line_color = sub ? v_line_secondary_color : v_line_primary_color;
@@ -3281,6 +3299,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Update Mode"));
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
@@ -3295,6 +3318,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
} break;
@@ -3305,6 +3333,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Loop Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
undo_redo->commit_action();
queue_redraw();
@@ -3582,7 +3615,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
_update_step_spinbox();
step->set_block_signals(false);
step->set_read_only(false);
- snap->set_disabled(false);
+ snap_keys->set_disabled(false);
+ snap_timeline->set_disabled(false);
snap_mode->set_disabled(false);
auto_fit->set_disabled(false);
auto_fit_bezier->set_disabled(false);
@@ -3603,7 +3637,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
step->set_value(0);
step->set_block_signals(false);
step->set_read_only(true);
- snap->set_disabled(true);
+ snap_keys->set_disabled(true);
+ snap_timeline->set_disabled(true);
snap_mode->set_disabled(true);
bezier_edit_icon->set_disabled(true);
auto_fit->set_disabled(true);
@@ -3661,7 +3696,7 @@ void AnimationTrackEditor::update_keying() {
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) {
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
- keying_enabled = Object::cast_to<Node>(obj) != nullptr;
+ keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr;
}
if (keying_enabled == keying) {
@@ -4078,19 +4113,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant
_query_insert(id);
}
-void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
+void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) {
ERR_FAIL_NULL(root);
// Let's build a node path.
- Node *node = p_node;
- String path = root->get_path_to(node, true);
+ String path = root->get_path_to(p_node, true);
+
+ Variant value = p_node->get(p_property);
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
+ if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
+ if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
- _insert_animation_key(path, p_value);
+ _insert_animation_key(path, value);
return;
}
@@ -4118,26 +4154,26 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = i;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
} else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
+ Variant actual_value;
String track_path = animation->track_get_path(i);
if (track_path == np) {
- value = p_value; // All good.
+ actual_value = value; // All good.
} else {
int sep = track_path.rfind(":");
if (sep != -1) {
String base_path = track_path.substr(0, sep);
if (base_path == np) {
String value_name = track_path.substr(sep + 1);
- value = p_value.get(value_name);
+ actual_value = value.get(value_name);
} else {
continue;
}
@@ -4149,10 +4185,10 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = animation->track_get_path(i);
id.track_idx = i;
- id.value = value;
+ id.value = actual_value;
id.type = Animation::TYPE_BEZIER;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
@@ -4165,105 +4201,41 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = -1;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
}
-void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
+void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) {
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
ERR_FAIL_NULL(root);
ERR_FAIL_COND(history->get_path_size() == 0);
Object *obj = ObjectDB::get_instance(history->get_path_object(0));
- ERR_FAIL_NULL(Object::cast_to<Node>(obj));
-
- // Let's build a node path.
- Node *node = Object::cast_to<Node>(obj);
- String path = root->get_path_to(node, true);
-
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
- EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
- return;
- }
- _insert_animation_key(path, p_value);
- return;
- }
-
- for (int i = 1; i < history->get_path_size(); i++) {
- String prop = history->get_path_property(i);
- ERR_FAIL_COND(prop.is_empty());
- path += ":" + prop;
- }
-
- path += ":" + p_property;
-
- NodePath np = path;
-
- // Locate track.
-
- bool inserted = false;
- make_insert_queue();
- for (int i = 0; i < animation->get_track_count(); i++) {
- if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
- if (animation->track_get_path(i) != np) {
- continue;
- }
+ Ref<MultiNodeEdit> multi_node_edit(obj);
+ if (multi_node_edit.is_valid()) {
+ Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
+ ERR_FAIL_NULL(edited_scene);
- InsertData id;
- id.path = np;
- id.track_idx = i;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
- } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
- if (animation->track_get_path(i) == np) {
- value = p_value; // All good.
- } else {
- String tpath = animation->track_get_path(i);
- int index = tpath.rfind(":");
- if (NodePath(tpath.substr(0, index + 1)) == np) {
- String subindex = tpath.substr(index + 1, tpath.length() - index);
- value = p_value.get(subindex);
- } else {
- continue;
- }
- }
+ make_insert_queue();
- InsertData id;
- id.path = animation->track_get_path(i);
- id.track_idx = i;
- id.value = value;
- id.type = Animation::TYPE_BEZIER;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
+ for (int i = 0; i < multi_node_edit->get_node_count(); ++i) {
+ Node *node = edited_scene->get_node(multi_node_edit->get_node(i));
+ insert_node_value_key(node, p_property, false, p_advance);
}
- }
- commit_insert_queue();
- if (!inserted) {
- InsertData id;
- id.path = np;
- id.track_idx = -1;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
+ commit_insert_queue();
+ } else {
+ Node *node = Object::cast_to<Node>(obj);
+ ERR_FAIL_NULL(node);
+
+ make_insert_queue();
+ insert_node_value_key(node, p_property, false, p_advance);
+ commit_insert_queue();
}
}
@@ -4585,8 +4557,16 @@ bool AnimationTrackEditor::is_key_clipboard_active() const {
return key_clipboard.keys.size();
}
-bool AnimationTrackEditor::is_snap_enabled() const {
- return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+bool AnimationTrackEditor::is_snap_timeline_enabled() const {
+ return snap_timeline->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_snap_keys_enabled() const {
+ return snap_keys->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_bezier_editor_active() const {
+ return bezier_edit->is_visible();
}
bool AnimationTrackEditor::can_add_reset_key() const {
@@ -4922,7 +4902,8 @@ void AnimationTrackEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom")));
bezier_edit_icon->set_icon(get_editor_theme_icon(SNAME("EditBezier")));
- snap->set_icon(get_editor_theme_icon(SNAME("Snap")));
+ snap_timeline->set_icon(get_editor_theme_icon(SNAME("SnapTimeline")));
+ snap_keys->set_icon(get_editor_theme_icon(SNAME("SnapKeys")));
view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
@@ -5243,7 +5224,7 @@ int AnimationTrackEditor::_get_track_selected() {
void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
ERR_FAIL_INDEX(p_track, animation->get_track_count());
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
p_ofs = snap_time(p_ofs);
}
while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid.
@@ -5650,6 +5631,14 @@ void AnimationTrackEditor::_move_selection_commit() {
moving_selection = false;
undo_redo->add_do_method(this, "_redraw_tracks");
undo_redo->add_undo_method(this, "_redraw_tracks");
+
+ // Update key frame.
+ AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
+ if (ape) {
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
+ }
+
undo_redo->commit_action();
}
@@ -5858,7 +5847,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6006,7 +5995,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6887,8 +6876,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
_redraw_tracks();
_update_key_edit();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
} break;
case EDIT_CLEAN_UP_ANIMATION: {
@@ -7030,8 +7019,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
_update_tracks();
}
@@ -7079,12 +7068,15 @@ void AnimationTrackEditor::_update_snap_unit() {
if (timeline->is_using_fps()) {
snap_unit = 1.0 / step->get_value();
} else {
- snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor.
+ double integer;
+ double fraction = Math::modf(step->get_value(), &integer);
+ fraction = 1.0 / Math::round(1.0 / fraction);
+ snap_unit = integer + fraction;
}
}
float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
- if (is_snap_enabled()) {
+ if (is_snap_keys_enabled()) {
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
// Use more precise snapping when holding Shift.
snap_unit *= 0.25;
@@ -7338,13 +7330,21 @@ AnimationTrackEditor::AnimationTrackEditor() {
bottom_hb->add_child(view_group);
bottom_hb->add_child(memnew(VSeparator));
- snap = memnew(Button);
- snap->set_flat(true);
- snap->set_text(TTR("Snap:") + " ");
- bottom_hb->add_child(snap);
- snap->set_disabled(true);
- snap->set_toggle_mode(true);
- snap->set_pressed(true);
+ snap_timeline = memnew(Button);
+ snap_timeline->set_flat(true);
+ bottom_hb->add_child(snap_timeline);
+ snap_timeline->set_disabled(true);
+ snap_timeline->set_toggle_mode(true);
+ snap_timeline->set_pressed(false);
+ snap_timeline->set_tooltip_text(TTR("Apply snapping to timeline cursor."));
+
+ snap_keys = memnew(Button);
+ snap_keys->set_flat(true);
+ bottom_hb->add_child(snap_keys);
+ snap_keys->set_disabled(true);
+ snap_keys->set_toggle_mode(true);
+ snap_keys->set_pressed(true);
+ snap_keys->set_tooltip_text(TTR("Apply snapping to selected key(s)."));
step = memnew(EditorSpinSlider);
step->set_min(0);
@@ -7713,6 +7713,8 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() {
undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time);
undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time);
}
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 59ee6535ac..8a263d7d20 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -408,7 +408,8 @@ class AnimationTrackEditor : public VBoxContainer {
HSlider *zoom = nullptr;
EditorSpinSlider *step = nullptr;
TextureRect *zoom_icon = nullptr;
- Button *snap = nullptr;
+ Button *snap_keys = nullptr;
+ Button *snap_timeline = nullptr;
Button *bezier_edit_icon = nullptr;
OptionButton *snap_mode = nullptr;
Button *auto_fit = nullptr;
@@ -713,8 +714,8 @@ public:
void cleanup();
void set_anim_pos(float p_pos);
- void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
- void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
+ void insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists = false, bool p_advance = false);
+ void insert_value_key(const String &p_property, bool p_advance);
void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value);
bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type);
void make_insert_queue();
@@ -728,7 +729,9 @@ public:
bool is_selection_active() const;
bool is_key_clipboard_active() const;
bool is_moving_selection() const;
- bool is_snap_enabled() const;
+ bool is_snap_timeline_enabled() const;
+ bool is_snap_keys_enabled() const;
+ bool is_bezier_editor_active() const;
bool can_add_reset_key() const;
float get_moving_selection_offset() const;
float snap_time(float p_value, bool p_relative = false);
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index f01301384a..b4265f9fc0 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -303,7 +303,7 @@ void EditorDebuggerNode::stop(bool p_force) {
});
_break_state_changed();
breakpoints.clear();
- EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::REMOTE_HISTORY, false);
set_process(false);
}
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index f8ffce0c83..c4d7899b2d 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -124,6 +124,7 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou
item_menu->clear();
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE);
item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH);
+ item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE);
item_menu->set_position(get_screen_position() + get_local_mouse_position());
item_menu->reset_size();
item_menu->popup();
@@ -359,6 +360,21 @@ void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
}
DisplayServer::get_singleton()->clipboard_set(text);
} break;
+ case ITEM_MENU_EXPAND_COLLAPSE: {
+ TreeItem *s_item = get_selected();
+
+ if (!s_item) {
+ s_item = get_root();
+ if (!s_item) {
+ break;
+ }
+ }
+
+ bool collapsed = s_item->is_any_collapsed();
+ s_item->set_collapsed_recursive(!collapsed);
+
+ ensure_cursor_is_visible();
+ }
}
}
diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h
index dbffb0f219..705df17baf 100644
--- a/editor/debugger/editor_debugger_tree.h
+++ b/editor/debugger/editor_debugger_tree.h
@@ -43,6 +43,7 @@ private:
enum ItemMenu {
ITEM_MENU_SAVE_REMOTE_NODE,
ITEM_MENU_COPY_NODE_PATH,
+ ITEM_MENU_EXPAND_COLLAPSE,
};
ObjectID inspected_object_id;
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 3b337997e0..c076c99cd3 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -1262,7 +1262,7 @@ void EditorAudioBuses::_load_default_layout() {
file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
@@ -1278,7 +1278,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
} else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
@@ -1298,7 +1298,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
edited_path = p_string;
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
}
@@ -1397,7 +1397,7 @@ void EditorAudioBuses::open_layout(const String &p_path) {
file->set_text(p_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp
index 80c4c49c87..e5caa6a352 100644
--- a/editor/editor_data.cpp
+++ b/editor/editor_data.cpp
@@ -36,11 +36,14 @@
#include "core/io/image_loader.h"
#include "core/io/resource_loader.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/multi_node_edit.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
+#include "scene/gui/popup_menu.h"
#include "scene/resources/packed_scene.h"
void EditorSelectionHistory::cleanup_history() {
@@ -519,6 +522,138 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_
return plugin == nullptr ? nullptr : *plugin;
}
+void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ContextMenu cm;
+ cm.p_slot = p_slot;
+ cm.plugin = p_plugin;
+ p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS;
+ context_menu_plugins.push_back(cm);
+}
+
+void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ for (int i = context_menu_plugins.size() - 1; i > -1; i--) {
+ if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) {
+ context_menu_plugins.remove_at(i);
+ }
+ }
+}
+
+int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) {
+ for (ContextMenu &cm : context_menu_plugins) {
+ if (cm.p_slot != p_slot) {
+ continue;
+ }
+ HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts;
+ int shortcut_idx = 0;
+ for (KeyValue<Ref<Shortcut>, Callable> &E : cms) {
+ const Ref<Shortcut> &p_shortcut = E.key;
+ if (p_shortcut->matches_event(p_event)) {
+ return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx;
+ }
+ shortcut_idx++;
+ }
+ }
+ return 0;
+}
+
+void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) {
+ bool add_separator = false;
+
+ for (ContextMenu &cm : context_menu_plugins) {
+ if (cm.p_slot != p_slot) {
+ continue;
+ }
+ cm.plugin->clear_context_menu_items();
+ cm.plugin->add_options(p_paths);
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items;
+ if (items.size() > 0 && !add_separator) {
+ add_separator = true;
+ p_popup->add_separator();
+ }
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+
+ if (item.icon.is_valid()) {
+ p_popup->add_icon_item(item.icon, item.item_name, item.idx);
+ const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
+ p_popup->set_item_icon_max_width(-1, icon_size);
+ } else {
+ p_popup->add_item(item.item_name, item.idx);
+ }
+ if (item.shortcut.is_valid()) {
+ p_popup->set_item_shortcut(-1, item.shortcut, true);
+ }
+ }
+ }
+}
+
+template <typename T>
+void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) {
+ Variant arg = p_arg;
+ Variant *argptr = &arg;
+
+ for (int i = 0; i < context_menu_plugins.size(); i++) {
+ if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) {
+ continue;
+ }
+ Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin;
+
+ // Shortcut callback.
+ int shortcut_idx = 0;
+ int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx;
+ for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) {
+ if (shortcut_base_idx + shortcut_idx == p_option) {
+ const Callable &callable = E.value;
+ Callable::CallError ce;
+ Variant result;
+ callable.callp((const Variant **)&argptr, 1, result, ce);
+ }
+ shortcut_idx++;
+ }
+ if (p_option < shortcut_base_idx + shortcut_idx) {
+ return;
+ }
+
+ HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items;
+ for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) {
+ EditorContextMenuPlugin::ContextMenuItem &item = E.value;
+
+ if (p_option != item.idx || !item.callable.is_valid()) {
+ continue;
+ }
+
+ Callable::CallError ce;
+ Variant result;
+ item.callable.callp((const Variant **)&argptr, 1, result, ce);
+
+ if (ce.error != Callable::CallError::CALL_OK) {
+ String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce);
+ ERR_PRINT("Error calling function from context menu: " + err);
+ }
+ }
+ }
+ // Invoke submenu items.
+ if (p_slot == CONTEXT_SLOT_FILESYSTEM) {
+ invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg);
+ }
+}
+
+void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) {
+ invoke_plugin_callback(p_slot, p_option, p_selected);
+}
+
+void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) {
+ TypedArray<Node> nodes;
+ for (Node *selected : p_selected) {
+ nodes.append(selected);
+ }
+ invoke_plugin_callback(p_slot, p_option, nodes);
+}
+
+void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) {
+ invoke_plugin_callback(p_slot, p_option, p_script);
+}
+
void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) {
ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object.");
CustomType ct;
diff --git a/editor/editor_data.h b/editor/editor_data.h
index 524c93807b..754d44c479 100644
--- a/editor/editor_data.h
+++ b/editor/editor_data.h
@@ -37,6 +37,8 @@
class ConfigFile;
class EditorPlugin;
class EditorUndoRedoManager;
+class EditorContextMenuPlugin;
+class PopupMenu;
/**
* Stores the history of objects which have been selected for editing in the Editor & the Inspector.
@@ -123,6 +125,22 @@ public:
uint64_t last_checked_version = 0;
};
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
+ };
+
+ inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000;
+
+ struct ContextMenu {
+ int p_slot;
+ Ref<EditorContextMenuPlugin> plugin;
+ };
+
+ Vector<ContextMenu> context_menu_plugins;
+
private:
Vector<EditorPlugin *> editor_plugins;
HashMap<StringName, EditorPlugin *> extension_editor_plugins;
@@ -177,6 +195,18 @@ public:
bool has_extension_editor_plugin(const StringName &p_class_name);
EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name);
+ // Context menu plugin.
+ void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event);
+
+ void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths);
+ void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected);
+ void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected);
+ void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script);
+ template <typename T>
+ void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg);
+
void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value)
void remove_undo_redo_inspector_hook_callback(Callable p_callable);
const Vector<Callable> get_undo_redo_inspector_hook_callback();
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 474a45cf2b..3d3caf59eb 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -33,8 +33,6 @@
#include "core/config/project_settings.h"
#include "core/extension/gdextension_manager.h"
#include "core/io/file_access.h"
-#include "core/io/resource_importer.h"
-#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/object/worker_thread_pool.h"
#include "core/os/os.h"
@@ -243,18 +241,27 @@ void EditorFileSystem::_first_scan_filesystem() {
first_scan_root_dir = memnew(ScannedDirectory);
first_scan_root_dir->full_path = "res://";
HashSet<String> existing_class_names;
+ HashSet<String> extensions;
ep.step(TTR("Scanning file structure..."), 0, true);
nb_files_total = _scan_new_dir(first_scan_root_dir, d);
// 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);
+ _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
+ // Processing extensions to add new extensions or remove invalid ones.
+ // Important to do it in the first scan so custom types, new class names, custom importers, etc...
+ // from extensions are ready to go before plugins, autoloads and resources validation/importation.
+ // At this point, a restart of the editor should not be needed so we don't use the return value.
+ ep.step(TTR("Verifying GDExtensions..."), 2, true);
+ GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
+
// Now that all the global class names should be loaded, create autoloads and plugins.
// This is done after loading the global class names because autoloads and plugins can use
// global class names.
@@ -267,9 +274,9 @@ 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) {
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, 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);
+ _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions);
}
for (const String &scan_file : p_scan_dir->files) {
@@ -285,6 +292,8 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
if (!script_class_name.is_empty()) {
p_existing_class_names.insert(script_class_name);
}
+ } else if (type == SNAME("GDExtension")) {
+ p_extensions.insert(path);
}
}
}
@@ -892,6 +901,7 @@ void EditorFileSystem::scan() {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
} else {
@@ -1502,6 +1512,7 @@ void EditorFileSystem::_notification(int p_what) {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
if (changed) {
emit_signal(SNAME("filesystem_changed"));
}
@@ -1524,6 +1535,7 @@ void EditorFileSystem::_notification(int p_what) {
// Set first_scan to false before the signals so the function doing_first_scan can return false
// in editor_node to start the export if needed.
first_scan = false;
+ ResourceImporter::load_on_startup = nullptr;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
}
@@ -1855,25 +1867,26 @@ void EditorFileSystem::_update_script_classes() {
return;
}
- update_script_mutex.lock();
+ {
+ MutexLock update_script_lock(update_script_mutex);
- EditorProgress *ep = nullptr;
- if (update_script_paths.size() > 1) {
- ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
- }
+ EditorProgress *ep = nullptr;
+ if (update_script_paths.size() > 1) {
+ ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
+ }
- int step_count = 0;
- for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
- _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
- if (ep) {
- ep->step(E.value.script_class_name, step_count++, false);
+ int step_count = 0;
+ for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
+ _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
+ if (ep) {
+ ep->step(E.value.script_class_name, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
+ memdelete_notnull(ep);
- update_script_paths.clear();
- update_script_mutex.unlock();
+ update_script_paths.clear();
+ }
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
@@ -1894,7 +1907,7 @@ void EditorFileSystem::_update_script_documentation() {
return;
}
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
EditorProgress *ep = nullptr;
if (update_script_paths_documentation.size() > 1) {
@@ -1933,7 +1946,6 @@ void EditorFileSystem::_update_script_documentation() {
memdelete_notnull(ep);
update_script_paths_documentation.clear();
- update_script_mutex.unlock();
}
void EditorFileSystem::_process_update_pending() {
@@ -1945,7 +1957,7 @@ void EditorFileSystem::_process_update_pending() {
}
void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
ScriptInfo si;
si.type = p_type;
@@ -1955,8 +1967,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St
update_script_paths.insert(p_path, si);
update_script_paths_documentation.insert(p_path);
-
- update_script_mutex.unlock();
}
void EditorFileSystem::_update_scene_groups() {
@@ -1970,31 +1980,32 @@ void EditorFileSystem::_update_scene_groups() {
}
int step_count = 0;
- update_scene_mutex.lock();
- for (const String &path : update_scene_paths) {
- ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
+ {
+ MutexLock update_scene_lock(update_scene_mutex);
+ for (const String &path : update_scene_paths) {
+ ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
- int index = -1;
- EditorFileSystemDirectory *efd = find_file(path, &index);
+ int index = -1;
+ EditorFileSystemDirectory *efd = find_file(path, &index);
- if (!efd || index < 0) {
- // The file was removed.
- continue;
- }
+ if (!efd || index < 0) {
+ // The file was removed.
+ continue;
+ }
- const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
- if (!scene_groups.is_empty()) {
- ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
- }
+ const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
+ if (!scene_groups.is_empty()) {
+ ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
+ }
- if (ep) {
- ep->step(efd->files[index]->file, step_count++, false);
+ if (ep) {
+ ep->step(efd->files[index]->file, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
- update_scene_paths.clear();
- update_scene_mutex.unlock();
+ memdelete_notnull(ep);
+ update_scene_paths.clear();
+ }
ProjectSettings::get_singleton()->save_scene_groups_cache();
}
@@ -2009,9 +2020,8 @@ void EditorFileSystem::_update_pending_scene_groups() {
}
void EditorFileSystem::_queue_update_scene_groups(const String &p_path) {
- update_scene_mutex.lock();
+ MutexLock update_scene_lock(update_scene_mutex);
update_scene_paths.insert(p_path);
- update_scene_mutex.unlock();
}
void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) {
@@ -2401,14 +2411,16 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
return err;
}
-Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) {
+Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters, bool p_update_file_system) {
print_verbose(vformat("EditorFileSystem: Importing file: %s", p_file));
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
EditorFileSystemDirectory *fs = nullptr;
int cpos = -1;
- bool found = _find_file(p_file, &fs, cpos);
- ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
+ if (p_update_file_system) {
+ bool found = _find_file(p_file, &fs, cpos);
+ ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
+ }
//try to obtain existing params
@@ -2462,11 +2474,13 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
if (importer_name == "keep" || importer_name == "skip") {
//keep files, do nothing.
- fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
- fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
- fs->files[cpos]->deps.clear();
- fs->files[cpos]->type = "";
- fs->files[cpos]->import_valid = false;
+ if (p_update_file_system) {
+ fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
+ fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->deps.clear();
+ fs->files[cpos]->type = "";
+ fs->files[cpos]->import_valid = false;
+ }
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
return OK;
}
@@ -2626,16 +2640,18 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
}
}
- // Update cpos, newly created files could've changed the index of the reimported p_file.
- _find_file(p_file, &fs, cpos);
+ if (p_update_file_system) {
+ // Update cpos, newly created files could've changed the index of the reimported p_file.
+ _find_file(p_file, &fs, cpos);
- // Update modified times, to avoid reimport.
- fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
- fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
- fs->files[cpos]->deps = _get_dependencies(p_file);
- fs->files[cpos]->type = importer->get_resource_type();
- fs->files[cpos]->uid = uid;
- fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
+ // Update modified times, to avoid reimport.
+ fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
+ fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->deps = _get_dependencies(p_file);
+ fs->files[cpos]->type = importer->get_resource_type();
+ fs->files[cpos]->uid = uid;
+ fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file);
+ }
if (ResourceUID::get_singleton()->has_id(uid)) {
ResourceUID::get_singleton()->set_id(uid, p_file);
@@ -2895,6 +2911,33 @@ Error EditorFileSystem::_resource_import(const String &p_path) {
return OK;
}
+Ref<Resource> EditorFileSystem::_load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode) {
+ ERR_FAIL_NULL_V(p_importer, Ref<Resource>());
+
+ if (!FileAccess::exists(p_path)) {
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. The file doesn't seem to exist.", p_path));
+ }
+
+ Ref<Resource> res;
+ bool can_retry = true;
+ bool retry = true;
+ while (retry) {
+ retry = false;
+
+ res = p_importer->load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, can_retry);
+
+ if (res.is_null() && can_retry) {
+ can_retry = false;
+ Error err = singleton->_reimport_file(p_path, HashMap<StringName, Variant>(), "", nullptr, false);
+ if (err == OK) {
+ retry = true;
+ }
+ }
+ }
+
+ return res;
+}
+
bool EditorFileSystem::_should_skip_directory(const String &p_path) {
String project_data_path = ProjectSettings::get_singleton()->get_project_data_path();
if (p_path == project_data_path || p_path.begins_with(project_data_path + "/")) {
@@ -3016,57 +3059,7 @@ bool EditorFileSystem::_scan_extensions() {
_scan_extensions_dir(d, extensions);
- //verify against loaded extensions
-
- Vector<String> extensions_added;
- Vector<String> extensions_removed;
-
- for (const String &E : extensions) {
- if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) {
- extensions_added.push_back(E);
- }
- }
-
- Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions();
- for (int i = 0; i < loaded_extensions.size(); i++) {
- if (!extensions.has(loaded_extensions[i])) {
- // The extension may not have a .gdextension file.
- if (!FileAccess::exists(loaded_extensions[i])) {
- extensions_removed.push_back(loaded_extensions[i]);
- }
- }
- }
-
- String extension_list_config_file = GDExtension::get_extension_list_config_file();
- if (extensions.size()) {
- if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed
- Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
- for (const String &E : extensions) {
- f->store_line(E);
- }
- }
- } else {
- if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- da->remove(extension_list_config_file);
- }
- }
-
- bool needs_restart = false;
- for (int i = 0; i < extensions_added.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
- for (int i = 0; i < extensions_removed.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
-
- return needs_restart;
+ return GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
}
void EditorFileSystem::_bind_methods() {
@@ -3142,6 +3135,10 @@ EditorFileSystem::EditorFileSystem() {
scan_total = 0;
ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path);
+
+ // Set the callback method that the ResourceFormatImporter will use
+ // if resources are loaded during the first scan.
+ ResourceImporter::load_on_startup = _load_resource_on_startup;
}
EditorFileSystem::~EditorFileSystem() {
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 1bc24416eb..2ceb3fe4a5 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -32,6 +32,8 @@
#define EDITOR_FILE_SYSTEM_H
#include "core/io/dir_access.h"
+#include "core/io/resource_importer.h"
+#include "core/io/resource_loader.h"
#include "core/os/thread.h"
#include "core/os/thread_safe.h"
#include "core/templates/hash_set.h"
@@ -187,7 +189,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);
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
HashSet<String> late_update_files;
@@ -252,7 +254,7 @@ class EditorFileSystem : public Node {
void _update_extensions();
- Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr);
+ Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr, bool p_update_file_system = true);
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
bool _test_for_reimport(const String &p_path, bool p_only_imported_files);
@@ -296,6 +298,7 @@ class EditorFileSystem : public Node {
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
static Error _resource_import(const String &p_path);
+ static Ref<Resource> _load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode);
bool using_fat32_or_exfat; // Workaround for projects in FAT32 or exFAT filesystem (pendrives, most of the time)
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index ff5bc6ba87..eb97337b37 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -134,18 +134,38 @@ void EditorHelpSearch::_native_action_cb(const String &p_item_string) {
}
void EditorHelpSearch::_update_results() {
- String term = search_box->get_text();
+ const String term = search_box->get_text().strip_edges();
int search_flags = filter_combo->get_selected_id();
- if (case_sensitive_button->is_pressed()) {
- search_flags |= SEARCH_CASE_SENSITIVE;
- }
- if (hierarchy_button->is_pressed()) {
- search_flags |= SEARCH_SHOW_HIERARCHY;
- }
- search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
- set_process(true);
+ // Process separately if term is not short, or is "@" for annotations.
+ if (term.length() > 1 || term == "@") {
+ case_sensitive_button->set_disabled(false);
+ hierarchy_button->set_disabled(false);
+
+ if (case_sensitive_button->is_pressed()) {
+ search_flags |= SEARCH_CASE_SENSITIVE;
+ }
+ if (hierarchy_button->is_pressed()) {
+ search_flags |= SEARCH_SHOW_HIERARCHY;
+ }
+
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
+
+ // Clear old search flags to force rebuild on short term.
+ old_search_flags = 0;
+ set_process(true);
+ } else {
+ // Disable hierarchy and case sensitive options, not used for short searches.
+ case_sensitive_button->set_disabled(true);
+ hierarchy_button->set_disabled(true);
+
+ // Always show hierarchy for short searches.
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags | SEARCH_SHOW_HIERARCHY)));
+
+ old_search_flags = search_flags;
+ set_process(true);
+ }
}
void EditorHelpSearch::_search_box_gui_input(const Ref<InputEvent> &p_event) {
@@ -205,6 +225,8 @@ void EditorHelpSearch::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
tree_cache.clear();
+ results_tree->get_vscroll_bar()->set_value(0);
+ search = Ref<Runner>();
callable_mp(results_tree, &Tree::clear).call_deferred(); // Wait for the Tree's mouse event propagation.
get_ok_button()->set_disabled(true);
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "search_help", Rect2(get_position(), get_size()));
@@ -278,6 +300,7 @@ void EditorHelpSearch::popup_dialog(const String &p_term) {
popup_centered_ratio(0.5F);
}
+ old_search_flags = 0;
if (p_term.is_empty()) {
search_box->clear();
} else {
@@ -401,6 +424,237 @@ bool EditorHelpSearch::Runner::_is_class_disabled_by_feature_profile(const Strin
return false;
}
+bool EditorHelpSearch::Runner::_fill() {
+ bool phase_done = false;
+ switch (phase) {
+ case PHASE_MATCH_CLASSES_INIT:
+ phase_done = _phase_fill_classes_init();
+ break;
+ case PHASE_MATCH_CLASSES:
+ phase_done = _phase_fill_classes();
+ break;
+ case PHASE_CLASS_ITEMS_INIT:
+ case PHASE_CLASS_ITEMS:
+ phase_done = true;
+ break;
+ case PHASE_MEMBER_ITEMS_INIT:
+ phase_done = _phase_fill_member_items_init();
+ break;
+ case PHASE_MEMBER_ITEMS:
+ phase_done = _phase_fill_member_items();
+ break;
+ case PHASE_SELECT_MATCH:
+ phase_done = _phase_select_match();
+ break;
+ case PHASE_MAX:
+ return true;
+ default:
+ WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
+ return true;
+ }
+
+ if (phase_done) {
+ phase++;
+ }
+ return false;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes_init() {
+ // Initialize fill.
+ iterator_stack.clear();
+ matched_classes.clear();
+ matched_item = nullptr;
+ match_highest_score = 0;
+
+ // Initialize stack of iterators to fill, in reverse.
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[""].back());
+
+ return true;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes() {
+ if (iterator_stack.is_empty()) {
+ return true;
+ }
+
+ if (iterator_stack[iterator_stack.size() - 1]) {
+ DocData::ClassDoc *class_doc = EditorHelp::get_doc_data()->class_list.getptr(iterator_stack[iterator_stack.size() - 1]->get());
+
+ // Decrement stack.
+ iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->prev();
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ if (!class_doc || class_doc->name.is_empty()) {
+ return false;
+ }
+
+ // If class matches the flags, add it to the matched stack.
+ const bool class_matched =
+ (search_flags & SEARCH_CLASSES) ||
+ ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) ||
+ ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) ||
+ ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) ||
+ ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) ||
+ ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) ||
+ ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) ||
+ ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) ||
+ ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty());
+
+ if (class_matched) {
+ if (term.is_empty() || class_doc->name.containsn(term)) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, String()));
+ } else if (String keyword = _match_keywords(term, class_doc->keywords); !keyword.is_empty()) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, keyword));
+ }
+ }
+
+ // Add inheriting classes, in reverse.
+ if (class_doc && EditorHelp::get_doc_data()->inheriting.has(class_doc->name)) {
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[class_doc->name].back());
+ }
+
+ return false;
+ }
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ return iterator_stack.is_empty();
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items_init() {
+ // Prepare tree.
+ class_items.clear();
+ _populate_cache();
+
+ return true;
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_text) {
+ const String item_meta = "class_" + p_metatype + ":" + p_class;
+
+ TreeItem *item = nullptr;
+ if (_find_or_create_item(p_parent, item_meta, item)) {
+ item->set_icon(0, ui_service->get_editor_theme_icon(p_icon));
+ item->set_text(0, p_text);
+ item->set_metadata(0, item_meta);
+ }
+ item->set_collapsed(true);
+
+ return item;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items() {
+ if (matched_classes.is_empty()) {
+ return true;
+ }
+
+ // Pop working item from stack.
+ Pair<DocData::ClassDoc *, String> match = matched_classes[matched_classes.size() - 1];
+ DocData::ClassDoc *class_doc = match.first;
+ const String &keyword = match.second;
+ matched_classes.resize(matched_classes.size() - 1);
+
+ if (class_doc) {
+ TreeItem *item = _create_class_hierarchy(class_doc, keyword, !(search_flags & SEARCH_CLASSES));
+
+ // If the class has no inheriting classes, fold its item.
+ item->set_collapsed(!item->get_first_child());
+
+ if (search_flags & SEARCH_CLASSES) {
+ item->clear_custom_color(0);
+ item->clear_custom_color(1);
+ } else {
+ item->set_custom_color(0, disabled_color);
+ item->set_custom_color(1, disabled_color);
+ }
+
+ // Create common header if required.
+ const bool search_all = (search_flags & SEARCH_ALL) == SEARCH_ALL;
+
+ if ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstructor"), TTRC("Constructors"), "constructors");
+ }
+ for (const DocData::MethodDoc &constructor_doc : class_doc->constructors) {
+ _create_constructor_item(parent_item, class_doc, &constructor_doc);
+ }
+ }
+ if ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberMethod"), TTRC("Methods"), "methods");
+ }
+ for (const DocData::MethodDoc &method_doc : class_doc->methods) {
+ _create_method_item(parent_item, class_doc, &method_doc);
+ }
+ }
+ if ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberOperator"), TTRC("Operators"), "operators");
+ }
+ for (const DocData::MethodDoc &operator_doc : class_doc->operators) {
+ _create_operator_item(parent_item, class_doc, &operator_doc);
+ }
+ }
+ if ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberSignal"), TTRC("Signals"), "signals");
+ }
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ _create_signal_item(parent_item, class_doc, &signal_doc);
+ }
+ }
+ if ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstant"), TTRC("Constants"), "constants");
+ }
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ _create_constant_item(parent_item, class_doc, &constant_doc);
+ }
+ }
+ if ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberProperty"), TTRC("Prtoperties"), "propertiess");
+ }
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ _create_property_item(parent_item, class_doc, &property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberTheme"), TTRC("Theme Properties"), "theme_items");
+ }
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ _create_theme_property_item(parent_item, class_doc, &theme_property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberAnnotation"), TTRC("Annotations"), "annotations");
+ }
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ _create_annotation_item(parent_item, class_doc, &annotation_doc);
+ }
+ }
+ }
+
+ return matched_classes.is_empty();
+}
+
bool EditorHelpSearch::Runner::_slice() {
bool phase_done = false;
switch (phase) {
@@ -430,7 +684,7 @@ bool EditorHelpSearch::Runner::_slice() {
default:
WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
return true;
- };
+ }
if (phase_done) {
phase++;
@@ -450,9 +704,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes_init() {
matched_item = nullptr;
match_highest_score = 0;
- terms = term.split_spaces();
- if (terms.is_empty()) {
- terms.append(term);
+ if (!term.is_empty()) {
+ terms = term.split_spaces();
+ if (terms.is_empty()) {
+ terms.append(term);
+ }
}
return true;
@@ -480,78 +736,71 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
// Match class name.
if (search_flags & SEARCH_CLASSES) {
- // If the search term is empty, add any classes which are not script docs or which don't start with
- // a double-quotation. This will ensure that only C++ classes and explicitly named classes will
- // be added.
- match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name);
+ match.name = _match_string(term, class_doc->name);
match.keyword = _match_keywords(term, class_doc->keywords);
}
- // Match members only if the term is long enough, to avoid slow performance from building a large tree.
- // Make an exception for annotations, since there are not that many of them.
- if (term.length() > 1 || term == "@") {
- if (search_flags & SEARCH_CONSTRUCTORS) {
- _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
- }
- if (search_flags & SEARCH_METHODS) {
- _match_method_name_and_push_back(class_doc->methods, &match.methods);
- }
- if (search_flags & SEARCH_OPERATORS) {
- _match_method_name_and_push_back(class_doc->operators, &match.operators);
- }
- if (search_flags & SEARCH_SIGNALS) {
- for (int i = 0; i < class_doc->signals.size(); i++) {
- MemberMatch<DocData::MethodDoc> signal;
- signal.name = _all_terms_in_name(class_doc->signals[i].name);
- signal.keyword = _match_keywords_in_all_terms(class_doc->signals[i].keywords);
- if (signal.name || !signal.keyword.is_empty()) {
- signal.doc = const_cast<DocData::MethodDoc *>(&class_doc->signals[i]);
- match.signals.push_back(signal);
- }
+ if (search_flags & SEARCH_CONSTRUCTORS) {
+ _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
+ }
+ if (search_flags & SEARCH_METHODS) {
+ _match_method_name_and_push_back(class_doc->methods, &match.methods);
+ }
+ if (search_flags & SEARCH_OPERATORS) {
+ _match_method_name_and_push_back(class_doc->operators, &match.operators);
+ }
+ if (search_flags & SEARCH_SIGNALS) {
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ MemberMatch<DocData::MethodDoc> signal;
+ signal.name = _all_terms_in_name(signal_doc.name);
+ signal.keyword = _match_keywords_in_all_terms(signal_doc.keywords);
+ if (signal.name || !signal.keyword.is_empty()) {
+ signal.doc = &signal_doc;
+ match.signals.push_back(signal);
}
}
- if (search_flags & SEARCH_CONSTANTS) {
- for (int i = 0; i < class_doc->constants.size(); i++) {
- MemberMatch<DocData::ConstantDoc> constant;
- constant.name = _all_terms_in_name(class_doc->constants[i].name);
- constant.keyword = _match_keywords_in_all_terms(class_doc->constants[i].keywords);
- if (constant.name || !constant.keyword.is_empty()) {
- constant.doc = const_cast<DocData::ConstantDoc *>(&class_doc->constants[i]);
- match.constants.push_back(constant);
- }
+ }
+ if (search_flags & SEARCH_CONSTANTS) {
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ MemberMatch<DocData::ConstantDoc> constant;
+ constant.name = _all_terms_in_name(constant_doc.name);
+ constant.keyword = _match_keywords_in_all_terms(constant_doc.keywords);
+ if (constant.name || !constant.keyword.is_empty()) {
+ constant.doc = &constant_doc;
+ match.constants.push_back(constant);
}
}
- if (search_flags & SEARCH_PROPERTIES) {
- for (int i = 0; i < class_doc->properties.size(); i++) {
- MemberMatch<DocData::PropertyDoc> property;
- property.name = _all_terms_in_name(class_doc->properties[i].name);
- property.keyword = _match_keywords_in_all_terms(class_doc->properties[i].keywords);
- if (property.name || !property.keyword.is_empty()) {
- property.doc = const_cast<DocData::PropertyDoc *>(&class_doc->properties[i]);
- match.properties.push_back(property);
- }
+ }
+ if (search_flags & SEARCH_PROPERTIES) {
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ MemberMatch<DocData::PropertyDoc> property;
+ property.name = _all_terms_in_name(property_doc.name);
+ property.keyword = _match_keywords_in_all_terms(property_doc.keywords);
+ if (property.name || !property.keyword.is_empty()) {
+ property.doc = &property_doc;
+ match.properties.push_back(property);
}
}
- if (search_flags & SEARCH_THEME_ITEMS) {
- for (int i = 0; i < class_doc->theme_properties.size(); i++) {
- MemberMatch<DocData::ThemeItemDoc> theme_property;
- theme_property.name = _all_terms_in_name(class_doc->theme_properties[i].name);
- theme_property.keyword = _match_keywords_in_all_terms(class_doc->theme_properties[i].keywords);
- if (theme_property.name || !theme_property.keyword.is_empty()) {
- theme_property.doc = const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i]);
- match.theme_properties.push_back(theme_property);
- }
+ }
+ if (search_flags & SEARCH_THEME_ITEMS) {
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ MemberMatch<DocData::ThemeItemDoc> theme_property;
+ theme_property.name = _all_terms_in_name(theme_property_doc.name);
+ theme_property.keyword = _match_keywords_in_all_terms(theme_property_doc.keywords);
+ if (theme_property.name || !theme_property.keyword.is_empty()) {
+ theme_property.doc = &theme_property_doc;
+ match.theme_properties.push_back(theme_property);
}
}
- if (search_flags & SEARCH_ANNOTATIONS) {
- for (int i = 0; i < class_doc->annotations.size(); i++) {
- MemberMatch<DocData::MethodDoc> annotation;
- annotation.name = _all_terms_in_name(class_doc->annotations[i].name);
- annotation.keyword = _match_keywords_in_all_terms(class_doc->annotations[i].keywords);
- if (annotation.name || !annotation.keyword.is_empty()) {
- annotation.doc = const_cast<DocData::MethodDoc *>(&class_doc->annotations[i]);
- match.annotations.push_back(annotation);
- }
+ }
+ if (search_flags & SEARCH_ANNOTATIONS) {
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ MemberMatch<DocData::MethodDoc> annotation;
+ annotation.name = _all_terms_in_name(annotation_doc.name);
+ annotation.keyword = _match_keywords_in_all_terms(annotation_doc.keywords);
+ if (annotation.name || !annotation.keyword.is_empty()) {
+ annotation.doc = &annotation_doc;
+ match.annotations.push_back(annotation);
}
}
}
@@ -564,9 +813,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
}
if (!iterator_stack.is_empty()) {
+ // Iterate on stack.
if (iterator_stack[iterator_stack.size() - 1]) {
iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->next();
}
+ // Drop last element of stack.
if (!iterator_stack[iterator_stack.size() - 1]) {
iterator_stack.resize(iterator_stack.size() - 1);
}
@@ -661,36 +912,32 @@ bool EditorHelpSearch::Runner::_phase_member_items() {
return false;
}
+ // Pick appropriate parent item if showing hierarchy, otherwise pick root.
TreeItem *parent_item = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item;
- bool constructor_created = false;
- for (int i = 0; i < match.methods.size(); i++) {
- String text = match.methods[i].doc->name;
- if (!constructor_created) {
- if (match.doc->name == match.methods[i].doc->name) {
- text += " " + TTR("(constructors)");
- constructor_created = true;
- }
- } else {
- if (match.doc->name == match.methods[i].doc->name) {
- continue;
- }
- }
- _create_method_item(parent_item, match.doc, text, match.methods[i]);
+
+ for (const MemberMatch<DocData::MethodDoc> &constructor_item : match.constructors) {
+ _create_constructor_item(parent_item, match.doc, constructor_item);
}
- for (int i = 0; i < match.signals.size(); i++) {
- _create_signal_item(parent_item, match.doc, match.signals[i]);
+ for (const MemberMatch<DocData::MethodDoc> &method_item : match.methods) {
+ _create_method_item(parent_item, match.doc, method_item);
}
- for (int i = 0; i < match.constants.size(); i++) {
- _create_constant_item(parent_item, match.doc, match.constants[i]);
+ for (const MemberMatch<DocData::MethodDoc> &operator_item : match.operators) {
+ _create_operator_item(parent_item, match.doc, operator_item);
}
- for (int i = 0; i < match.properties.size(); i++) {
- _create_property_item(parent_item, match.doc, match.properties[i]);
+ for (const MemberMatch<DocData::MethodDoc> &signal_item : match.signals) {
+ _create_signal_item(parent_item, match.doc, signal_item);
}
- for (int i = 0; i < match.theme_properties.size(); i++) {
- _create_theme_property_item(parent_item, match.doc, match.theme_properties[i]);
+ for (const MemberMatch<DocData::ConstantDoc> &constant_item : match.constants) {
+ _create_constant_item(parent_item, match.doc, constant_item);
}
- for (int i = 0; i < match.annotations.size(); i++) {
- _create_annotation_item(parent_item, match.doc, match.annotations[i]);
+ for (const MemberMatch<DocData::PropertyDoc> &property_item : match.properties) {
+ _create_property_item(parent_item, match.doc, property_item);
+ }
+ for (const MemberMatch<DocData::ThemeItemDoc> &theme_property_item : match.theme_properties) {
+ _create_theme_property_item(parent_item, match.doc, theme_property_item);
+ }
+ for (const MemberMatch<DocData::MethodDoc> &annotation_item : match.annotations) {
+ _create_annotation_item(parent_item, match.doc, annotation_item);
}
++iterator_match;
@@ -704,7 +951,7 @@ bool EditorHelpSearch::Runner::_phase_select_match() {
return true;
}
-void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
+void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
// Constructors, Methods, Operators...
for (int i = 0; i < p_methods.size(); i++) {
String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower();
@@ -765,12 +1012,12 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
return;
}
- float inverse_length = 1.f / float(p_text.length());
+ float inverse_length = 1.0f / float(p_text.length());
// Favor types where search term is a substring close to the start of the type.
float w = 0.5f;
int pos = p_text.findn(term);
- float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
+ float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.0f, 0.9f - w);
// Favor shorter items: they resemble the search term more.
w = 0.1f;
@@ -781,7 +1028,8 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
score *= 0.9f;
}
- if (match_highest_score == 0 || score > match_highest_score) {
+ // Replace current match if term is short as we are searching in reverse.
+ if (match_highest_score == 0 || score > match_highest_score || (score == match_highest_score && term.length() == 1)) {
matched_item = p_item;
match_highest_score = score;
}
@@ -820,6 +1068,29 @@ String EditorHelpSearch::Runner::_build_keywords_tooltip(const String &p_keyword
return tooltip.left(-2);
}
+TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray) {
+ if (p_class_doc->name.is_empty()) {
+ return nullptr;
+ }
+ if (TreeItem **found = class_items.getptr(p_class_doc->name)) {
+ return *found;
+ }
+
+ // Ensure parent nodes are created first.
+ TreeItem *parent_item = root_item;
+ if (!p_class_doc->inherits.is_empty()) {
+ if (class_items.has(p_class_doc->inherits)) {
+ parent_item = class_items[p_class_doc->inherits];
+ } else if (const DocData::ClassDoc *found = EditorHelp::get_doc_data()->class_list.getptr(p_class_doc->inherits)) {
+ parent_item = _create_class_hierarchy(found, String(), true);
+ }
+ }
+
+ TreeItem *class_item = _create_class_item(parent_item, p_class_doc, p_gray, p_matching_keyword);
+ class_items[p_class_doc->name] = class_item;
+ return class_item;
+}
+
TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_match) {
if (p_match.doc->name.is_empty()) {
return nullptr;
@@ -887,6 +1158,8 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental."));
}
}
+ // Cached item might be collapsed.
+ item->set_collapsed(false);
if (p_gray) {
item->set_custom_color(0, disabled_color);
@@ -902,7 +1175,9 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword)));
}
- _match_item(item, p_doc->name);
+ if (!term.is_empty()) {
+ _match_item(item, p_doc->name);
+ }
for (const String &keyword : p_doc->keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -910,44 +1185,73 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
return item;
}
-TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match) {
+TreeItem *EditorHelpSearch::Runner::_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = p_class_doc->name + "(";
+ String text = p_class_doc->name + "(";
+ for (int i = 0; i < p_match.doc->arguments.size(); i++) {
+ const DocData::ArgumentDoc &arg = p_match.doc->arguments[i];
+ tooltip += arg.type + " " + arg.name;
+ text += arg.type;
+ if (!arg.default_value.is_empty()) {
+ tooltip += " = " + arg.default_value;
+ }
+ if (i < p_match.doc->arguments.size() - 1) {
+ tooltip += ", ";
+ text += ", ";
+ }
+ }
+ tooltip += ")";
+ tooltip += _build_keywords_tooltip(p_match.doc->keywords);
+ text += ")";
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstructor"), p_match.doc->name, text, TTRC("Constructor"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_match.doc->name, p_text, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberMethod"), p_match.doc->name, p_match.doc->name, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
+ String text = p_match.doc->name;
+ if (!p_match.doc->arguments.is_empty()) {
+ text += "(" + p_match.doc->arguments[0].type + ")";
+ }
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberOperator"), p_match.doc->name, text, TTRC("Operator"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberSignal"), p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
// Hide the redundant leading @ symbol.
String text = p_match.doc->name.substr(1);
- return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberAnnotation"), p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match) {
String tooltip = p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstant"), p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->setter + "(value) setter";
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->getter + "() getter";
- tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberProperty"), p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberTheme"), p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword);
}
-TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
+TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name;
TreeItem *item = nullptr;
@@ -978,7 +1282,10 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
}
item->set_text(0, text);
- _match_item(item, p_name);
+ // Don't match member items for short searches.
+ if (term.length() > 1 || term == "@") {
+ _match_item(item, p_name);
+ }
for (const String &keyword : p_keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -989,9 +1296,17 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
bool EditorHelpSearch::Runner::work(uint64_t slot) {
// Return true when the search has been completed, otherwise false.
const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot;
- while (!_slice()) {
- if (OS::get_singleton()->get_ticks_usec() > until) {
- return false;
+ if (term.length() > 1 || term == "@") {
+ while (!_slice()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
+ }
+ } else {
+ while (!_fill()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
}
}
return true;
@@ -1001,7 +1316,7 @@ EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree,
ui_service(p_icon_service),
results_tree(p_results_tree),
tree_cache(p_tree_cache),
- term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.strip_edges().to_lower() : p_term.strip_edges()),
+ term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.to_lower() : p_term),
search_flags(p_search_flags),
disabled_color(ui_service->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))) {
}
diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h
index 58061dae4c..b8b3c26b41 100644
--- a/editor/editor_help_search.h
+++ b/editor/editor_help_search.h
@@ -63,6 +63,7 @@ class EditorHelpSearch : public ConfirmationDialog {
Tree *results_tree = nullptr;
bool old_search = false;
String old_term;
+ int old_search_flags = 0;
class Runner;
Ref<Runner> search;
@@ -119,26 +120,30 @@ class EditorHelpSearch::Runner : public RefCounted {
template <typename T>
struct MemberMatch {
- T *doc = nullptr;
+ const T *doc = nullptr;
bool name = false;
String keyword;
+
+ MemberMatch() {}
+ MemberMatch(const T *p_doc) :
+ doc(p_doc) {}
};
struct ClassMatch {
- DocData::ClassDoc *doc = nullptr;
+ const DocData::ClassDoc *doc = nullptr;
bool name = false;
String keyword;
- Vector<MemberMatch<DocData::MethodDoc>> constructors;
- Vector<MemberMatch<DocData::MethodDoc>> methods;
- Vector<MemberMatch<DocData::MethodDoc>> operators;
- Vector<MemberMatch<DocData::MethodDoc>> signals;
- Vector<MemberMatch<DocData::ConstantDoc>> constants;
- Vector<MemberMatch<DocData::PropertyDoc>> properties;
- Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
- Vector<MemberMatch<DocData::MethodDoc>> annotations;
+ LocalVector<MemberMatch<DocData::MethodDoc>> constructors;
+ LocalVector<MemberMatch<DocData::MethodDoc>> methods;
+ LocalVector<MemberMatch<DocData::MethodDoc>> operators;
+ LocalVector<MemberMatch<DocData::MethodDoc>> signals;
+ LocalVector<MemberMatch<DocData::ConstantDoc>> constants;
+ LocalVector<MemberMatch<DocData::PropertyDoc>> properties;
+ LocalVector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
+ LocalVector<MemberMatch<DocData::MethodDoc>> annotations;
bool required() {
- return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size();
+ return name || !keyword.is_empty() || !constructors.is_empty() || !methods.is_empty() || !operators.is_empty() || !signals.is_empty() || !constants.is_empty() || !properties.is_empty() || !theme_properties.is_empty() || !annotations.is_empty();
}
};
@@ -155,6 +160,7 @@ class EditorHelpSearch::Runner : public RefCounted {
LocalVector<RBSet<String, NaturalNoCaseComparator>::Element *> iterator_stack;
HashMap<String, ClassMatch> matches;
HashMap<String, ClassMatch>::Iterator iterator_match;
+ LocalVector<Pair<DocData::ClassDoc *, String>> matched_classes;
TreeItem *root_item = nullptr;
HashMap<String, TreeItem *> class_items;
TreeItem *matched_item = nullptr;
@@ -165,6 +171,12 @@ class EditorHelpSearch::Runner : public RefCounted {
void _populate_cache();
bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item);
+ bool _fill();
+ bool _phase_fill_classes_init();
+ bool _phase_fill_classes();
+ bool _phase_fill_member_items_init();
+ bool _phase_fill_member_items();
+
bool _slice();
bool _phase_match_classes_init();
bool _phase_match_classes();
@@ -177,21 +189,25 @@ class EditorHelpSearch::Runner : public RefCounted {
String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const;
String _build_keywords_tooltip(const String &p_keywords) const;
- void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
+ void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
bool _all_terms_in_name(const String &p_name) const;
String _match_keywords_in_all_terms(const String &p_keywords) const;
bool _match_string(const String &p_term, const String &p_string) const;
String _match_keywords(const String &p_term, const String &p_keywords) const;
void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false);
TreeItem *_create_class_hierarchy(const ClassMatch &p_match);
+ TreeItem *_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray);
TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword);
- TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_type);
+ TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match);
TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match);
TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match);
- TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
+ TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
public:
bool work(uint64_t slot = 100000);
diff --git a/editor/editor_interface.compat.inc b/editor/editor_interface.compat.inc
new file mode 100644
index 0000000000..f5b35931fe
--- /dev/null
+++ b/editor/editor_interface.compat.inc
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* editor_interface.compat.inc */
+/**************************************************************************/
+/* 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 "editor_interface.h"
+
+#ifndef DISABLE_DEPRECATED
+
+void EditorInterface::_popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) {
+ popup_node_selector(p_callback, p_valid_types, nullptr);
+}
+
+void EditorInterface::_popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) {
+ popup_property_selector(p_object, p_callback, p_type_filter, String());
+}
+
+void EditorInterface::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::_popup_node_selector_bind_compat_94323, DEFVAL(TypedArray<StringName>()));
+ ClassDB::bind_compatibility_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::_popup_property_selector_bind_compat_94323, DEFVAL(PackedInt32Array()));
+}
+
+#endif
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 46113ab2cb..86b66ef410 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "editor_interface.h"
+#include "editor_interface.compat.inc"
#include "editor/editor_command_palette.h"
#include "editor/editor_feature_profile.h"
@@ -86,6 +87,10 @@ Ref<EditorSettings> EditorInterface::get_editor_settings() const {
return EditorSettings::get_singleton();
}
+EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const {
+ return EditorUndoRedoManager::get_singleton();
+}
+
TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) {
Vector<Ref<Mesh>> meshes;
@@ -272,7 +277,7 @@ void EditorInterface::set_current_feature_profile(const String &p_profile_name)
// Editor dialogs.
-void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) {
+void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types, Node *p_current_value) {
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_valid_types first.
if (node_selector) {
node_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_node_selected).bind(p_callback));
@@ -292,7 +297,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type
get_base_control()->add_child(node_selector);
- node_selector->popup_scenetree_dialog();
+ node_selector->popup_scenetree_dialog(p_current_value);
const Callable selected_callback = callable_mp(this, &EditorInterface::_node_selected).bind(p_callback);
node_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
@@ -301,7 +306,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type
node_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED);
}
-void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) {
+void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter, const String &p_current_value) {
// TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_type_filter first.
if (property_selector) {
property_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_property_selected).bind(p_callback));
@@ -321,7 +326,7 @@ void EditorInterface::popup_property_selector(Object *p_object, const Callable &
get_base_control()->add_child(property_selector);
- property_selector->select_property_from_instance(p_object);
+ property_selector->select_property_from_instance(p_object, p_current_value);
const Callable selected_callback = callable_mp(this, &EditorInterface::_property_selected).bind(p_callback);
property_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED);
@@ -525,6 +530,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer);
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings);
+ ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo);
ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews);
@@ -559,8 +565,8 @@ void EditorInterface::_bind_methods() {
// Editor dialogs.
- ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()));
- ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()));
+ ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types", "current_value"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()), DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter", "current_value"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()), DEFVAL(String()));
// Editor docks.
diff --git a/editor/editor_interface.h b/editor/editor_interface.h
index 3ef4325780..20d66d71f5 100644
--- a/editor/editor_interface.h
+++ b/editor/editor_interface.h
@@ -45,6 +45,7 @@ class EditorPlugin;
class EditorResourcePreview;
class EditorSelection;
class EditorSettings;
+class EditorUndoRedoManager;
class FileSystemDock;
class Mesh;
class Node;
@@ -80,6 +81,13 @@ class EditorInterface : public Object {
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>());
+ void _popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array());
+
+ static void _bind_compatibility_methods();
+#endif
+
public:
static EditorInterface *get_singleton() { return singleton; }
@@ -93,6 +101,7 @@ public:
EditorResourcePreview *get_resource_previewer() const;
EditorSelection *get_selection() const;
Ref<EditorSettings> get_editor_settings() const;
+ EditorUndoRedoManager *get_editor_undo_redo() const;
Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
@@ -126,9 +135,9 @@ public:
// Editor dialogs.
- void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>());
+ void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>(), Node *p_current_value = nullptr);
// Must use Vector<int> because exposing Vector<Variant::Type> is not supported.
- void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array());
+ void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array(), const String &p_current_value = String());
// Editor docks.
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index c6144a34cb..32e6126225 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -167,6 +167,10 @@
#include "modules/modules_enabled.gen.h" // For gdscript, mono.
+#if defined(GLES3_ENABLED)
+#include "drivers/gles3/rasterizer_gles3.h"
+#endif
+
EditorNode *EditorNode::singleton = nullptr;
static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode";
@@ -481,6 +485,9 @@ void EditorNode::_gdextensions_reloaded() {
// In case the developer is inspecting an object that will be changed by the reload.
InspectorDock::get_inspector_singleton()->update_tree();
+ // Reload script editor to revalidate GDScript if classes are added or removed.
+ ScriptEditor::get_singleton()->reload_scripts(true);
+
// Regenerate documentation.
EditorHelp::generate_doc();
}
@@ -2961,7 +2968,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
ERR_PRINT("Failed to load scene");
}
editor_data.move_edited_scene_to_index(cur_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
scene_tabs->set_current_tab(cur_idx);
} break;
@@ -3963,7 +3970,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
}
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(p_idx), false);
}
Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
@@ -4073,7 +4080,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
_set_current_scene(idx);
}
} else {
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
}
dependency_errors.clear();
@@ -4949,7 +4956,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa
}
void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps));
} else if (singleton->progress_dialog) {
singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
@@ -4957,7 +4966,9 @@ void EditorNode::progress_add_task(const String &p_task, const String &p_label,
}
bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return false;
+ } else if (singleton->cmdline_export_mode) {
print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state);
return false;
} else if (singleton->progress_dialog) {
@@ -4968,7 +4979,9 @@ bool EditorNode::progress_task_step(const String &p_task, const String &p_state,
}
void EditorNode::progress_end_task(const String &p_task) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": end");
} else if (singleton->progress_dialog) {
singleton->progress_dialog->end_task(p_task);
@@ -5007,8 +5020,8 @@ String EditorNode::_get_system_info() const {
#ifdef LINUXBSD_ENABLED
const String display_server = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").capitalize().replace(" ", ""); // `replace` is necessary, because `capitalize` introduces a whitespace between "x" and "11".
#endif // LINUXBSD_ENABLED
- String driver_name = GLOBAL_GET("rendering/rendering_device/driver");
- String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method");
+ String driver_name = OS::get_singleton()->get_current_rendering_driver_name().to_lower();
+ String rendering_method = OS::get_singleton()->get_current_rendering_method().to_lower();
const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name();
@@ -5044,12 +5057,23 @@ String EditorNode::_get_system_info() const {
rendering_method = "Mobile";
} else if (rendering_method == "gl_compatibility") {
rendering_method = "Compatibility";
- driver_name = GLOBAL_GET("rendering/gl_compatibility/driver");
}
if (driver_name == "vulkan") {
driver_name = "Vulkan";
- } else if (driver_name.begins_with("opengl3")) {
- driver_name = "GLES3";
+ } else if (driver_name == "d3d12") {
+ driver_name = "Direct3D 12";
+#if defined(GLES3_ENABLED)
+ } else if (driver_name == "opengl3_angle") {
+ driver_name = "OpenGL ES 3/ANGLE";
+ } else if (driver_name == "opengl3_es") {
+ driver_name = "OpenGL ES 3";
+ } else if (driver_name == "opengl3") {
+ if (RasterizerGLES3::is_gles_over_gl()) {
+ driver_name = "OpenGL 3";
+ } else {
+ driver_name = "OpenGL ES 3";
+ }
+#endif
} else if (driver_name == "metal") {
driver_name = "Metal";
}
@@ -5928,7 +5952,7 @@ void EditorNode::reload_scene(const String &p_path) {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Scene is not open, so at it might be instantiated. We'll refresh the whole scene later.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
if (is_unsaved) {
EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id);
}
@@ -5947,7 +5971,7 @@ void EditorNode::reload_scene(const String &p_path) {
// Adjust index so tab is back a the previous position.
editor_data.move_edited_scene_to_index(scene_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(scene_idx), false);
// Recover the tab.
scene_tabs->set_current_tab(current_tab);
@@ -6092,7 +6116,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Clear the history for this affected tab.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
// Update the version
editor_data.is_scene_changed(current_scene_idx);
@@ -6135,7 +6159,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
base_packed_scene = current_packed_scene;
}
if (!local_scene_cache.find(path)) {
- current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err);
+ current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
local_scene_cache[path] = current_packed_scene;
} else {
current_packed_scene = local_scene_cache[path];
@@ -7358,11 +7382,9 @@ EditorNode::EditorNode() {
settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\")."));
-#ifndef ANDROID_ENABLED
ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
-#endif
settings_menu->add_separator();
#ifndef ANDROID_ENABLED
diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp
index a40055cf85..9ff8bf674d 100644
--- a/editor/editor_properties_vector.cpp
+++ b/editor/editor_properties_vector.cpp
@@ -130,9 +130,11 @@ void EditorPropertyVectorN::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (linked->is_visible()) {
- const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
- linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
- _update_ratio();
+ if (get_edited_object()) {
+ const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
+ linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
+ _update_ratio();
+ }
}
} break;
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 8935b9ad8a..f20dd992bb 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() {
_file_selected(quick_open->get_selected());
}
+void EditorResourcePicker::_resource_saved(Object *p_resource) {
+ if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
+ emit_signal(SNAME("resource_changed"), edited_resource);
+ _update_resource();
+ }
+}
+
void EditorResourcePicker::_update_menu() {
_update_menu_items();
@@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
if (edited_resource.is_null()) {
return;
}
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->connect("resource_saved", resource_saved);
+ }
EditorNode::get_singleton()->save_resource_as(edited_resource);
} break;
@@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) {
assign_button->queue_redraw();
}
} break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
+ }
+ } break;
}
}
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 28229e6b37..05e392da2c 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer {
void _file_quick_selected();
void _file_selected(const String &p_path);
+ void _resource_saved(Object *p_resource);
+
void _update_menu();
void _update_menu_items();
void _edit_menu_cbk(int p_which);
diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp
index 71865f7e8c..956fdc5cfa 100644
--- a/editor/editor_resource_preview.cpp
+++ b/editor/editor_resource_preview.cpp
@@ -221,7 +221,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<
r_small_texture->set_image(small_image);
}
- break;
+ if (generated.is_valid()) {
+ break;
+ }
}
if (!p_item.resource.is_valid()) {
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index 5d378820ae..e0e1ef6d19 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -141,7 +141,7 @@ Error EditorRunNative::start_run_native(int p_id) {
emit_signal(SNAME("native_run"), preset);
- int flags = 0;
+ BitField<EditorExportPlatform::DebugFlags> flags = 0;
bool deploy_debug_remote = is_deploy_debug_remote_enabled();
bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false);
@@ -149,16 +149,16 @@ Error EditorRunNative::start_run_native(int p_id) {
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
if (deploy_debug_remote) {
- flags |= EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG);
}
if (deploy_dumb) {
- flags |= EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT);
}
if (debug_collisions) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS);
}
if (debug_navigation) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION);
}
eep->clear_messages();
diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp
index b55a951b49..2e96ae82fc 100644
--- a/editor/editor_undo_redo_manager.cpp
+++ b/editor/editor_undo_redo_manager.cpp
@@ -390,7 +390,7 @@ bool EditorUndoRedoManager::has_history(int p_idx) const {
return history_map.has(p_idx);
}
-void EditorUndoRedoManager::clear_history(bool p_increase_version, int p_idx) {
+void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) {
if (p_idx != INVALID_HISTORY) {
History &history = get_or_create_history(p_idx);
history.undo_redo->clear_history(p_increase_version);
@@ -507,6 +507,7 @@ void EditorUndoRedoManager::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object);
ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo);
+ ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true));
ADD_SIGNAL(MethodInfo("history_changed"));
ADD_SIGNAL(MethodInfo("version_changed"));
diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h
index 219d5e0702..54475c3c6c 100644
--- a/editor/editor_undo_redo_manager.h
+++ b/editor/editor_undo_redo_manager.h
@@ -125,7 +125,7 @@ public:
bool undo_history(int p_id);
bool redo();
bool redo_history(int p_id);
- void clear_history(bool p_increase_version = true, int p_idx = INVALID_HISTORY);
+ void clear_history(int p_idx = INVALID_HISTORY, bool p_increase_version = true);
void set_history_as_saved(int p_idx);
void set_history_as_unsaved(int p_idx);
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 72ab186036..975a601ae1 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -124,7 +124,17 @@ void EditorExport::_bind_methods() {
void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
export_platforms.push_back(p_platform);
+
should_update_presets = true;
+ should_reload_presets = true;
+}
+
+void EditorExport::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ export_platforms.erase(p_platform);
+ p_platform->cleanup();
+
+ should_update_presets = true;
+ should_reload_presets = true;
}
int EditorExport::get_export_platform_count() {
@@ -244,7 +254,7 @@ void EditorExport::load_config() {
if (!preset.is_valid()) {
index++;
- ERR_CONTINUE(!preset.is_valid());
+ continue; // Unknown platform, skip without error (platform might be loaded later).
}
preset->set_name(config->get_value(section, "name"));
@@ -343,6 +353,12 @@ void EditorExport::load_config() {
void EditorExport::update_export_presets() {
HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options;
+ if (should_reload_presets) {
+ should_reload_presets = false;
+ export_presets.clear();
+ load_config();
+ }
+
for (int i = 0; i < export_platforms.size(); i++) {
Ref<EditorExportPlatform> platform = export_platforms[i];
diff --git a/editor/export/editor_export.h b/editor/export/editor_export.h
index f8cb90dc39..ebb2038f53 100644
--- a/editor/export/editor_export.h
+++ b/editor/export/editor_export.h
@@ -47,6 +47,7 @@ class EditorExport : public Node {
Timer *save_timer = nullptr;
bool block_save = false;
bool should_update_presets = false;
+ bool should_reload_presets = false;
static EditorExport *singleton;
@@ -66,6 +67,7 @@ public:
void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
int get_export_platform_count();
Ref<EditorExportPlatform> get_export_platform(int p_idx);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
void add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos = -1);
int get_export_preset_count() const;
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 8b31eda3bc..7ad589a58d 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -71,7 +71,7 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
p_log->add_text(" ");
p_log->add_text(get_name());
p_log->add_text(" - ");
- if (p_err == OK) {
+ if (p_err == OK && get_worst_message_type() < EditorExportPlatform::EXPORT_MESSAGE_ERROR) {
if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
p_log->add_image(p_log->get_editor_theme_icon(SNAME("StatusWarning")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
p_log->add_text(" ");
@@ -167,58 +167,6 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
return has_messages;
}
-void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) {
- String host = EDITOR_GET("network/debug/remote_host");
- int remote_port = (int)EDITOR_GET("network/debug/remote_port");
-
- if (EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
- host = EDITOR_GET("export/android/wifi_remote_debug_host");
- } else if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
- host = "localhost";
- }
-
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
- int port = EDITOR_GET("filesystem/file_server/port");
- String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
- if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
- }
- }
-
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
-
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
-
- List<String> breakpoints;
- ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
-
- if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
- String bpoints;
- for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
- bpoints += E->get().replace(" ", "%20");
- if (E->next()) {
- bpoints += ",";
- }
- }
-
- r_flags.push_back(bpoints);
- }
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
- }
-}
-
Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -530,7 +478,7 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset>
return result;
}
-EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
HashSet<String> features = p_platform.get_features(p_preset, p_debug);
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
//initial export plugin callback
@@ -919,6 +867,55 @@ Vector<String> EditorExportPlatform::get_forced_export_files() {
return files;
}
+Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb;
+ ERR_FAIL_COND_V(!cb.is_valid(), FAILED);
+
+ Variant path = p_path;
+ Variant data = p_data;
+ Variant file = p_file;
+ Variant total = p_total;
+ Variant enc_in = p_enc_in_filters;
+ Variant enc_ex = p_enc_ex_filters;
+ Variant enc_key = p_key;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[7] = { &path, &data, &file, &total, &enc_in, &enc_ex, &enc_key };
+
+ cb.callp(args, 7, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute file save callback: %s.", Variant::get_callable_error_text(cb, args, 7, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->so_cb;
+ if (!cb.is_valid()) {
+ return OK; // Optional.
+ }
+
+ Variant path = p_so.path;
+ Variant tags = p_so.tags;
+ Variant target = p_so.target;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &path, &tags, &target };
+
+ cb.callp(args, 3, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute shared object save callback: %s.", Variant::get_callable_error_text(cb, args, 3, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) {
+ ScriptCallbackData data;
+ data.file_cb = p_save_func;
+ data.so_cb = p_so_func;
+ return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object);
+}
+
Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
//figure out paths of files that will be exported
HashSet<String> paths;
@@ -1425,7 +1422,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key);
}
-Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) {
PackData *pack_data = (PackData *)p_userdata;
if (pack_data->so_files) {
pack_data->so_files->push_back(p_so);
@@ -1434,6 +1431,15 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj
return OK;
}
+Error EditorExportPlatform::_zip_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ ZipData *zip_data = (ZipData *)p_userdata;
+ if (zip_data->so_files) {
+ zip_data->so_files->push_back(p_so);
+ }
+
+ return OK;
+}
+
void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder);
@@ -1551,6 +1557,54 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_
da->list_dir_end();
}
+Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed) {
+ Vector<SharedObject> so_files;
+ int64_t embedded_start = 0;
+ int64_t embedded_size = 0;
+ Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ if (p_embed) {
+ ret["embedded_start"] = embedded_start;
+ ret["embedded_size"] = embedded_size;
+ }
+ }
+
+ return ret;
+}
+
+Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_zip(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
EditorProgress ep("savepack", TTR("Packing"), 102, true);
@@ -1570,7 +1624,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
pd.f = ftmp;
pd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object);
// Close temp file.
pd.f.unref();
@@ -1777,7 +1831,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return OK;
}
-Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
EditorProgress ep("savezip", TTR("Packing"), 102, true);
Ref<FileAccess> io_fa;
@@ -1787,8 +1841,9 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
ZipData zd;
zd.ep = &ep;
zd.zip = zip;
+ zd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd);
+ Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object);
if (err != OK && err != ERR_SKIP) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
}
@@ -1798,45 +1853,48 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
return OK;
}
-Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_pack(p_preset, p_debug, p_path);
}
-Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_zip(p_preset, p_debug, p_path);
}
-void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) {
+Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ Vector<String> ret;
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (get_name() == "Android" && EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
+ host = EDITOR_GET("export/android/wifi_remote_debug_host");
+ } else if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
+ ret.push_back("--remote-fs");
+ ret.push_back(host + ":" + itos(port));
if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
+ ret.push_back("--remote-fs-password");
+ ret.push_back(passwd);
}
}
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
+ if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
+ ret.push_back("--remote-debug");
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
+ ret.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
List<String> breakpoints;
ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
+ ret.push_back("--breakpoints");
String bpoints;
for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
bpoints += E->get().replace(" ", "%20");
@@ -1845,17 +1903,18 @@ void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags
}
}
- r_flags.push_back(bpoints);
+ ret.push_back(bpoints);
}
}
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
+ ret.push_back("--debug-collisions");
}
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
+ ret.push_back("--debug-navigation");
}
+ return ret;
}
bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
@@ -2035,8 +2094,61 @@ Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const Strin
return OK;
}
+Array EditorExportPlatform::get_current_presets() const {
+ Array ret;
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
+ if (ep->get_platform() == this) {
+ ret.push_back(ep);
+ }
+ }
+ return ret;
+}
+
void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_os_name"), &EditorExportPlatform::get_os_name);
+
+ ClassDB::bind_method(D_METHOD("create_preset"), &EditorExportPlatform::create_preset);
+
+ ClassDB::bind_method(D_METHOD("find_export_template", "template_file_name"), &EditorExportPlatform::_find_export_template);
+ ClassDB::bind_method(D_METHOD("get_current_presets"), &EditorExportPlatform::get_current_presets);
+
+ ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip);
+
+ ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags);
+
+ ClassDB::bind_method(D_METHOD("export_project_files", "preset", "debug", "save_cb", "shared_cb"), &EditorExportPlatform::_export_project_files, DEFVAL(Callable()));
+
+ ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages);
+ ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message);
+ ClassDB::bind_method(D_METHOD("get_message_count"), &EditorExportPlatform::get_message_count);
+
+ ClassDB::bind_method(D_METHOD("get_message_type", "index"), &EditorExportPlatform::_get_message_type);
+ ClassDB::bind_method(D_METHOD("get_message_category", "index"), &EditorExportPlatform::_get_message_category);
+ ClassDB::bind_method(D_METHOD("get_message_text", "index"), &EditorExportPlatform::_get_message_text);
+ ClassDB::bind_method(D_METHOD("get_worst_message_type"), &EditorExportPlatform::get_worst_message_type);
+
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote", "host", "port", "ssh_arg", "cmd_args", "output", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote, DEFVAL(Array()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote_no_wait", "host", "port", "ssh_args", "cmd_args", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote_no_wait, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_push_to_remote", "host", "port", "scp_args", "src_file", "dst_file"), &EditorExportPlatform::ssh_push_to_remote);
+
+ ClassDB::bind_static_method("EditorExportPlatform", D_METHOD("get_forced_export_files"), &EditorExportPlatform::get_forced_export_files);
+
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_NONE);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_INFO);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_WARNING);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_ERROR);
+
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_DUMB_CLIENT);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_COLLISIONS);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_NAVIGATION);
}
EditorExportPlatform::EditorExportPlatform() {
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 3fd75ff67f..a800bb95e6 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -56,6 +56,14 @@ public:
typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
+ enum DebugFlags {
+ DEBUG_FLAG_DUMB_CLIENT = 1,
+ DEBUG_FLAG_REMOTE_DEBUG = 2,
+ DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
+ DEBUG_FLAG_VIEW_COLLISIONS = 8,
+ DEBUG_FLAG_VIEW_NAVIGATION = 16,
+ };
+
enum ExportMessageType {
EXPORT_MESSAGE_NONE,
EXPORT_MESSAGE_INFO,
@@ -92,6 +100,7 @@ private:
struct ZipData {
void *zip = nullptr;
EditorProgress *ep = nullptr;
+ Vector<SharedObject> *so_files = nullptr;
};
Vector<ExportMessage> messages;
@@ -101,13 +110,22 @@ private:
void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
+ struct ScriptCallbackData {
+ Callable file_cb;
+ Callable so_cb;
+ };
+
+ static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so);
void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude);
void _edit_filter_list(HashSet<String> &r_list, const String &p_filter, bool exclude);
- static Error _add_shared_object(void *p_userdata, const SharedObject &p_so);
-
struct FileExportCache {
uint64_t source_modified_time = 0;
String source_md5;
@@ -126,19 +144,46 @@ private:
protected:
struct ExportNotifier {
- ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
~ExportNotifier();
};
HashSet<String> get_features(const Ref<EditorExportPreset> &p_preset, bool p_debug) const;
- bool exists_export_template(const String &template_file_name, String *err) const;
- String find_export_template(const String &template_file_name, String *err = nullptr) const;
- void gen_export_flags(Vector<String> &r_flags, int p_flags);
- void gen_debug_flags(Vector<String> &r_flags, int p_flags);
+ Dictionary _find_export_template(const String &p_template_file_name) const {
+ Dictionary ret;
+ String err;
+
+ String path = find_export_template(p_template_file_name, &err);
+ ret["result"] = (err.is_empty() && !path.is_empty()) ? OK : FAILED;
+ ret["path"] = path;
+ ret["error_string"] = err;
+
+ return ret;
+ }
+
+ bool exists_export_template(const String &p_template_file_name, String *r_err) const;
+ String find_export_template(const String &p_template_file_name, String *r_err = nullptr) const;
+ Vector<String> gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
+ Error _ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, Array r_output = Array(), int p_port_fwd = -1) const {
+ String pipe;
+ Error err = ssh_run_on_remote(p_host, p_port, p_ssh_args, p_cmd_args, &pipe, p_port_fwd);
+ r_output.push_back(pipe);
+ return err;
+ }
+ OS::ProcessID _ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, int p_port_fwd = -1) const {
+ OS::ProcessID pid = 0;
+ Error err = ssh_run_on_remote_no_wait(p_host, p_port, p_ssh_args, p_cmd_args, &pid, p_port_fwd);
+ if (err != OK) {
+ return -1;
+ } else {
+ return pid;
+ }
+ }
+
Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const;
Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const;
Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;
@@ -195,6 +240,21 @@ public:
return messages[p_index];
}
+ virtual ExportMessageType _get_message_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), EXPORT_MESSAGE_NONE);
+ return messages[p_index].msg_type;
+ }
+
+ virtual String _get_message_category(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].category;
+ }
+
+ virtual String _get_message_text(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].text;
+ }
+
virtual ExportMessageType get_worst_message_type() const {
ExportMessageType worst_type = EXPORT_MESSAGE_NONE;
for (int i = 0; i < messages.size(); i++) {
@@ -216,10 +276,16 @@ public:
virtual String get_name() const = 0;
virtual Ref<Texture2D> get_logo() const = 0;
+ Array get_current_presets() const;
+
+ Error _export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func);
Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr);
+ Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false);
+ Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+
Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
- Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+ Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
virtual bool poll_export() { return false; }
virtual int get_options_count() const { return 0; }
@@ -229,31 +295,26 @@ public:
virtual String get_option_tooltip(int p_device) const { return ""; }
virtual String get_device_architecture(int p_device) const { return ""; }
- enum DebugFlags {
- DEBUG_FLAG_DUMB_CLIENT = 1,
- DEBUG_FLAG_REMOTE_DEBUG = 2,
- DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
- DEBUG_FLAG_VIEW_COLLISIONS = 8,
- DEBUG_FLAG_VIEW_NAVIGATION = 16,
- };
-
virtual void cleanup() {}
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; }
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { return OK; }
virtual Ref<Texture2D> get_run_icon() const { return get_logo(); }
- bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const = 0;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) = 0;
- virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
- virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0;
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual void get_platform_features(List<String> *r_features) const = 0;
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) = 0;
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features){};
virtual String get_debug_protocol() const { return "tcp://"; }
EditorExportPlatform();
};
+VARIANT_ENUM_CAST(EditorExportPlatform::ExportMessageType)
+VARIANT_BITFIELD_CAST(EditorExportPlatform::DebugFlags);
+
#endif // EDITOR_EXPORT_PLATFORM_H
diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp
new file mode 100644
index 0000000000..808a2076e2
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.cpp
@@ -0,0 +1,317 @@
+/**************************************************************************/
+/* editor_export_platform_extension.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 "editor_export_platform_extension.h"
+
+void EditorExportPlatformExtension::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_config_error", "error_text"), &EditorExportPlatformExtension::set_config_error);
+ ClassDB::bind_method(D_METHOD("get_config_error"), &EditorExportPlatformExtension::get_config_error);
+
+ ClassDB::bind_method(D_METHOD("set_config_missing_templates", "missing_templates"), &EditorExportPlatformExtension::set_config_missing_templates);
+ ClassDB::bind_method(D_METHOD("get_config_missing_templates"), &EditorExportPlatformExtension::get_config_missing_templates);
+
+ GDVIRTUAL_BIND(_get_preset_features, "preset");
+ GDVIRTUAL_BIND(_is_executable, "path");
+ GDVIRTUAL_BIND(_get_export_options);
+ GDVIRTUAL_BIND(_should_update_export_options);
+ GDVIRTUAL_BIND(_get_export_option_visibility, "preset", "option");
+ GDVIRTUAL_BIND(_get_export_option_warning, "preset", "option");
+
+ GDVIRTUAL_BIND(_get_os_name);
+ GDVIRTUAL_BIND(_get_name);
+ GDVIRTUAL_BIND(_get_logo);
+
+ GDVIRTUAL_BIND(_poll_export);
+ GDVIRTUAL_BIND(_get_options_count);
+ GDVIRTUAL_BIND(_get_options_tooltip);
+
+ GDVIRTUAL_BIND(_get_option_icon, "device");
+ GDVIRTUAL_BIND(_get_option_label, "device");
+ GDVIRTUAL_BIND(_get_option_tooltip, "device");
+ GDVIRTUAL_BIND(_get_device_architecture, "device");
+
+ GDVIRTUAL_BIND(_cleanup);
+
+ GDVIRTUAL_BIND(_run, "preset", "device", "debug_flags");
+ GDVIRTUAL_BIND(_get_run_icon);
+
+ GDVIRTUAL_BIND(_can_export, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_export_configuration, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_project_configuration, "preset");
+
+ GDVIRTUAL_BIND(_get_binary_extensions, "preset");
+
+ GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags");
+
+ GDVIRTUAL_BIND(_get_platform_features);
+
+ GDVIRTUAL_BIND(_get_debug_protocol);
+}
+
+void EditorExportPlatformExtension::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_preset_features, p_preset, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::is_executable(const String &p_path) const {
+ bool ret = false;
+ GDVIRTUAL_CALL(_is_executable, p_path, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::get_export_options(List<ExportOption> *r_options) const {
+ TypedArray<Dictionary> ret;
+ if (GDVIRTUAL_CALL(_get_export_options, ret) && r_options) {
+ for (const Variant &var : ret) {
+ const Dictionary &d = var;
+ ERR_CONTINUE(!d.has("name"));
+ ERR_CONTINUE(!d.has("type"));
+
+ PropertyInfo pinfo = PropertyInfo::from_dict(d);
+ ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE));
+ ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX);
+
+ Variant default_value;
+ if (d.has("default_value")) {
+ default_value = d["default_value"];
+ }
+ bool update_visibility = false;
+ if (d.has("update_visibility")) {
+ update_visibility = d["update_visibility"];
+ }
+ bool required = false;
+ if (d.has("required")) {
+ required = d["required"];
+ }
+
+ r_options->push_back(ExportOption(pinfo, default_value, update_visibility, required));
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::should_update_export_options() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_should_update_export_options, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+ bool ret = true;
+ GDVIRTUAL_CALL(_get_export_option_visibility, Ref<EditorExportPreset>(p_preset), p_option, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_export_option_warning, Ref<EditorExportPreset>(p_preset), p_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_os_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_os_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_name, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_logo() const {
+ Ref<Texture2D> ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_logo, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::poll_export() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_poll_export, ret);
+ return ret;
+}
+
+int EditorExportPlatformExtension::get_options_count() const {
+ int ret = 0;
+ GDVIRTUAL_CALL(_get_options_count, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_options_tooltip() const {
+ String ret;
+ GDVIRTUAL_CALL(_get_options_tooltip, ret);
+ return ret;
+}
+
+Ref<ImageTexture> EditorExportPlatformExtension::get_option_icon(int p_index) const {
+ Ref<ImageTexture> ret;
+ if (GDVIRTUAL_CALL(_get_option_icon, p_index, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_option_icon(p_index);
+}
+
+String EditorExportPlatformExtension::get_option_label(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_label, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_option_tooltip(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_tooltip, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_device_architecture(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_device_architecture, p_device, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::cleanup() {
+ GDVIRTUAL_CALL(_cleanup);
+}
+
+Error EditorExportPlatformExtension::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
+ Error ret = OK;
+ GDVIRTUAL_CALL(_run, p_preset, p_device, p_debug_flags, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_run_icon() const {
+ Ref<Texture2D> ret;
+ if (GDVIRTUAL_CALL(_get_run_icon, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_run_icon();
+}
+
+bool EditorExportPlatformExtension::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_CALL(_can_export, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ return ret;
+ }
+ return EditorExportPlatform::can_export(p_preset, r_error, r_missing_templates, p_debug);
+}
+
+bool EditorExportPlatformExtension::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_export_configuration, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ }
+ return ret;
+}
+
+bool EditorExportPlatformExtension::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ bool ret = false;
+ config_error = r_error;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_project_configuration, p_preset, ret)) {
+ r_error = config_error;
+ }
+ return ret;
+}
+
+List<String> EditorExportPlatformExtension::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ List<String> ret_list;
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_binary_extensions, p_preset, ret)) {
+ for (const String &E : ret) {
+ ret_list.push_back(E);
+ }
+ }
+ return ret_list;
+}
+
+Error EditorExportPlatformExtension::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ GDVIRTUAL_REQUIRED_CALL(_export_project, p_preset, p_debug, p_path, p_flags, ret);
+ return ret;
+}
+
+Error EditorExportPlatformExtension::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_pack, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_pack(p_preset, p_debug, p_path);
+}
+
+Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_zip, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_zip(p_preset, p_debug, p_path);
+}
+
+void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+String EditorExportPlatformExtension::get_debug_protocol() const {
+ String ret;
+ if (GDVIRTUAL_CALL(_get_debug_protocol, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_debug_protocol();
+}
+
+EditorExportPlatformExtension::EditorExportPlatformExtension() {
+ //NOP
+}
+
+EditorExportPlatformExtension::~EditorExportPlatformExtension() {
+ //NOP
+}
diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h
new file mode 100644
index 0000000000..6391e65ac1
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.h
@@ -0,0 +1,149 @@
+/**************************************************************************/
+/* editor_export_platform_extension.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 EDITOR_EXPORT_PLATFORM_EXTENSION_H
+#define EDITOR_EXPORT_PLATFORM_EXTENSION_H
+
+#include "editor_export_platform.h"
+#include "editor_export_preset.h"
+
+class EditorExportPlatformExtension : public EditorExportPlatform {
+ GDCLASS(EditorExportPlatformExtension, EditorExportPlatform);
+
+ mutable String config_error;
+ mutable bool config_missing_templates = false;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_preset_features, Ref<EditorExportPreset>);
+
+ virtual bool is_executable(const String &p_path) const override;
+ GDVIRTUAL1RC(bool, _is_executable, const String &);
+
+ virtual void get_export_options(List<ExportOption> *r_options) const override;
+ GDVIRTUAL0RC(TypedArray<Dictionary>, _get_export_options);
+
+ virtual bool should_update_export_options() override;
+ GDVIRTUAL0R(bool, _should_update_export_options);
+
+ virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
+ GDVIRTUAL2RC(bool, _get_export_option_visibility, Ref<EditorExportPreset>, const String &);
+
+ virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
+ GDVIRTUAL2RC(String, _get_export_option_warning, Ref<EditorExportPreset>, const StringName &);
+
+ virtual String get_os_name() const override;
+ GDVIRTUAL0RC(String, _get_os_name);
+
+ virtual String get_name() const override;
+ GDVIRTUAL0RC(String, _get_name);
+
+ virtual Ref<Texture2D> get_logo() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_logo);
+
+ virtual bool poll_export() override;
+ GDVIRTUAL0R(bool, _poll_export);
+
+ virtual int get_options_count() const override;
+ GDVIRTUAL0RC(int, _get_options_count);
+
+ virtual String get_options_tooltip() const override;
+ GDVIRTUAL0RC(String, _get_options_tooltip);
+
+ virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+ GDVIRTUAL1RC(Ref<ImageTexture>, _get_option_icon, int);
+
+ virtual String get_option_label(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_label, int);
+
+ virtual String get_option_tooltip(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_tooltip, int);
+
+ virtual String get_device_architecture(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_device_architecture, int);
+
+ virtual void cleanup() override;
+ GDVIRTUAL0(_cleanup);
+
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
+ GDVIRTUAL3R(Error, _run, Ref<EditorExportPreset>, int, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Ref<Texture2D> get_run_icon() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_run_icon);
+
+ void set_config_error(const String &p_error) const {
+ config_error = p_error;
+ }
+ String get_config_error() const {
+ return config_error;
+ }
+
+ void set_config_missing_templates(bool p_missing_templates) const {
+ config_missing_templates = p_missing_templates;
+ }
+ bool get_config_missing_templates() const {
+ return config_missing_templates;
+ }
+
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _can_export, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _has_valid_export_configuration, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+ GDVIRTUAL1RC(bool, _has_valid_project_configuration, Ref<EditorExportPreset>);
+
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_binary_extensions, Ref<EditorExportPreset>);
+
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_project, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_pack, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual void get_platform_features(List<String> *r_features) const override;
+ GDVIRTUAL0RC(Vector<String>, _get_platform_features);
+
+ virtual String get_debug_protocol() const override;
+ GDVIRTUAL0RC(String, _get_debug_protocol);
+
+ EditorExportPlatformExtension();
+ ~EditorExportPlatformExtension();
+};
+
+#endif // EDITOR_EXPORT_PLATFORM_EXTENSION_H
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index cdaf18b346..24d89b7f34 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -115,7 +115,7 @@ bool EditorExportPlatformPC::has_valid_project_configuration(const Ref<EditorExp
return true;
}
-Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
Error err = prepare_template(p_preset, p_debug, p_path, p_flags);
@@ -129,7 +129,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr
return err;
}
-Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (!DirAccess::exists(p_path.get_base_dir())) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist."));
return ERR_FILE_BAD_PATH;
@@ -182,7 +182,7 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_
return err;
}
-Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String pck_path;
if (p_preset->get("binary_format/embed_pck")) {
pck_path = p_path;
diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h
index 53574c2333..668ddaf47e 100644
--- a/editor/export/editor_export_platform_pc.h
+++ b/editor/export/editor_export_platform_pc.h
@@ -54,13 +54,13 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
virtual String get_template_file_name(const String &p_target, const String &p_arch) const = 0;
- virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; };
- virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return OK; }
+ virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
void set_name(const String &p_name);
void set_os_name(const String &p_name);
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 28d0750d5a..6bb21d7fd4 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -48,6 +48,14 @@ Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const {
return export_preset;
}
+Ref<EditorExportPlatform> EditorExportPlugin::get_export_platform() const {
+ if (export_preset.is_valid()) {
+ return export_preset->get_platform();
+ } else {
+ return Ref<EditorExportPlatform>();
+ }
+}
+
void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) {
ExtraFile ef;
ef.data = p_file;
@@ -132,7 +140,7 @@ Vector<String> EditorExportPlugin::get_ios_project_static_libs() const {
}
Variant EditorExportPlugin::get_option(const StringName &p_name) const {
- ERR_FAIL_NULL_V(export_preset, Variant());
+ ERR_FAIL_COND_V(export_preset.is_null(), Variant());
return export_preset->get(p_name);
}
@@ -321,6 +329,9 @@ void EditorExportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option);
+ ClassDB::bind_method(D_METHOD("get_export_preset"), &EditorExportPlugin::get_export_preset);
+ ClassDB::bind_method(D_METHOD("get_export_platform"), &EditorExportPlugin::get_export_platform);
+
GDVIRTUAL_BIND(_export_file, "path", "type", "features");
GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags");
GDVIRTUAL_BIND(_export_end);
diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h
index 56eea85010..7a355614c7 100644
--- a/editor/export/editor_export_plugin.h
+++ b/editor/export/editor_export_plugin.h
@@ -91,6 +91,7 @@ class EditorExportPlugin : public RefCounted {
protected:
void set_export_preset(const Ref<EditorExportPreset> &p_preset);
Ref<EditorExportPreset> get_export_preset() const;
+ Ref<EditorExportPlatform> get_export_platform() const;
void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index e2e3e9d154..9f805666d0 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -62,6 +62,48 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
void EditorExportPreset::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_property_warning", "name"), &EditorExportPreset::_get_property_warning);
+
+ ClassDB::bind_method(D_METHOD("has", "property"), &EditorExportPreset::has);
+
+ ClassDB::bind_method(D_METHOD("get_files_to_export"), &EditorExportPreset::get_files_to_export);
+ ClassDB::bind_method(D_METHOD("get_customized_files"), &EditorExportPreset::get_customized_files);
+ ClassDB::bind_method(D_METHOD("get_customized_files_count"), &EditorExportPreset::get_customized_files_count);
+ ClassDB::bind_method(D_METHOD("has_export_file", "path"), &EditorExportPreset::has_export_file);
+ ClassDB::bind_method(D_METHOD("get_file_export_mode", "path", "default"), &EditorExportPreset::get_file_export_mode, DEFVAL(MODE_FILE_NOT_CUSTOMIZED));
+
+ ClassDB::bind_method(D_METHOD("get_preset_name"), &EditorExportPreset::get_name);
+ ClassDB::bind_method(D_METHOD("is_runnable"), &EditorExportPreset::is_runnable);
+ ClassDB::bind_method(D_METHOD("are_advanced_options_enabled"), &EditorExportPreset::are_advanced_options_enabled);
+ ClassDB::bind_method(D_METHOD("is_dedicated_server"), &EditorExportPreset::is_dedicated_server);
+ ClassDB::bind_method(D_METHOD("get_export_filter"), &EditorExportPreset::get_export_filter);
+ ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter);
+ ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter);
+ ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features);
+ ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path);
+ ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter);
+ ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter);
+ ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck);
+ ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory);
+ ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key);
+ ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode);
+
+ ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env);
+ ClassDB::bind_method(D_METHOD("get_version", "name", "windows_version"), &EditorExportPreset::get_version);
+
+ BIND_ENUM_CONSTANT(EXPORT_ALL_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_SCENES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXCLUDE_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_CUSTOMIZED);
+
+ BIND_ENUM_CONSTANT(MODE_FILE_NOT_CUSTOMIZED);
+ BIND_ENUM_CONSTANT(MODE_FILE_STRIP);
+ BIND_ENUM_CONSTANT(MODE_FILE_KEEP);
+ BIND_ENUM_CONSTANT(MODE_FILE_REMOVE);
+
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_TEXT);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS_COMPRESSED);
}
String EditorExportPreset::_get_property_warning(const StringName &p_name) const {
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index c6a8808af1..f220477461 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -168,6 +168,9 @@ public:
void set_script_export_mode(int p_mode);
int get_script_export_mode() const;
+ Variant _get_or_env(const StringName &p_name, const String &p_env_var) const {
+ return get_or_env(p_name, p_env_var);
+ }
Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const;
// Return the preset's version number, or fall back to the
@@ -183,4 +186,8 @@ public:
EditorExportPreset();
};
+VARIANT_ENUM_CAST(EditorExportPreset::ExportFilter);
+VARIANT_ENUM_CAST(EditorExportPreset::FileExportMode);
+VARIANT_ENUM_CAST(EditorExportPreset::ScriptExportMode);
+
#endif // EDITOR_EXPORT_PRESET_H
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 9dfbe515d2..2e4f2da9a8 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -47,6 +47,32 @@
#include "scene/gui/tree.h"
#include "scene/main/http_request.h"
+enum DownloadsAvailability {
+ DOWNLOADS_AVAILABLE,
+ DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE,
+ DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS,
+};
+
+static DownloadsAvailability _get_downloads_availability() {
+ const int network_mode = EDITOR_GET("network/connection/network_mode");
+ if (network_mode == EditorSettings::NETWORK_OFFLINE) {
+ return DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE;
+ }
+
+ // Downloadable export templates are only available for stable and official alpha/beta/RC builds
+ // (which always have a number following their status, e.g. "alpha1").
+ // Therefore, don't display download-related features when using a development version
+ // (whose builds aren't numbered).
+ if (String(VERSION_STATUS) == String("dev") ||
+ String(VERSION_STATUS) == String("alpha") ||
+ String(VERSION_STATUS) == String("beta") ||
+ String(VERSION_STATUS) == String("rc")) {
+ return DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS;
+ }
+
+ return DOWNLOADS_AVAILABLE;
+}
+
void ExportTemplateManager::_update_template_status() {
// Fetch installed templates from the file system.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -641,9 +667,55 @@ void ExportTemplateManager::_open_template_folder(const String &p_version) {
void ExportTemplateManager::popup_manager() {
_update_template_status();
- if (downloads_available && !is_downloading_templates) {
- _refresh_mirrors();
+
+ switch (_get_downloads_availability()) {
+ case DOWNLOADS_AVAILABLE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Best available mirror"), 0);
+ mirrors_list->set_disabled(false);
+ mirrors_list->set_tooltip_text("");
+
+ mirror_options_button->set_disabled(false);
+
+ download_current_button->set_disabled(false);
+ download_current_button->set_tooltip_text("");
+
+ if (!is_downloading_templates) {
+ _refresh_mirrors();
+ }
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Not available in offline mode"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("No templates for development builds"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+ } break;
}
+
popup_centered(Size2(720, 280) * EDSCALE);
}
@@ -866,16 +938,6 @@ ExportTemplateManager::ExportTemplateManager() {
set_hide_on_ok(false);
set_ok_button_text(TTR("Close"));
- // Downloadable export templates are only available for stable and official alpha/beta/RC builds
- // (which always have a number following their status, e.g. "alpha1").
- // Therefore, don't display download-related features when using a development version
- // (whose builds aren't numbered).
- downloads_available =
- String(VERSION_STATUS) != String("dev") &&
- String(VERSION_STATUS) != String("alpha") &&
- String(VERSION_STATUS) != String("beta") &&
- String(VERSION_STATUS) != String("rc");
-
VBoxContainer *main_vb = memnew(VBoxContainer);
add_child(main_vb);
@@ -898,11 +960,6 @@ ExportTemplateManager::ExportTemplateManager() {
current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
- if (downloads_available) {
- current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
- } else {
- current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
- }
current_hb->add_child(current_missing_label);
// Status: Current version is installed.
@@ -957,12 +1014,6 @@ ExportTemplateManager::ExportTemplateManager() {
mirrors_list = memnew(OptionButton);
mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
- if (downloads_available) {
- mirrors_list->add_item(TTR("Best available mirror"), 0);
- } else {
- mirrors_list->add_item(TTR("(no templates for development builds)"), 0);
- mirrors_list->set_disabled(true);
- }
download_install_hb->add_child(mirrors_list);
request_mirrors = memnew(HTTPRequest);
@@ -972,24 +1023,17 @@ ExportTemplateManager::ExportTemplateManager() {
mirror_options_button = memnew(MenuButton);
mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR);
mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL);
- mirror_options_button->set_disabled(!downloads_available);
download_install_hb->add_child(mirror_options_button);
mirror_options_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk));
download_install_hb->add_spacer();
- Button *download_current_button = memnew(Button);
+ download_current_button = memnew(Button);
download_current_button->set_text(TTR("Download and Install"));
download_current_button->set_tooltip_text(TTR("Download and install templates for the current version from the best possible mirror."));
download_install_hb->add_child(download_current_button);
download_current_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_download_current));
- // Update downloads buttons to prevent unsupported downloads.
- if (!downloads_available) {
- download_current_button->set_disabled(true);
- download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
- }
-
HBoxContainer *install_file_hb = memnew(HBoxContainer);
install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END);
install_options_vb->add_child(install_file_hb);
diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h
index 5227e43a6e..ccfe568d32 100644
--- a/editor/export/export_template_manager.h
+++ b/editor/export/export_template_manager.h
@@ -46,7 +46,6 @@ class ExportTemplateManager : public AcceptDialog {
GDCLASS(ExportTemplateManager, AcceptDialog);
bool current_version_exists = false;
- bool downloads_available = true;
bool mirrors_available = false;
bool is_refreshing_mirrors = false;
bool is_downloading_templates = false;
@@ -74,6 +73,7 @@ class ExportTemplateManager : public AcceptDialog {
Label *download_progress_label = nullptr;
HTTPRequest *download_templates = nullptr;
Button *install_file_button = nullptr;
+ Button *download_current_button = nullptr;
HTTPRequest *request_mirrors = nullptr;
enum TemplatesAction {
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index bfb7123925..99cd2ad526 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -49,6 +49,7 @@
#include "editor/gui/editor_scene_tabs.h"
#include "editor/import/3d/scene_import_settings.h"
#include "editor/import_dock.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/scene_create_dialog.h"
#include "editor/scene_tree_dock.h"
@@ -2556,6 +2557,9 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
String dir = ProjectSettings::get_singleton()->globalize_path(fpath);
ScriptEditor::get_singleton()->open_text_file_create_dialog(dir);
} break;
+ default:
+ EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected);
+ break;
}
}
@@ -3178,6 +3182,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Script")), TTR("Script..."), FILE_NEW_SCRIPT);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE);
new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE);
+
+ EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths);
p_popup->add_separator();
}
@@ -3317,6 +3323,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
current_path = fpath;
}
+
+ EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths);
}
void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
@@ -3546,6 +3554,10 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
} else if (ED_IS_SHORTCUT("editor/open_search", p_event)) {
focus_on_filter();
} else {
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event);
+ if (match_option) {
+ _tree_rmb_option(match_option);
+ }
return;
}
diff --git a/editor/icons/MemberConstructor.svg b/editor/icons/MemberConstructor.svg
new file mode 100644
index 0000000000..0e61768739
--- /dev/null
+++ b/editor/icons/MemberConstructor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm6 2v-2H6a1 1 0 0 1 0-2h1V7H6a3 3 0 0 0 0 6z"/></svg> \ No newline at end of file
diff --git a/editor/icons/MemberOperator.svg b/editor/icons/MemberOperator.svg
new file mode 100644
index 0000000000..a00d990e26
--- /dev/null
+++ b/editor/icons/MemberOperator.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm4-4a3 3 0 0 0 0 6 3 3 0 0 0 0-6zm0 2a1 1 0 0 1 0 2 1 1 0 0 1 0-2z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapKeys.svg b/editor/icons/SnapKeys.svg
new file mode 100644
index 0000000000..813781b801
--- /dev/null
+++ b/editor/icons/SnapKeys.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="M 5,1 1,5 5,9 9,5 Z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapTimeline.svg b/editor/icons/SnapTimeline.svg
new file mode 100644
index 0000000000..b558545f5a
--- /dev/null
+++ b/editor/icons/SnapTimeline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="m 1,2 3,3 v 9 H 6 V 5 L 9,2 Z"/></svg> \ No newline at end of file
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
index a8886b3818..64bec0532b 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
@@ -48,7 +48,7 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo
// TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options).
// get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values.
// r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array()));
- r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array()));
+ r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s:", Variant::STRING_NAME)), Array()));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15));
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/base_height_adjustment", PROPERTY_HINT_RANGE, "-1,1,0.01"), 0.0));
}
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 853e3e813b..5c28213ca7 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -446,7 +446,7 @@ static String _fixstr(const String &p_what, const String &p_str) {
}
static void _pre_gen_shape_list(Ref<ImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) {
- ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value.");
+ ERR_FAIL_COND_MSG(mesh.is_null(), "Cannot generate shape list with null mesh value.");
if (!p_convex) {
Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape();
r_shape_list.push_back(shape);
@@ -649,6 +649,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
String name = p_node->get_name();
NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints.
+ Ref<Resource> original_meta = memnew(Resource); // Create temp resource to hold original meta
+ original_meta->merge_meta_from(p_node);
+
bool isroot = p_node == p_root;
if (!isroot && _teststr(name, "noimp")) {
@@ -1022,6 +1025,8 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path));
r_node_renames.push_back({ original_path, p_node });
}
+ // If we created new node instead, merge meta values from the original node.
+ p_node->merge_meta_from(*original_meta);
}
return p_node;
@@ -2452,6 +2457,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_transform(src_mesh_node->get_transform());
mesh_node->set_skin(src_mesh_node->get_skin());
mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path());
+ mesh_node->merge_meta_from(src_mesh_node);
+
if (src_mesh_node->get_mesh().is_valid()) {
Ref<ArrayMesh> mesh;
if (!src_mesh_node->get_mesh()->has_mesh()) {
@@ -2599,6 +2606,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
for (int i = 0; i < mesh->get_surface_count(); i++) {
mesh_node->set_surface_override_material(i, src_mesh_node->get_surface_material(i));
}
+ mesh->merge_meta_from(*src_mesh_node->get_mesh());
}
}
@@ -3096,6 +3104,19 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
}
}
+ // Apply RESET animation before serializing.
+ if (_scene_import_type == "PackedScene") {
+ int scene_child_count = scene->get_child_count();
+ for (int i = 0; i < scene_child_count; i++) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i));
+ if (ap) {
+ if (ap->can_apply_reset()) {
+ ap->apply_reset();
+ }
+ }
+ }
+ }
+
if (post_import_script.is_valid()) {
post_import_script->init(p_source_file);
scene = post_import_script->post_import(scene);
@@ -3114,7 +3135,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
progress.step(TTR("Saving..."), 104);
int flags = 0;
- if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
+ if (EditorSettings::get_singleton() && EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
flags |= ResourceSaver::FLAG_COMPRESS;
}
diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp
index 590e3a9ede..c526ca0b0c 100644
--- a/editor/import/dynamic_font_import_settings.cpp
+++ b/editor/import/dynamic_font_import_settings.cpp
@@ -509,7 +509,7 @@ void DynamicFontImportSettingsDialog::_variation_add() {
Ref<DynamicFontImportSettingsData> import_variation_data;
import_variation_data.instantiate();
import_variation_data->owner = this;
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
for (List<ResourceImporter::ImportOption>::Element *E = options_variations.front(); E; E = E->next()) {
import_variation_data->defaults[E->get().option.name] = E->get().default_value;
@@ -529,7 +529,7 @@ void DynamicFontImportSettingsDialog::_variation_selected() {
TreeItem *vars_item = vars_list->get_selected();
if (vars_item) {
Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
inspector_vars->edit(import_variation_data.ptr());
import_variation_data->notify_property_list_changed();
@@ -588,14 +588,14 @@ void DynamicFontImportSettingsDialog::_variations_validate() {
}
for (TreeItem *vars_item_a = vars_list_root->get_first_child(); vars_item_a; vars_item_a = vars_item_a->get_next()) {
Ref<DynamicFontImportSettingsData> import_variation_data_a = vars_item_a->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data_a);
+ ERR_FAIL_COND(import_variation_data_a.is_null());
for (TreeItem *vars_item_b = vars_list_root->get_first_child(); vars_item_b; vars_item_b = vars_item_b->get_next()) {
if (vars_item_b != vars_item_a) {
bool match = true;
for (const KeyValue<StringName, Variant> &E : import_variation_data_a->settings) {
Ref<DynamicFontImportSettingsData> import_variation_data_b = vars_item_b->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data_b);
+ ERR_FAIL_COND(import_variation_data_b.is_null());
match = match && (import_variation_data_b->settings[E.key] == E.value);
}
if (match) {
@@ -956,7 +956,7 @@ void DynamicFontImportSettingsDialog::_re_import() {
Array configurations;
for (TreeItem *vars_item = vars_list_root->get_first_child(); vars_item; vars_item = vars_item->get_next()) {
Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0);
- ERR_FAIL_NULL(import_variation_data);
+ ERR_FAIL_COND(import_variation_data.is_null());
Dictionary preload_config;
preload_config["name"] = vars_item->get_text(0);
@@ -1107,7 +1107,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
inspector_general->edit(nullptr);
text_settings_data.instantiate();
- ERR_FAIL_NULL(text_settings_data);
+ ERR_FAIL_COND(text_settings_data.is_null());
text_settings_data->owner = this;
@@ -1137,7 +1137,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
Ref<ConfigFile> config;
config.instantiate();
- ERR_FAIL_NULL(config);
+ ERR_FAIL_COND(config.is_null());
Error err = config->load(p_path + ".import");
print_verbose("Loading import settings:");
@@ -1169,7 +1169,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) {
Ref<DynamicFontImportSettingsData> import_variation_data_custom;
import_variation_data_custom.instantiate();
- ERR_FAIL_NULL(import_variation_data_custom);
+ ERR_FAIL_COND(import_variation_data_custom.is_null());
import_variation_data_custom->owner = this;
for (List<ResourceImporter::ImportOption>::Element *F = options_variations.front(); F; F = F->next()) {
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index 212a4160bf..552815a6af 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -510,7 +510,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source
}
bool can_compress_hdr = r_texture_import->hdr_compression > 0;
- ERR_FAIL_NULL(r_texture_import->image);
+ ERR_FAIL_COND(r_texture_import->image.is_null());
bool is_hdr = (r_texture_import->image->get_format() >= Image::FORMAT_RF && r_texture_import->image->get_format() <= Image::FORMAT_RGBE9995);
ERR_FAIL_NULL(r_texture_import->slices);
// Can compress hdr, but hdr with alpha is not compressible.
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index ce3411bf41..77b3629b07 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -90,7 +90,8 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1));
- r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0));
+ // Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature.
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 2));
}
Error ResourceImporterWAV::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) {
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index a8f8e9ef11..16f4aeda95 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -171,7 +171,7 @@ void ImportDock::_add_keep_import_option(const String &p_importer_name) {
void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_config) {
// Set the importer class to fetch the correct class in the XML class reference.
// This allows tooltips to display when hovering properties.
- if (params->importer != nullptr) {
+ if (params->importer.is_valid()) {
// Null check to avoid crashing if the "Keep File (exported as is)" mode is selected.
import_opts->set_object_class(params->importer->get_class_name());
}
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index aa075c80c3..dc07403213 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -190,7 +190,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
}
int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current);
- EditorUndoRedoManager::get_singleton()->clear_history(true, history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(history_id);
EditorNode::get_singleton()->edit_item(current, inspector);
}
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 660e4647a1..b882112950 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -224,6 +224,69 @@ void AnimationPlayerEditor::_autoplay_pressed() {
}
}
+void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) {
+ if (_get_current().is_empty()) {
+ return;
+ }
+
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+
+ double current_time = player->get_current_animation_position();
+ // Offset the time to avoid finding the same keyframe with Animation::track_find_key().
+ double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2);
+ double current_time_offset = current_time + (p_backward ? -time_offset : time_offset);
+
+ float nearest_key_time = p_backward ? 0 : anim->get_length();
+ int track_count = anim->get_track_count();
+ bool bezier_active = track_editor->is_bezier_editor_active();
+
+ Node *root = get_tree()->get_edited_scene_root();
+ EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection();
+
+ Vector<int> selected_tracks;
+ for (int i = 0; i < track_count; ++i) {
+ if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) {
+ selected_tracks.push_back(i);
+ }
+ }
+
+ // Find the nearest keyframe in selection if the scene has selected nodes
+ // or the nearest keyframe in the entire animation otherwise.
+ if (selected_tracks.size() > 0) {
+ for (int track : selected_tracks) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ } else {
+ for (int track = 0; track < track_count; ++track) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ }
+
+ player->seek_internal(nearest_key_time, true, true, true);
+ frame->set_value(nearest_key_time);
+ track_editor->set_anim_pos(nearest_key_time);
+}
+
void AnimationPlayerEditor::_play_pressed() {
String current = _get_current();
@@ -474,17 +537,12 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
}
float AnimationPlayerEditor::_get_editor_step() const {
- // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
- if (track_editor->is_snap_enabled()) {
- const String current = player->get_assigned_animation();
- const Ref<Animation> anim = player->get_animation(current);
- ERR_FAIL_COND_V(!anim.is_valid(), 0.0);
+ const String current = player->get_assigned_animation();
+ const Ref<Animation> anim = player->get_animation(current);
+ ERR_FAIL_COND_V(anim.is_null(), 0.0);
- // Use more precise snapping when holding Shift
- return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
- }
-
- return 0.0f;
+ // Use more precise snapping when holding Shift
+ return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
}
void AnimationPlayerEditor::_animation_name_edited() {
@@ -1290,7 +1348,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o
anim = player->get_animation(current);
double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
- if (track_editor->is_snap_enabled()) {
+ if (track_editor->is_snap_timeline_enabled()) {
pos = Math::snapped(pos, _get_editor_step());
}
pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR.
@@ -1408,11 +1466,17 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel
}
updating = true;
- frame->set_value(Math::snapped(p_pos, _get_editor_step()));
+ frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos);
updating = false;
_seek_value_changed(p_pos, p_timeline_only);
}
+void AnimationPlayerEditor::_animation_update_key_frame() {
+ if (player) {
+ player->advance(0);
+ }
+}
+
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
String current = _get_current();
@@ -1505,30 +1569,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventKey> k = p_ev;
- if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
- switch (k->get_keycode()) {
- case Key::A: {
- if (!k->is_shift_pressed()) {
- _play_bw_from_pressed();
- } else {
- _play_bw_pressed();
- }
- accept_event();
- } break;
- case Key::S: {
- _stop_pressed();
- accept_event();
- } break;
- case Key::D: {
- if (!k->is_shift_pressed()) {
- _play_from_pressed();
- } else {
- _play_pressed();
- }
- accept_event();
- } break;
- default:
- break;
+ if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) {
+ _stop_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) {
+ _play_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) {
+ _play_bw_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) {
+ _play_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) {
+ _play_bw_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(false);
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(true);
+ accept_event();
}
}
}
@@ -1874,6 +1936,7 @@ bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
void AnimationPlayerEditor::_bind_methods() {
// Needed for UndoRedo.
ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
+ ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1902,27 +1965,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
play_bw_from = memnew(Button);
play_bw_from->set_theme_type_variation("FlatButton");
- play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
+ play_bw_from->set_tooltip_text(TTR("Play Animation Backwards"));
hb->add_child(play_bw_from);
play_bw = memnew(Button);
play_bw->set_theme_type_variation("FlatButton");
- play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
+ play_bw->set_tooltip_text(TTR("Play Animation Backwards from End"));
hb->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);
- stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
play = memnew(Button);
play->set_theme_type_variation("FlatButton");
- play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
+ play->set_tooltip_text(TTR("Play Animation from Start"));
hb->add_child(play);
play_from = memnew(Button);
play_from->set_theme_type_variation("FlatButton");
- play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
+ play_from->set_tooltip_text(TTR("Play Animation"));
hb->add_child(play_from);
frame = memnew(SpinBox);
@@ -2138,6 +2201,14 @@ void fragment() {
}
)");
RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
+
+ ED_SHORTCUT("animation_editor/stop_animation", TTR("Pause/Stop Animation"), Key::S);
+ ED_SHORTCUT("animation_editor/play_animation", TTR("Play Animation"), Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_backwards", TTR("Play Animation Backwards"), Key::A);
+ ED_SHORTCUT("animation_editor/play_animation_from_start", TTR("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_from_end", TTR("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A);
+ ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTR("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D);
+ ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTR("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A);
}
AnimationPlayerEditor::~AnimationPlayerEditor() {
@@ -2165,7 +2236,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V
return;
}
te->_clear_selection();
- te->insert_value_key(p_keyed, p_value, p_advance);
+ te->insert_value_key(p_keyed, p_advance);
}
void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4a3b1f37ab..860d421b91 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -178,6 +178,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _select_anim_by_name(const String &p_anim);
float _get_editor_step() const;
+ void _go_to_nearest_keyframe(bool p_backward);
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
@@ -216,6 +217,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false);
void _animation_key_editor_anim_len_changed(float p_len);
+ void _animation_update_key_frame();
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
void _animation_tool_menu(int p_option);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index ec42cb31be..6e41e98360 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -4331,13 +4331,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Node2D *n2d = Object::cast_to<Node2D>(ci);
if (key_pos && p_location) {
- te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
+ te->insert_node_value_key(n2d, "position", p_on_existing);
}
if (key_rot && p_rotation) {
- te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
+ te->insert_node_value_key(n2d, "rotation", p_on_existing);
}
if (key_scale && p_scale) {
- te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
+ te->insert_node_value_key(n2d, "scale", p_on_existing);
}
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
@@ -4363,13 +4363,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
if (has_chain && ik_chain.size()) {
for (Node2D *&F : ik_chain) {
if (key_pos) {
- te->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
+ te->insert_node_value_key(F, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
+ te->insert_node_value_key(F, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
+ te->insert_node_value_key(F, "scale", p_on_existing);
}
}
}
@@ -4379,13 +4379,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Control *ctrl = Object::cast_to<Control>(ci);
if (key_pos) {
- te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing);
+ te->insert_node_value_key(ctrl, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing);
+ te->insert_node_value_key(ctrl, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing);
+ te->insert_node_value_key(ctrl, "size", p_on_existing);
}
}
}
diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp
new file mode 100644
index 0000000000..86d3c0a896
--- /dev/null
+++ b/editor/plugins/editor_context_menu_plugin.cpp
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* editor_context_menu_plugin.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 "editor_context_menu_plugin.h"
+
+#include "core/input/shortcut.h"
+#include "editor/editor_node.h"
+#include "scene/resources/texture.h"
+
+void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) {
+ GDVIRTUAL_CALL(_popup_menu, p_paths);
+}
+
+void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable) {
+ context_menu_shortcuts.insert(p_shortcut, p_callable);
+}
+
+void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) {
+ ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered.");
+ ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached.");
+ ContextMenuItem item;
+ item.item_name = p_name;
+ item.callable = p_callable;
+ item.icon = p_texture;
+ item.shortcut = p_shortcut;
+ item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size();
+ context_menu_items.insert(p_name, item);
+}
+
+void EditorContextMenuPlugin::clear_context_menu_items() {
+ context_menu_items.clear();
+}
+
+void EditorContextMenuPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut);
+ ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>()));
+ GDVIRTUAL_BIND(_popup_menu, "paths");
+}
diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h
new file mode 100644
index 0000000000..db05e6c6c7
--- /dev/null
+++ b/editor/plugins/editor_context_menu_plugin.h
@@ -0,0 +1,70 @@
+/**************************************************************************/
+/* editor_context_menu_plugin.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 EDITOR_CONTEXT_MENU_PLUGIN_H
+#define EDITOR_CONTEXT_MENU_PLUGIN_H
+
+#include "core/object/gdvirtual.gen.inc"
+#include "core/object/ref_counted.h"
+
+class Texture2D;
+class Shortcut;
+
+class EditorContextMenuPlugin : public RefCounted {
+ GDCLASS(EditorContextMenuPlugin, RefCounted);
+
+public:
+ int start_idx;
+
+ inline static constexpr int MAX_ITEMS = 100;
+
+ struct ContextMenuItem {
+ int idx = 0;
+ String item_name;
+ Callable callable;
+ Ref<Texture2D> icon;
+ Ref<Shortcut> shortcut;
+ };
+ HashMap<String, ContextMenuItem> context_menu_items;
+ HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts;
+
+protected:
+ static void _bind_methods();
+
+ GDVIRTUAL1(_popup_menu, Vector<String>);
+
+public:
+ virtual void add_options(const Vector<String> &p_paths);
+ void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable);
+ void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut);
+ void clear_context_menu_items();
+};
+
+#endif // EDITOR_CONTEXT_MENU_PLUGIN_H
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index d9f60e155d..0ee67e08ba 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -40,12 +40,14 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export.h"
+#include "editor/export/editor_export_platform.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_title_bar.h"
#include "editor/import/3d/resource_importer_scene.h"
#include "editor/import/editor_import_plugin.h"
#include "editor/inspector_dock.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
@@ -441,6 +443,16 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte
EditorExport::get_singleton()->remove_export_plugin(p_exporter);
}
+void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->add_export_platform(p_platform);
+}
+
+void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->remove_export_platform(p_platform);
+}
+
void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) {
ERR_FAIL_COND(!p_gizmo_plugin.is_valid());
Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin);
@@ -479,6 +491,16 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo
ResourceImporterScene::remove_post_importer_plugin(p_plugin);
}
+void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+}
+
+void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) {
+ ERR_FAIL_COND(p_plugin.is_null());
+ EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin);
+}
+
int find(const PackedStringArray &a, const String &v) {
const String *r = a.ptr();
for (int j = 0; j < a.size(); ++j) {
@@ -608,6 +630,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin);
ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin);
ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin);
+ ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform);
+ ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform);
ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin);
@@ -616,6 +640,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_resource_conversion_plugin", "plugin"), &EditorPlugin::remove_resource_conversion_plugin);
ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled);
ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled);
+ ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin);
+ ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin);
ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface);
ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog);
@@ -681,6 +707,11 @@ void EditorPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP);
BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM);
+
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM);
+ BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR);
+ BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE);
}
EditorUndoRedoManager *EditorPlugin::get_undo_redo() {
diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h
index f6c4b35407..35f65f03cd 100644
--- a/editor/plugins/editor_plugin.h
+++ b/editor/plugins/editor_plugin.h
@@ -41,6 +41,7 @@ class PopupMenu;
class EditorDebuggerPlugin;
class EditorExport;
class EditorExportPlugin;
+class EditorExportPlatform;
class EditorImportPlugin;
class EditorInspectorPlugin;
class EditorInterface;
@@ -52,6 +53,7 @@ class EditorToolAddons;
class EditorTranslationParserPlugin;
class EditorUndoRedoManager;
class ScriptCreateDialog;
+class EditorContextMenuPlugin;
class EditorPlugin : public Node {
GDCLASS(EditorPlugin, Node);
@@ -101,6 +103,13 @@ public:
AFTER_GUI_INPUT_CUSTOM,
};
+ enum ContextMenuSlot {
+ CONTEXT_SLOT_SCENE_TREE,
+ CONTEXT_SLOT_FILESYSTEM,
+ CONTEXT_SLOT_SCRIPT_EDITOR,
+ CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE,
+ };
+
protected:
void _notification(int p_what);
@@ -224,6 +233,9 @@ public:
void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
+ void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
+
void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
@@ -245,6 +257,9 @@ public:
void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin);
+ void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+ void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin);
+
void enable_plugin();
void disable_plugin();
@@ -255,6 +270,7 @@ public:
VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer);
VARIANT_ENUM_CAST(EditorPlugin::DockSlot);
VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput);
+VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot);
typedef EditorPlugin *(*EditorPluginCreateFunc)();
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
index ae24b4250e..c277ec8cd3 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
@@ -293,9 +293,15 @@ Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
void Joint3DGizmoPlugin::incremental_update_gizmos() {
if (!current_gizmos.is_empty()) {
- update_idx++;
- update_idx = update_idx % current_gizmos.size();
- redraw(current_gizmos.get(update_idx));
+ HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn);
+ if (E) {
+ ++E;
+ }
+ if (!E) {
+ E = current_gizmos.begin();
+ }
+ redraw(*E);
+ last_drawn = *E;
}
}
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
index 79fe40d1b2..25b08d71c6 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
@@ -37,7 +37,7 @@ class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
Timer *update_timer = nullptr;
- uint64_t update_idx = 0;
+ EditorNode3DGizmo *last_drawn = nullptr;
void incremental_update_gizmos();
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 67d5e44ce5..d4d918743d 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -982,7 +982,7 @@ void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool
handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
- Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons));
+ Ref<Texture2D> handle_t = p_icon.is_valid() ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons));
handle_material->set_point_size(handle_t->get_width());
handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
handle_material->set_albedo(Color(1, 1, 1));
@@ -1060,7 +1060,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
ref->set_node_3d(p_spatial);
ref->set_hidden(current_state == HIDDEN);
- current_gizmos.push_back(ref.ptr());
+ current_gizmos.insert(ref.ptr());
return ref;
}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
index c4b275032a..1916bc2058 100644
--- a/editor/plugins/node_3d_editor_gizmos.h
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -157,7 +157,7 @@ public:
protected:
int current_state;
- List<EditorNode3DGizmo *> current_gizmos;
+ HashSet<EditorNode3DGizmo *> current_gizmos;
HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
static void _bind_methods();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 8b0f4a64a7..8679dc25a4 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -4506,8 +4506,8 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
Node *instantiated_scene = nullptr;
- if (mesh != nullptr || scene != nullptr) {
- if (mesh != nullptr) {
+ if (mesh.is_valid() || scene.is_valid()) {
+ if (mesh.is_valid()) {
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
mesh_instance->set_mesh(mesh);
@@ -4538,7 +4538,7 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
}
}
- if (scene != nullptr) {
+ if (scene.is_valid()) {
instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
}
@@ -5462,7 +5462,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
- preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD_OR_CTRL | Key::P));
+ // Using Control even on macOS to avoid conflict with Quick Open shortcut.
+ preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P));
vbox->add_child(preview_camera);
preview_camera->set_h_size_flags(0);
preview_camera->hide();
@@ -9291,7 +9292,7 @@ struct _GizmoPluginNameComparator {
};
void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
- ERR_FAIL_NULL(p_plugin.ptr());
+ ERR_FAIL_COND(p_plugin.is_null());
gizmo_plugins_by_priority.push_back(p_plugin);
gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();
diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp
index 5b23cf44d0..24cc15d656 100644
--- a/editor/plugins/path_2d_editor_plugin.cpp
+++ b/editor/plugins/path_2d_editor_plugin.cpp
@@ -197,12 +197,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
const Vector2 new_point = xform.affine_inverse().xform(gpoint2);
curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action(TTR("Split Curve"));
- undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1);
- undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1);
-
- action = ACTION_MOVING_NEW_POINT;
+ action = ACTION_MOVING_NEW_POINT_FROM_SPLIT;
action_point = insertion_point + 1;
moving_from = curve->get_point_position(action_point);
moving_screen_from = gpoint2;
@@ -245,6 +240,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
undo_redo->commit_action(false);
} break;
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
+ undo_redo->create_action(TTR("Split Curve"));
+ undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point);
+ undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint);
+ undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point);
+ undo_redo->add_do_method(canvas_item_editor, "update_viewport");
+ undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
+ undo_redo->commit_action(false);
+ } break;
+
case ACTION_MOVING_IN: {
if (original_mouse_pos != gpoint) {
undo_redo->create_action(TTR("Move In-Control in Curve"));
@@ -295,7 +300,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
Vector2 gpoint = mm->get_position();
Ref<Curve2D> curve = node->get_curve();
- if (curve == nullptr) {
+ if (curve.is_null()) {
return true;
}
if (curve->get_point_count() < 2) {
@@ -350,7 +355,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
break;
case ACTION_MOVING_POINT:
- case ACTION_MOVING_NEW_POINT: {
+ case ACTION_MOVING_NEW_POINT:
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
curve->set_point_position(action_point, cpoint);
} break;
@@ -573,6 +579,10 @@ void Path2DEditor::_cancel_current_action() {
curve->remove_point(curve->get_point_count() - 1);
} break;
+ case ACTION_MOVING_NEW_POINT_FROM_SPLIT: {
+ curve->remove_point(action_point);
+ } break;
+
case ACTION_MOVING_IN: {
curve->set_point_in(action_point, moving_from);
curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length));
diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h
index a2857fddb7..f45b75a968 100644
--- a/editor/plugins/path_2d_editor_plugin.h
+++ b/editor/plugins/path_2d_editor_plugin.h
@@ -81,6 +81,7 @@ class Path2DEditor : public HBoxContainer {
ACTION_NONE,
ACTION_MOVING_POINT,
ACTION_MOVING_NEW_POINT,
+ ACTION_MOVING_NEW_POINT_FROM_SPLIT,
ACTION_MOVING_IN,
ACTION_MOVING_OUT,
};
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 496323aa2a..b8a309bc60 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -1482,7 +1482,7 @@ Polygon2DEditor::Polygon2DEditor() {
grid_settings = memnew(AcceptDialog);
grid_settings->set_title(TTR("Configure Grid:"));
- add_child(grid_settings);
+ uv_edit->add_child(grid_settings);
VBoxContainer *grid_settings_vb = memnew(VBoxContainer);
grid_settings->add_child(grid_settings_vb);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index a33fc47955..93c8ae5438 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -57,6 +57,7 @@
#include "editor/gui/editor_toaster.h"
#include "editor/inspector_dock.h"
#include "editor/node_dock.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/shader_editor_plugin.h"
#include "editor/plugins/text_shader_editor.h"
#include "editor/themes/editor_scale.h"
@@ -510,7 +511,7 @@ void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) {
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
se->set_executing_line(p_line);
}
}
@@ -526,7 +527,7 @@ void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) {
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) {
se->clear_executing_line();
}
}
@@ -578,8 +579,8 @@ void ScriptEditor::_clear_breakpoints() {
script_editor_cache->get_sections(&cached_editors);
for (const String &E : cached_editors) {
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false);
}
if (breakpoints.size() > 0) {
@@ -711,7 +712,7 @@ void ScriptEditor::_go_to_tab(int p_idx) {
}
Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
notify_script_changed(scr);
}
@@ -1017,7 +1018,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) {
}
Ref<TextFile> text_file = scr;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
se->apply_code();
_save_text_file(text_file, text_file->get_path());
break;
@@ -1228,7 +1229,7 @@ Ref<Script> ScriptEditor::_get_current_script() {
if (current) {
Ref<Script> scr = current->get_edited_resource();
- return scr != nullptr ? scr : nullptr;
+ return scr.is_valid() ? scr : nullptr;
} else {
return nullptr;
}
@@ -1398,6 +1399,16 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
+ // Context menu options.
+ if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ Ref<Resource> resource;
+ if (current) {
+ resource = current->get_edited_resource();
+ }
+ EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource);
+ return;
+ }
+
if (current) {
switch (p_option) {
case FILE_SAVE: {
@@ -1420,7 +1431,7 @@ void ScriptEditor::_menu_option(int p_option) {
Ref<TextFile> text_file = resource;
Ref<Script> scr = resource;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
file_dialog_option = FILE_SAVE_AS;
@@ -1449,7 +1460,7 @@ void ScriptEditor::_menu_option(int p_option) {
case FILE_TOOL_RELOAD_SOFT: {
Ref<Script> scr = current->get_edited_resource();
- if (scr == nullptr || scr.is_null()) {
+ if (scr.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading."));
break;
}
@@ -1463,7 +1474,7 @@ void ScriptEditor::_menu_option(int p_option) {
case FILE_RUN: {
Ref<Script> scr = current->get_edited_resource();
- if (scr == nullptr || scr.is_null()) {
+ if (scr.is_null()) {
EditorToaster::get_singleton()->popup_str(TTR("Cannot run the edited file because it's not a script."), EditorToaster::SEVERITY_WARNING);
break;
}
@@ -1796,7 +1807,7 @@ void ScriptEditor::_close_builtin_scripts_from_scene(const String &p_scene) {
if (se) {
Ref<Script> scr = se->get_edited_resource();
- if (scr == nullptr || !scr.is_valid()) {
+ if (scr.is_null()) {
continue;
}
@@ -1820,6 +1831,48 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) {
emit_signal(SNAME("editor_script_changed"), p_script);
}
+Vector<String> ScriptEditor::_get_breakpoints() {
+ Vector<String> ret;
+ HashSet<String> loaded_scripts;
+ for (int i = 0; i < tab_container->get_tab_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
+ if (!se) {
+ continue;
+ }
+
+ Ref<Script> scr = se->get_edited_resource();
+ if (scr.is_null()) {
+ continue;
+ }
+
+ String base = scr->get_path();
+ loaded_scripts.insert(base);
+ if (base.is_empty() || base.begins_with("local://")) {
+ continue;
+ }
+
+ PackedInt32Array bpoints = se->get_breakpoints();
+ for (int32_t bpoint : bpoints) {
+ ret.push_back(base + ":" + itos((int)bpoint + 1));
+ }
+ }
+
+ // Load breakpoints that are in closed scripts.
+ List<String> cached_editors;
+ script_editor_cache->get_sections(&cached_editors);
+ for (const String &E : cached_editors) {
+ if (loaded_scripts.has(E)) {
+ continue;
+ }
+
+ Array breakpoints = _get_cached_breakpoints_for_script(E);
+ for (int breakpoint : breakpoints) {
+ ret.push_back(E + ":" + itos((int)breakpoint + 1));
+ }
+ }
+ return ret;
+}
+
void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
HashSet<String> loaded_scripts;
for (int i = 0; i < tab_container->get_tab_count(); i++) {
@@ -1829,19 +1882,19 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Ref<Script> scr = se->get_edited_resource();
- if (scr == nullptr) {
+ if (scr.is_null()) {
continue;
}
String base = scr->get_path();
loaded_scripts.insert(base);
- if (base.begins_with("local://") || base.is_empty()) {
+ if (base.is_empty() || base.begins_with("local://")) {
continue;
}
PackedInt32Array bpoints = se->get_breakpoints();
- for (int j = 0; j < bpoints.size(); j++) {
- p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1));
+ for (int32_t bpoint : bpoints) {
+ p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1));
}
}
@@ -1854,8 +1907,8 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1));
+ for (int breakpoint : breakpoints) {
+ p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1));
}
}
}
@@ -2458,7 +2511,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
continue;
}
- if ((scr != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
+ if ((scr.is_valid() && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
if (should_open) {
se->enable_editor(this);
@@ -2508,7 +2561,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
PackedStringArray languages = highlighter->_get_supported_languages();
// If script try language, else use extension.
- if (scr != nullptr) {
+ if (scr.is_valid()) {
if (languages.has(scr->get_language()->get_name())) {
se->set_syntax_highlighter(highlighter);
highlighter_set = true;
@@ -2618,7 +2671,7 @@ void ScriptEditor::save_current_script() {
Ref<TextFile> text_file = resource;
Ref<Script> scr = resource;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
return;
@@ -2669,7 +2722,7 @@ void ScriptEditor::save_all_scripts() {
Ref<TextFile> text_file = edited_res;
Ref<Script> scr = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
_save_text_file(text_file, text_file->get_path());
continue;
}
@@ -2746,7 +2799,7 @@ void ScriptEditor::_reload_scripts(bool p_refresh_only) {
}
Ref<JSON> json = edited_res;
- if (json != nullptr) {
+ if (json.is_valid()) {
Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
ERR_CONTINUE(!rel_json.is_valid());
json->parse(rel_json->get_parsed_text(), true);
@@ -2944,8 +2997,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi
// If Script, update breakpoints with debugger.
Array breakpoints = _get_cached_breakpoints_for_script(p_new_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- int line = (int)breakpoints[i] + 1;
+ for (int breakpoint : breakpoints) {
+ int line = (int)breakpoint + 1;
EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false);
if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) {
EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true);
@@ -2969,8 +3022,8 @@ void ScriptEditor::_file_removed(const String &p_removed_file) {
// Check closed.
if (script_editor_cache->has_section(p_removed_file)) {
Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false);
}
script_editor_cache->erase_section(p_removed_file);
}
@@ -3262,6 +3315,11 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) {
_menu_option(WINDOW_MOVE_DOWN);
accept_event();
}
+ // Context menu shortcuts.
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event);
+ if (match_option) {
+ _menu_option(match_option);
+ }
}
void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) {
@@ -3296,7 +3354,7 @@ void ScriptEditor::_make_script_list_context_menu() {
context_menu->add_separator();
if (se) {
Ref<Script> scr = se->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
if (!scr.is_null() && scr->is_tool()) {
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT);
context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_RUN);
@@ -3320,6 +3378,17 @@ void ScriptEditor::_make_script_list_context_menu() {
context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1);
context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_SORT), tab_container->get_tab_count() <= 1);
+ // Context menu plugin.
+ Vector<String> selected_paths;
+ if (se) {
+ Ref<Resource> scr = se->get_edited_resource();
+ if (scr.is_valid()) {
+ String path = scr->get_path();
+ selected_paths.push_back(path);
+ }
+ }
+ EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths);
+
context_menu->set_position(get_screen_position() + get_local_mouse_position());
context_menu->reset_size();
context_menu->popup();
@@ -3425,8 +3494,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true);
}
}
@@ -3643,7 +3712,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) {
seb->ensure_focus();
Ref<Script> scr = seb->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
notify_script_changed(scr);
}
}
@@ -3682,7 +3751,7 @@ Vector<Ref<Script>> ScriptEditor::get_open_scripts() const {
}
Ref<Script> scr = se->get_edited_resource();
- if (scr != nullptr) {
+ if (scr.is_valid()) {
out_scripts.push_back(scr);
}
}
@@ -3972,6 +4041,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);
ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor);
ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors);
+ ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints);
ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);
ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);
@@ -4239,7 +4309,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
if (!make_floating->is_disabled()) {
// Override default ScreenSelect tooltip if multi-window support is available.
- make_floating->set_tooltip_text(TTR("Make the script editor floating."));
+ make_floating->set_tooltip_text(TTR("Make the script editor floating.\nRight-click to open the screen selector."));
}
menu_hb->add_child(make_floating);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 66885790a7..e0fac5d0c6 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -539,6 +539,7 @@ public:
_FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); }
bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true);
+ Vector<String> _get_breakpoints();
void get_breakpoints(List<String> *p_breakpoints);
PackedStringArray get_unsaved_scripts() const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 34557b26b4..7e8fba8b9e 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1843,7 +1843,8 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
const String &line = te->get_line(drop_at_line);
const bool is_empty_line = line_will_be_empty || line.is_empty() || te->get_first_non_whitespace_column(drop_at_line) == line.length();
- if (d.has("type") && String(d["type"]) == "resource") {
+ const String type = d.get("type", "");
+ if (type == "resource") {
Ref<Resource> resource = d["resource"];
if (resource.is_null()) {
return;
@@ -1868,11 +1869,11 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
}
- if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) {
- Array files = d["files"];
- for (int i = 0; i < files.size(); i++) {
- const String &path = String(files[i]);
+ if (type == "files" || type == "files_and_dirs") {
+ const PackedStringArray files = d["files"];
+ PackedStringArray parts;
+ for (const String &path : files) {
if (drop_modifier_pressed && ResourceLoader::exists(path)) {
Ref<Resource> resource = ResourceLoader::load(path);
if (resource.is_null()) {
@@ -1880,18 +1881,15 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
resource.instantiate();
resource->set_path_cache(path);
}
- text_to_drop += _get_dropped_resource_line(resource, is_empty_line);
+ parts.append(_get_dropped_resource_line(resource, is_empty_line));
} else {
- text_to_drop += _quote_drop_data(path);
- }
-
- if (i < files.size() - 1) {
- text_to_drop += is_empty_line ? "\n" : ", ";
+ parts.append(_quote_drop_data(path));
}
}
+ text_to_drop = String(is_empty_line ? "\n" : ", ").join(parts);
}
- if (d.has("type") && String(d["type"]) == "nodes") {
+ if (type == "nodes") {
Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene."));
@@ -1982,7 +1980,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
}
- if (d.has("type") && String(d["type"]) == "obj_property") {
+ if (type == "obj_property") {
bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals");
text_to_drop = add_literal ? "^" : "";
// It is unclear whether properties may contain single or double quotes.
@@ -2543,8 +2541,9 @@ void ScriptTextEditor::register_editor() {
ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B);
ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9);
- ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::PERIOD);
- ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::COMMA);
+ // Using Control for these shortcuts even on macOS because Command+Comma is taken for opening Editor Settings.
+ ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CTRL | Key::PERIOD);
+ ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CTRL | Key::COMMA);
ScriptEditor::register_create_script_editor_function(create_editor);
}
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index ea049756b7..597ef49004 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -802,7 +802,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
if (!make_floating->is_disabled()) {
// Override default ScreenSelect tooltip if multi-window support is available.
- make_floating->set_tooltip_text(TTR("Make the shader editor floating."));
+ make_floating->set_tooltip_text(TTR("Make the shader editor floating.\nRight-click to open the screen selector."));
}
menu_hb->add_child(make_floating);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 230d5809aa..dc4d4db3f8 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -47,6 +47,7 @@
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
+#include "scene/property_utils.h"
#include "scene/resources/3d/capsule_shape_3d.h"
#include "scene/resources/skeleton_profile.h"
#include "scene/resources/surface_tool.h"
@@ -65,7 +66,7 @@ void BoneTransformEditor::create_editors() {
// Position property.
position_property = memnew(EditorPropertyVector3());
- position_property->setup(-10000, 10000, 0.001f, true);
+ position_property->setup(-10000, 10000, 0.001, true);
position_property->set_label("Position");
position_property->set_selectable(false);
position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -74,7 +75,7 @@ void BoneTransformEditor::create_editors() {
// Rotation property.
rotation_property = memnew(EditorPropertyQuaternion());
- rotation_property->setup(-10000, 10000, 0.001f, true);
+ rotation_property->setup(-10000, 10000, 0.001, true);
rotation_property->set_label("Rotation");
rotation_property->set_selectable(false);
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -83,7 +84,7 @@ void BoneTransformEditor::create_editors() {
// Scale property.
scale_property = memnew(EditorPropertyVector3());
- scale_property->setup(-10000, 10000, 0.001f, true);
+ scale_property->setup(-10000, 10000, 0.001, true, true);
scale_property->set_label("Scale");
scale_property->set_selectable(false);
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -97,7 +98,7 @@ void BoneTransformEditor::create_editors() {
// Transform/Matrix property.
rest_matrix = memnew(EditorPropertyTransform3D());
- rest_matrix->setup(-10000, 10000, 0.001f, true);
+ rest_matrix->setup(-10000, 10000, 0.001, true);
rest_matrix->set_label("Transform");
rest_matrix->set_selectable(false);
rest_section->get_vbox()->add_child(rest_matrix);
@@ -122,6 +123,13 @@ void BoneTransformEditor::_value_changed(const String &p_property, const Variant
undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property));
undo_redo->add_do_property(skeleton, p_property, p_value);
+
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ undo_redo->add_do_method(se, "update_joint_tree");
+ undo_redo->add_undo_method(se, "update_joint_tree");
+ }
+
undo_redo->commit_action();
}
}
@@ -189,26 +197,31 @@ void BoneTransformEditor::_update_properties() {
if (split[2] == "enabled") {
enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
enabled_checkbox->update_property();
+ enabled_checkbox->update_editor_property_status();
enabled_checkbox->queue_redraw();
}
if (split[2] == "position") {
position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
position_property->update_property();
+ position_property->update_editor_property_status();
position_property->queue_redraw();
}
if (split[2] == "rotation") {
rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rotation_property->update_property();
+ rotation_property->update_editor_property_status();
rotation_property->queue_redraw();
}
if (split[2] == "scale") {
scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
scale_property->update_property();
+ scale_property->update_editor_property_status();
scale_property->queue_redraw();
}
if (split[2] == "rest") {
rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rest_matrix->update_property();
+ rest_matrix->update_editor_property_status();
rest_matrix->queue_redraw();
}
}
@@ -232,6 +245,11 @@ void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enable
skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled);
};
+void Skeleton3DEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("update_all"), &Skeleton3DEditor::update_all);
+ ClassDB::bind_method(D_METHOD("update_joint_tree"), &Skeleton3DEditor::update_joint_tree);
+}
+
void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
if (!skeleton) {
return;
@@ -294,6 +312,10 @@ void Skeleton3DEditor::reset_pose(const bool p_all_bones) {
ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone));
ur->add_do_method(skeleton, "reset_bone_pose", selected_bone);
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -357,6 +379,10 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -620,9 +646,12 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
}
ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx));
ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx);
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx);
- update_joint_tree();
ur->commit_action();
}
@@ -655,6 +684,107 @@ void Skeleton3DEditor::_joint_tree_selection_changed() {
void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
}
+void Skeleton3DEditor::_joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button) {
+ if (!skeleton) {
+ return;
+ }
+
+ TreeItem *tree_item = Object::cast_to<TreeItem>(p_item);
+ if (tree_item) {
+ String tree_item_metadata = tree_item->get_metadata(0);
+
+ String bone_enabled_property = tree_item_metadata + "/enabled";
+ String bone_parent_property = tree_item_metadata + "/parent";
+ String bone_name_property = tree_item_metadata + "/name";
+ String bone_position_property = tree_item_metadata + "/position";
+ String bone_rotation_property = tree_item_metadata + "/rotation";
+ String bone_scale_property = tree_item_metadata + "/scale";
+ String bone_rest_property = tree_item_metadata + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Revert Bone"));
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ if (can_revert_enabled) {
+ bool is_valid = false;
+ Variant new_enabled = EditorPropertyRevert::get_property_revert_value(skeleton, bone_enabled_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_enabled_property, current_enabled);
+ ur->add_do_method(skeleton, "set", bone_enabled_property, new_enabled);
+ }
+ }
+
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ if (can_revert_parent) {
+ bool is_valid = false;
+ Variant new_parent = EditorPropertyRevert::get_property_revert_value(skeleton, bone_parent_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_parent_property, current_parent);
+ ur->add_do_method(skeleton, "set", bone_parent_property, new_parent);
+ }
+ }
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ if (can_revert_name) {
+ bool is_valid = false;
+ Variant new_name = EditorPropertyRevert::get_property_revert_value(skeleton, bone_name_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_name_property, current_name);
+ ur->add_do_method(skeleton, "set", bone_name_property, new_name);
+ }
+ }
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ if (can_revert_position) {
+ bool is_valid = false;
+ Variant new_position = EditorPropertyRevert::get_property_revert_value(skeleton, bone_position_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_position_property, current_position);
+ ur->add_do_method(skeleton, "set", bone_position_property, new_position);
+ }
+ }
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ if (can_revert_rotation) {
+ bool is_valid = false;
+ Variant new_rotation = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rotation_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rotation_property, current_rotation);
+ ur->add_do_method(skeleton, "set", bone_rotation_property, new_rotation);
+ }
+ }
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ if (can_revert_scale) {
+ bool is_valid = false;
+ Variant new_scale = EditorPropertyRevert::get_property_revert_value(skeleton, bone_scale_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_scale_property, current_scale);
+ ur->add_do_method(skeleton, "set", bone_scale_property, new_scale);
+ }
+ }
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+ if (can_revert_rest) {
+ bool is_valid = false;
+ Variant new_rest = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rest_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rest_property, current_rest);
+ ur->add_do_method(skeleton, "set", bone_rest_property, new_rest);
+ }
+ }
+
+ ur->add_undo_method(this, "update_all");
+ ur->add_do_method(this, "update_all");
+
+ ur->commit_action();
+ }
+ return;
+}
+
void Skeleton3DEditor::_update_properties() {
if (pose_editor) {
pose_editor->_update_properties();
@@ -693,15 +823,52 @@ void Skeleton3DEditor::update_joint_tree() {
joint_item->set_selectable(0, true);
joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
+ String bone_enabled_property = "bones/" + itos(current_bone_idx) + "/enabled";
+ String bone_parent_property = "bones/" + itos(current_bone_idx) + "/parent";
+ String bone_name_property = "bones/" + itos(current_bone_idx) + "/name";
+ String bone_position_property = "bones/" + itos(current_bone_idx) + "/position";
+ String bone_rotation_property = "bones/" + itos(current_bone_idx) + "/rotation";
+ String bone_scale_property = "bones/" + itos(current_bone_idx) + "/scale";
+ String bone_rest_property = "bones/" + itos(current_bone_idx) + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+
+ if (can_revert_enabled || can_revert_parent || can_revert_name || can_revert_position || can_revert_rotation || can_revert_scale || can_revert_rest) {
+ joint_item->add_button(0, get_editor_theme_icon(SNAME("ReloadSmall")), JOINT_BUTTON_REVERT, false, TTR("Revert"));
+ }
+
// Add the bone's children to the list of bones to be processed.
Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);
int child_bone_size = current_bone_child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(current_bone_child_bones[i]);
}
+
+ if (current_bone_idx == selected_bone) {
+ joint_item->select(0);
+ }
}
}
+void Skeleton3DEditor::update_all() {
+ _update_properties();
+ update_joint_tree();
+}
+
void Skeleton3DEditor::create_editors() {
set_h_size_flags(SIZE_EXPAND_FILL);
set_focus_mode(FOCUS_ALL);
@@ -840,6 +1007,7 @@ void Skeleton3DEditor::_notification(int p_what) {
joint_tree->connect(SceneStringName(item_selected), callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
+ joint_tree->connect("button_clicked", callable_mp(this, &Skeleton3DEditor::_joint_tree_button_clicked));
#ifdef TOOLS_ENABLED
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_update_properties));
@@ -1337,6 +1505,10 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c
ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale());
}
}
+
+ ur->add_do_method(se, "update_joint_tree");
+ ur->add_undo_method(se, "update_joint_tree");
+
ur->commit_action();
}
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 79dc16ae2f..0bb58aac23 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -96,6 +96,8 @@ public:
class Skeleton3DEditor : public VBoxContainer {
GDCLASS(Skeleton3DEditor, VBoxContainer);
+ static void _bind_methods();
+
friend class Skeleton3DEditorPlugin;
enum SkeletonOption {
@@ -116,6 +118,10 @@ class Skeleton3DEditor : public VBoxContainer {
Skeleton3D *skeleton = nullptr;
+ enum {
+ JOINT_BUTTON_REVERT = 0,
+ };
+
Tree *joint_tree = nullptr;
BoneTransformEditor *rest_editor = nullptr;
BoneTransformEditor *pose_editor = nullptr;
@@ -149,6 +155,7 @@ class Skeleton3DEditor : public VBoxContainer {
EditorFileDialog *file_export_lib = nullptr;
void update_joint_tree();
+ void update_all();
void create_editors();
@@ -189,6 +196,7 @@ class Skeleton3DEditor : public VBoxContainer {
void _joint_tree_selection_changed();
void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button);
+ void _joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _update_properties();
void _subgizmo_selection_change();
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index ff5aca6cb0..37d5b787eb 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -659,6 +659,7 @@ void SpriteFramesEditor::_notification(int p_what) {
zoom_reset->set_icon(get_editor_theme_icon(SNAME("ZoomReset")));
zoom_in->set_icon(get_editor_theme_icon(SNAME("ZoomMore")));
add_anim->set_icon(get_editor_theme_icon(SNAME("New")));
+ duplicate_anim->set_icon(get_editor_theme_icon(SNAME("Duplicate")));
delete_anim->set_icon(get_editor_theme_icon(SNAME("Remove")));
anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
split_sheet_zoom_out->set_icon(get_editor_theme_icon(SNAME("ZoomLess")));
@@ -1179,6 +1180,41 @@ void SpriteFramesEditor::_animation_add() {
animations->grab_focus();
}
+void SpriteFramesEditor::_animation_duplicate() {
+ if (updating) {
+ return;
+ }
+
+ if (!frames->has_animation(edited_anim)) {
+ return;
+ }
+
+ int counter = 1;
+ String new_name = edited_anim;
+ PackedStringArray name_component = new_name.rsplit("_", true, 1);
+ String base_name = name_component[0];
+ if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) {
+ counter = name_component[1].to_int();
+ }
+ new_name = base_name + "_" + itos(counter);
+ while (frames->has_animation(new_name)) {
+ counter++;
+ new_name = base_name + "_" + itos(counter);
+ }
+
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
+ undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);
+ undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);
+ undo_redo->add_do_method(this, "_select_animation", new_name);
+ undo_redo->add_undo_method(this, "_select_animation", edited_anim);
+ undo_redo->add_do_method(this, "_update_library");
+ undo_redo->add_undo_method(this, "_update_library");
+ undo_redo->commit_action();
+
+ animations->grab_focus();
+}
+
void SpriteFramesEditor::_animation_remove() {
if (updating) {
return;
@@ -1541,6 +1577,7 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
_zoom_reset();
add_anim->set_disabled(read_only);
+ duplicate_anim->set_disabled(read_only);
delete_anim->set_disabled(read_only);
anim_speed->set_editable(!read_only);
anim_loop->set_disabled(read_only);
@@ -1865,6 +1902,11 @@ SpriteFramesEditor::SpriteFramesEditor() {
hbc_animlist->add_child(add_anim);
add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));
+ duplicate_anim = memnew(Button);
+ duplicate_anim->set_flat(true);
+ hbc_animlist->add_child(duplicate_anim);
+ duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));
+
delete_anim = memnew(Button);
delete_anim->set_theme_type_variation("FlatButton");
hbc_animlist->add_child(delete_anim);
@@ -1918,6 +1960,8 @@ SpriteFramesEditor::SpriteFramesEditor() {
add_anim->set_shortcut_context(animations);
add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTR("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));
+ duplicate_anim->set_shortcut_context(animations);
+ duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTR("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));
delete_anim->set_shortcut_context(animations);
delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTR("Delete Animation"), Key::KEY_DELETE));
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index d6345a3ac8..a85a6b2453 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -121,6 +121,7 @@ class SpriteFramesEditor : public HSplitContainer {
Vector<int> selection;
Button *add_anim = nullptr;
+ Button *duplicate_anim = nullptr;
Button *delete_anim = nullptr;
SpinBox *anim_speed = nullptr;
Button *anim_loop = nullptr;
@@ -210,6 +211,7 @@ class SpriteFramesEditor : public HSplitContainer {
void _animation_selected();
void _animation_name_edited();
void _animation_add();
+ void _animation_duplicate();
void _animation_remove();
void _animation_remove_confirmed();
void _animation_search_text_changed(const String &p_text);
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index c1bcd43b2e..1262fe1ce8 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -104,12 +104,12 @@ void TextEditor::set_edited_resource(const Ref<Resource> &p_res) {
edited_res = p_res;
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
code_editor->get_text_editor()->set_text(text_file->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
code_editor->get_text_editor()->set_text(json_file->get_parsed_text());
}
@@ -169,12 +169,12 @@ void TextEditor::reload_text() {
int v = te->get_v_scroll();
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
te->set_text(text_file->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
te->set_text(json_file->get_parsed_text());
}
@@ -194,7 +194,7 @@ void TextEditor::_validate_script() {
emit_signal(SNAME("edited_script_changed"));
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
CodeEdit *te = code_editor->get_text_editor();
te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0));
@@ -245,12 +245,12 @@ void TextEditor::_bookmark_item_pressed(int p_idx) {
void TextEditor::apply_code() {
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
+ if (text_file.is_valid()) {
text_file->set_text(code_editor->get_text_editor()->get_text());
}
Ref<JSON> json_file = edited_res;
- if (json_file != nullptr) {
+ if (json_file.is_valid()) {
json_file->parse(code_editor->get_text_editor()->get_text(), true);
}
code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp
index a3c1405553..d93466b5ba 100644
--- a/editor/plugins/texture_editor_plugin.cpp
+++ b/editor/plugins/texture_editor_plugin.cpp
@@ -169,7 +169,7 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) {
Ref<Image> image(Object::cast_to<Image>(p_object));
texture = ImageTexture::create_from_image(image);
- ERR_FAIL_NULL_MSG(texture, "Failed to create the texture from an invalid image.");
+ ERR_FAIL_COND_MSG(texture.is_null(), "Failed to create the texture from an invalid image.");
}
add_custom_control(memnew(TexturePreview(texture, true)));
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 99635a2531..ea1756b65a 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2174,8 +2174,26 @@ void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) {
_update_add_type_options(p_value);
}
+void ThemeTypeDialog::_type_filter_input(const Ref<InputEvent> &p_ie) {
+ Ref<InputEventKey> k = p_ie;
+ if (k.is_valid() && k->is_pressed()) {
+ switch (k->get_keycode()) {
+ case Key::UP:
+ case Key::DOWN:
+ case Key::PAGEUP:
+ case Key::PAGEDOWN: {
+ add_type_options->gui_input(k);
+ add_type_filter->accept_event();
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
void ThemeTypeDialog::_add_type_options_cbk(int p_index) {
add_type_filter->set_text(add_type_options->get_item_text(p_index));
+ add_type_filter->set_caret_column(add_type_filter->get_text().length());
}
void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) {
@@ -2245,6 +2263,7 @@ ThemeTypeDialog::ThemeTypeDialog() {
add_type_vb->add_child(add_type_filter);
add_type_filter->connect(SceneStringName(text_changed), callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk));
add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered));
+ add_type_filter->connect(SceneStringName(gui_input), callable_mp(this, &ThemeTypeDialog::_type_filter_input));
Label *add_type_options_label = memnew(Label);
add_type_options_label->set_text(TTR("Available Node-based types:"));
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index ba8e3a30b7..ae92365c32 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -303,6 +303,7 @@ class ThemeTypeDialog : public ConfirmationDialog {
void _update_add_type_options(const String &p_filter = "");
void _add_type_filter_cbk(const String &p_value);
+ void _type_filter_input(const Ref<InputEvent> &p_ie);
void _add_type_options_cbk(int p_index);
void _add_type_dialog_entered(const String &p_value);
void _add_type_dialog_activated(int p_index);
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index 8dbf58e228..79915012a8 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -1482,30 +1482,36 @@ void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Tra
debug_occlusion_color.push_back(color);
RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform);
- Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer);
- if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
- p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ Ref<OccluderPolygon2D> occluder = tile_data->get_occluder_polygon(occlusion_layer, i);
+ if (occluder.is_valid() && occluder->get_polygon().size() >= 3) {
+ p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color);
+ }
}
RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D());
}
Variant TileDataOcclusionShapeEditor::_get_painted_value() {
- Ref<OccluderPolygon2D> occluder_polygon;
- if (polygon_editor->get_polygon_count() >= 1) {
+ Array polygons;
+ for (int i = 0; i < polygon_editor->get_polygon_count(); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon;
occluder_polygon.instantiate();
- occluder_polygon->set_polygon(polygon_editor->get_polygon(0));
+ occluder_polygon->set_polygon(polygon_editor->get_polygon(i));
+ polygons.push_back(occluder_polygon);
}
- return occluder_polygon;
+ return polygons;
}
void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL(tile_data);
- Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer);
polygon_editor->clear_polygons();
- if (occluder_polygon.is_valid()) {
- polygon_editor->add_polygon(occluder_polygon->get_polygon());
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer, i);
+ if (occluder_polygon.is_valid()) {
+ polygon_editor->add_polygon(occluder_polygon->get_polygon());
+ }
}
polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
@@ -1513,8 +1519,13 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile
void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL(tile_data);
- Ref<OccluderPolygon2D> occluder_polygon = p_value;
- tile_data->set_occluder(occlusion_layer, occluder_polygon);
+
+ Array polygons = p_value;
+ tile_data->set_occluder_polygons_count(occlusion_layer, polygons.size());
+ for (int i = 0; i < polygons.size(); i++) {
+ Ref<OccluderPolygon2D> occluder_polygon = polygons[i];
+ tile_data->set_occluder_polygon(occlusion_layer, i, occluder_polygon);
+ }
polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
@@ -1522,7 +1533,11 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl
Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile);
ERR_FAIL_NULL_V(tile_data, Variant());
- return tile_data->get_occluder(occlusion_layer);
+ Array polygons;
+ for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) {
+ polygons.push_back(tile_data->get_occluder_polygon(occlusion_layer, i));
+ }
+ return polygons;
}
void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) {
@@ -1548,6 +1563,7 @@ void TileDataOcclusionShapeEditor::_notification(int p_what) {
TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() {
polygon_editor = memnew(GenericTilePolygonEditor);
+ polygon_editor->set_multiple_polygon_mode(true);
add_child(polygon_editor);
}
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 7e7d7e3291..7e6746dd3c 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -86,8 +86,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
transform_toolbar->show();
tools_settings_vsep_2->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
} else {
tools_settings_vsep->show();
picker_button->show();
@@ -96,8 +95,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
tools_settings_vsep_2->show();
bucket_contiguous_checkbox->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
}
}
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 224c4e434f..b1417b2878 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -2768,15 +2768,7 @@ void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) {
void EditorPropertyTilePolygon::_polygons_changed() {
if (String(count_property).is_empty()) {
- if (base_type == "OccluderPolygon2D") {
- // Single OccluderPolygon2D.
- Ref<OccluderPolygon2D> occluder;
- if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
- occluder.instantiate();
- occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0));
- }
- emit_changed(get_edited_property(), occluder);
- } else if (base_type == "NavigationPolygon") {
+ if (base_type == "NavigationPolygon") {
Ref<NavigationPolygon> navigation_polygon;
if (generic_tile_polygon_editor->get_polygon_count() >= 1) {
navigation_polygon.instantiate();
@@ -2798,19 +2790,24 @@ void EditorPropertyTilePolygon::_polygons_changed() {
emit_changed(get_edited_property(), navigation_polygon);
}
} else {
- if (base_type.is_empty()) {
- // Multiple array of vertices.
- Vector<String> changed_properties;
- Array values;
- int count = generic_tile_polygon_editor->get_polygon_count();
- changed_properties.push_back(count_property);
- values.push_back(count);
- for (int i = 0; i < count; i++) {
- changed_properties.push_back(vformat(element_pattern, i));
+ // Multiple array of vertices or OccluderPolygon2D.
+ Vector<String> changed_properties;
+ Array values;
+ int count = generic_tile_polygon_editor->get_polygon_count();
+ changed_properties.push_back(count_property);
+ values.push_back(count);
+ for (int i = 0; i < count; i++) {
+ changed_properties.push_back(vformat(element_pattern, i));
+ if (base_type.is_empty()) {
values.push_back(generic_tile_polygon_editor->get_polygon(i));
+ } else if (base_type == "OccluderPolygon2D") {
+ Ref<OccluderPolygon2D> occluder;
+ occluder.instantiate();
+ occluder->set_polygon(generic_tile_polygon_editor->get_polygon(i));
+ values.push_back(occluder);
}
- emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
}
+ emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false);
}
}
@@ -2834,15 +2831,8 @@ void EditorPropertyTilePolygon::update_property() {
generic_tile_polygon_editor->clear_polygons();
if (String(count_property).is_empty()) {
- if (base_type == "OccluderPolygon2D") {
- // Single OccluderPolygon2D.
- Ref<OccluderPolygon2D> occluder = get_edited_property_value();
- generic_tile_polygon_editor->clear_polygons();
- if (occluder.is_valid()) {
- generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
- }
- } else if (base_type == "NavigationPolygon") {
- // Single OccluderPolygon2D.
+ if (base_type == "NavigationPolygon") {
+ // Single NavigationPolygon.
Ref<NavigationPolygon> navigation_polygon = get_edited_property_value();
generic_tile_polygon_editor->clear_polygons();
if (navigation_polygon.is_valid()) {
@@ -2859,6 +2849,15 @@ void EditorPropertyTilePolygon::update_property() {
for (int i = 0; i < count; i++) {
generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i)));
}
+ } else if (base_type == "OccluderPolygon2D") {
+ // Multiple OccluderPolygon2D.
+ generic_tile_polygon_editor->clear_polygons();
+ for (int i = 0; i < count; i++) {
+ Ref<OccluderPolygon2D> occluder = get_edited_object()->get(vformat(element_pattern, i));
+ if (occluder.is_valid()) {
+ generic_tile_polygon_editor->add_polygon(occluder->get_polygon());
+ }
+ }
}
}
}
@@ -2899,16 +2898,30 @@ bool EditorInspectorPluginTileData::can_handle(Object *p_object) {
bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
Vector<String> components = String(p_path).split("/", true, 2);
- if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
- if (components[1] == "polygon") {
+ if (components[1] == "polygons_count") {
EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon);
- ep->setup_single_mode(p_path, "OccluderPolygon2D");
- add_property_editor(p_path, ep);
+ ep->setup_multiple_mode(vformat("occlusion_layer_%d/polygons", layer_index), vformat("occlusion_layer_%d/polygons_count", layer_index), vformat("occlusion_layer_%d/polygon_%%d/polygon", layer_index), "OccluderPolygon2D");
+ Vector<String> properties;
+ properties.push_back(p_path);
+ int count = p_object->get(vformat("occlusion_layer_%d/polygons_count", layer_index));
+ for (int i = 0; i < count; i++) {
+ properties.push_back(vformat("occlusion_layer_%d/polygon_%d/polygon", layer_index, i));
+ }
+ add_property_editor_for_multiple_properties("Polygons", properties, ep);
return true;
}
+ // We keep the original editor for now, but here is the code that could be used if we need a custom editor for each polygon:
+ /*else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
+ if (components[2] == "polygon") {
+ return true;
+ }
+ }*/
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
int layer_index = components[0].trim_prefix("physics_layer_").to_int();
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index d47270841d..8f609850b8 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -162,6 +162,9 @@ void PropertySelector::_update_search() {
if (!found && !search_box->get_text().is_empty() && E.name.containsn(search_text)) {
item->select(0);
found = true;
+ } else if (!found && search_box->get_text().is_empty() && E.name == selected) {
+ item->select(0);
+ found = true;
}
item->set_selectable(0, true);
@@ -173,6 +176,12 @@ void PropertySelector::_update_search() {
if (category && category->get_first_child() == nullptr) {
memdelete(category); //old category was unused
}
+
+ if (found) {
+ // As we call this while adding items, defer until list is completely populated.
+ callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
+ }
+
} else {
List<MethodInfo> methods;
@@ -305,12 +314,20 @@ void PropertySelector::_update_search() {
if (!found && !search_box->get_text().is_empty() && name.containsn(search_text)) {
item->select(0);
found = true;
+ } else if (!found && search_box->get_text().is_empty() && name == selected) {
+ item->select(0);
+ found = true;
}
}
if (category && category->get_first_child() == nullptr) {
memdelete(category); //old category was unused
}
+
+ if (found) {
+ // As we call this while adding items, defer until list is completely populated.
+ callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true);
+ }
}
get_ok_button()->set_disabled(root->get_first_child() == nullptr);
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 610ad3efdf..00377a0dd2 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -46,6 +46,7 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export_platform.h"
+#include "editor/export/editor_export_platform_extension.h"
#include "editor/export/editor_export_platform_pc.h"
#include "editor/export/editor_export_plugin.h"
#include "editor/filesystem_dock.h"
@@ -78,6 +79,7 @@
#include "editor/plugins/cpu_particles_2d_editor_plugin.h"
#include "editor/plugins/cpu_particles_3d_editor_plugin.h"
#include "editor/plugins/curve_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_resource_tooltip_plugins.h"
#include "editor/plugins/font_config_plugin.h"
@@ -161,6 +163,8 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorExportPlugin);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC);
+ GDREGISTER_CLASS(EditorExportPlatformExtension);
+ GDREGISTER_ABSTRACT_CLASS(EditorExportPreset);
register_exporter_types();
@@ -176,6 +180,7 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorResourcePicker);
GDREGISTER_CLASS(EditorScriptPicker);
GDREGISTER_ABSTRACT_CLASS(EditorUndoRedoManager);
+ GDREGISTER_CLASS(EditorContextMenuPlugin);
GDREGISTER_ABSTRACT_CLASS(FileSystemDock);
GDREGISTER_VIRTUAL_CLASS(EditorFileSystemImportFormatSupportQuery);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 02c8a03ac6..baa77cb41d 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -50,6 +50,7 @@
#include "editor/node_dock.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
+#include "editor/plugins/editor_context_menu_plugin.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/reparent_dialog.h"
@@ -213,6 +214,10 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
} else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) {
_tool_selected(TOOL_ERASE);
} else {
+ int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event);
+ if (match_option) {
+ _tool_selected(match_option);
+ }
return;
}
@@ -1482,6 +1487,13 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
} break;
default: {
+ // Editor context plugin.
+ if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) {
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection);
+ break;
+ }
+
_filter_option_selected(p_tool);
if (p_tool >= EDIT_SUBRESOURCE_BASE) {
@@ -2650,6 +2662,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
}
+ if (!entire_scene) {
+ for (const Node *E : remove_list) {
+ // `move_child` + `get_index` doesn't really work for internal nodes.
+ ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
+ }
+ }
+
EditorNode::get_singleton()->hide_unused_editors(this);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@@ -2662,10 +2681,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->add_undo_reference(edited_scene);
} else {
- for (const Node *E : remove_list) {
- // `move_child` + `get_index` doesn't really work for internal nodes.
- ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
- }
if (delete_tracks_checkbox->is_pressed() || p_cut) {
remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions.
HashMap<Node *, NodePath> path_renames;
@@ -3679,7 +3694,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (selection.size() == 1) {
bool is_external = (!selection.front()->get()->get_scene_file_path().is_empty());
if (is_external) {
- bool is_inherited = selection.front()->get()->get_scene_inherited_state() != nullptr;
+ bool is_inherited = selection.front()->get()->get_scene_inherited_state().is_valid();
bool is_top_level = selection.front()->get()->get_owner() == nullptr;
if (is_inherited && is_top_level) {
menu->add_separator();
@@ -3726,6 +3741,15 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
menu->add_separator();
menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("scene_tree/delete"), TOOL_ERASE);
}
+
+ Vector<String> p_paths;
+ Node *root = EditorNode::get_singleton()->get_edited_scene();
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ String node_path = root->get_path().rel_path_to(E->get()->get_path());
+ p_paths.push_back(node_path);
+ }
+ EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths);
+
menu->reset_size();
menu->set_position(p_menu_pos);
menu->popup();
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index f654707630..7517bc3cb9 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -183,7 +183,7 @@ Ref<EditorTheme> EditorThemeManager::_create_base_theme(const Ref<EditorTheme> &
// If settings are comparable to the old theme, then just copy existing icons over.
// Otherwise, regenerate them.
- bool keep_old_icons = (p_old_theme != nullptr && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash());
+ bool keep_old_icons = (p_old_theme.is_valid() && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash());
if (keep_old_icons) {
print_verbose("EditorTheme: Can keep old icons, copying.");
editor_copy_icons(theme, p_old_theme);
@@ -1804,7 +1804,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_color("background", EditorStringName(Editor), background_color_opaque);
p_theme->set_stylebox("Background", EditorStringName(EditorStyles), make_flat_stylebox(background_color_opaque, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
- p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), p_config.base_style);
+ Ref<StyleBoxFlat> editor_panel_foreground = p_config.base_style->duplicate();
+ editor_panel_foreground->set_corner_radius_all(0);
+ p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), editor_panel_foreground);
// Editor focus.
p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus);
diff --git a/main/main.cpp b/main/main.cpp
index 791562a9bb..f2f71c27a4 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2327,15 +2327,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
}
- // note this is the desired rendering driver, it doesn't mean we will get it.
- // TODO - make sure this is updated in the case of fallbacks, so that the user interface
- // shows the correct driver string.
- OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
- OS::get_singleton()->set_current_rendering_method(rendering_method);
-
// always convert to lower case for consistency in the code
rendering_driver = rendering_driver.to_lower();
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
+ OS::get_singleton()->set_current_rendering_method(rendering_method);
+
if (use_custom_res) {
if (!force_res) {
window_size.width = GLOBAL_GET("display/window/size/viewport_width");
@@ -3394,7 +3391,8 @@ void Main::setup_boot_logo() {
boot_logo.instantiate();
Error load_err = ImageLoader::load_image(boot_logo_path, boot_logo);
if (load_err) {
- ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + "'. Loading default splash.");
+ String msg = (boot_logo_path.ends_with(".png") ? "" : "The only supported format is PNG.");
+ ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + +"'. " + msg + " Loading default splash.");
}
}
} else {
diff --git a/methods.py b/methods.py
index 6d81b35aff..bfd08cfc7b 100644
--- a/methods.py
+++ b/methods.py
@@ -163,7 +163,7 @@ def add_source_files(self, sources, files, allow_gen=False):
def disable_warnings(self):
# 'self' is the environment
- if self.msvc:
+ if self.msvc and not using_clang(self):
# We have to remove existing warning level defines before appending /w,
# otherwise we get: "warning D9025 : overriding '/W3' with '/w'"
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
@@ -817,21 +817,20 @@ def get_compiler_version(env):
"apple_patch3": -1,
}
- if not env.msvc:
- # Not using -dumpversion as some GCC distros only return major, and
- # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
- try:
- version = (
- subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"))
- .strip()
- .decode("utf-8")
- )
- except (subprocess.CalledProcessError, OSError):
- print_warning("Couldn't parse CXX environment variable to infer compiler version.")
- return ret
- else:
+ if env.msvc and not using_clang(env):
# TODO: Implement for MSVC
return ret
+
+ # Not using -dumpversion as some GCC distros only return major, and
+ # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
+ try:
+ version = subprocess.check_output(
+ [env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"), encoding="utf-8"
+ ).strip()
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't parse CXX environment variable to infer compiler version.")
+ return ret
+
match = re.search(
r"(?:(?<=version )|(?<=\) )|(?<=^))"
r"(?P<major>\d+)"
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 39dd064012..882f96a0dc 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -56,3 +56,20 @@ Validate extension JSON: Error: Field 'classes/RegEx/methods/compile/arguments':
Validate extension JSON: Error: Field 'classes/RegEx/methods/create_from_string/arguments': size changed value in new API, from 1 to 2.
Add optional argument to control error printing on compilation fail. Compatibility methods registered.
+
+
+GH-95375
+--------
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer2D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer3D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+
+These setters have been renamed to expose them. GDExtension language bindings couldn't have exposed these properties before.
+
+
+GH-94322
+--------
+Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_node_selector/arguments': size changed value in new API, from 2 to 3.
+Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4.
+
+Added optional argument to popup_property_selector and popup_node_selector to specify the current value.
diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl
index 0d10d378fd..37e7591aea 100644
--- a/modules/betsy/bc6h.glsl
+++ b/modules/betsy/bc6h.glsl
@@ -1,7 +1,7 @@
#[versions]
signed = "#define SIGNED";
-unsigned = "";
+unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned.
#[compute]
#version 450
@@ -10,10 +10,6 @@ unsigned = "";
#include "UavCrossPlatform_piece_all.glsl"
#VERSION_DEFINES
-#define QUALITY
-
-//SIGNED macro is WIP
-//#define SIGNED
float3 f32tof16(float3 value) {
return float3(packHalf2x16(float2(value.x, 0.0)),
@@ -48,11 +44,59 @@ params;
const float HALF_MAX = 65504.0f;
const uint PATTERN_NUM = 32u;
+#ifdef SIGNED
+const float HALF_MIN = -65504.0f;
+#else
+const float HALF_MIN = 0.0f;
+#endif
+
+#ifdef SIGNED
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950
+bool isNegative(float a) {
+ return a < 0.0f;
+}
+
+float CalcSignlessMSLE(float a, float b) {
+ float err = log2((b + 1.0f) / (a + 1.0f));
+ err = err * err;
+ return err;
+}
+
+float CrossCalcMSLE(float a, float b) {
+ float result = 0.0f;
+ result += CalcSignlessMSLE(0.0f, abs(a));
+ result += CalcSignlessMSLE(0.0f, abs(b));
+ return result;
+}
+
+float CalcMSLE(float3 a, float3 b) {
+ float result = 0.0f;
+ if (isNegative(a.x) != isNegative(b.x)) {
+ result += CrossCalcMSLE(a.x, b.x);
+ } else {
+ result += CalcSignlessMSLE(abs(a.x), abs(b.x));
+ }
+ if (isNegative(a.y) != isNegative(b.y)) {
+ result += CrossCalcMSLE(a.y, b.y);
+ } else {
+ result += CalcSignlessMSLE(abs(a.y), abs(b.y));
+ }
+ if (isNegative(a.z) != isNegative(b.z)) {
+ result += CrossCalcMSLE(a.z, b.z);
+ } else {
+ result += CalcSignlessMSLE(abs(a.z), abs(b.z));
+ }
+
+ return result;
+}
+#else
float CalcMSLE(float3 a, float3 b) {
float3 err = log2((b + 1.0f) / (a + 1.0f));
err = err * err;
return err.x + err.y + err.z;
}
+#endif
uint PatternFixupID(uint i) {
uint ret = 15u;
@@ -176,11 +220,6 @@ float3 Unquantize10(float3 x) {
float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) {
float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f);
- /*float3 signVal;
- signVal.x = comp.x >= 0.0f ? 0.0f : 0x8000;
- signVal.y = comp.y >= 0.0f ? 0.0f : 0x8000;
- signVal.z = comp.z >= 0.0f ? 0.0f : 0x8000;*/
- //return f16tof32( uint3( signVal + abs( comp ) ) );
return f16tof32(uint3(comp));
}
#endif
@@ -207,6 +246,7 @@ uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) {
return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f));
}
+// This adds a bitflag to quantized values that signifies whether they are negative.
void SignExtend(inout float3 v1, uint mask, uint signFlag) {
int3 v = int3(v1);
v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0);
@@ -215,6 +255,7 @@ void SignExtend(inout float3 v1, uint mask, uint signFlag) {
v1 = v;
}
+// Encodes a block with mode 11 (2x 10-bit endpoints).
void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
// compute endpoints (min/max RGB bbox)
float3 blockMin = texels[0];
@@ -250,6 +291,12 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
float endPoint0Pos = f32tof16(dot(blockMin, blockDir));
float endPoint1Pos = f32tof16(dot(blockMax, blockDir));
+#ifdef SIGNED
+ int maxVal10 = 0x1FF;
+ endpoint0 = clamp(endpoint0, -maxVal10, maxVal10);
+ endpoint1 = clamp(endpoint1, -maxVal10, maxVal10);
+#endif
+
// check if endpoint swap is required
float fixupTexelPos = f32tof16(dot(texels[0], blockDir));
uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos);
@@ -276,6 +323,11 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
msle += CalcMSLE(texels[i], texelUnc);
}
+#ifdef SIGNED
+ SignExtend(endpoint0, 0x1FF, 0x200);
+ SignExtend(endpoint1, 0x1FF, 0x200);
+#endif
+
// encode block for mode 11
blockMSLE = msle;
block.x = 0x03;
@@ -316,11 +368,12 @@ float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) {
return dot(x, x);
}
+// Gets the deviation from the source data of a particular pattern (smaller is better).
float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0; i < 16; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -350,11 +403,12 @@ float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
return sqDistanceFromLine;
}
+// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding.
void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0u; i < 16u; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -430,6 +484,13 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
endpoint952 = clamp(endpoint952, -maxVal95, maxVal95);
endpoint953 = clamp(endpoint953, -maxVal95, maxVal95);
+#ifdef SIGNED
+ int maxVal7 = 0x3F;
+ int maxVal9 = 0xFF;
+ endpoint760 = clamp(endpoint760, -maxVal7, maxVal7);
+ endpoint950 = clamp(endpoint950, -maxVal9, maxVal9);
+#endif
+
float3 endpoint760Unq = Unquantize7(endpoint760);
float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761);
float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762);
@@ -465,6 +526,11 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
SignExtend(endpoint952, 0xF, 0x10);
SignExtend(endpoint953, 0xF, 0x10);
+#ifdef SIGNED
+ SignExtend(endpoint760, 0x3F, 0x40);
+ SignExtend(endpoint950, 0xFF, 0x100);
+#endif
+
// encode block
float p2MSLE = min(msle76, msle95);
if (p2MSLE < blockMSLE) {
@@ -637,7 +703,7 @@ void main() {
float bestScore = EvaluateP2Pattern(0, texels);
uint bestPattern = 0;
- for (uint i = 1u; i < 32u; ++i) {
+ for (uint i = 1u; i < PATTERN_NUM; ++i) {
float score = EvaluateP2Pattern(i, texels);
if (score < bestScore) {
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 7f723826d1..c17651da45 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -36,6 +36,9 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
#include "bc6h.glsl.gen.h"
@@ -66,10 +69,16 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
if (rd == nullptr) {
#if defined(RD_ENABLED)
-#if defined(VULKAN_ENABLED)
- rcd = memnew(RenderingContextDriverVulkan);
+#if defined(METAL_ENABLED)
+ rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
+#if defined(VULKAN_ENABLED)
+ if (rcd == nullptr) {
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+ }
+#endif
#endif
if (rcd != nullptr && rd != nullptr) {
err = rcd->initialize();
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index 4e1a00cad6..8d4d0234da 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -875,7 +875,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
const int material = int(fbx_material->typed_id);
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
- ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
@@ -891,7 +891,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) {
}
mat = mat3d;
}
- ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -1056,7 +1056,7 @@ GLTFImageIndex FBXDocument::_parse_image_save_image(Ref<FBXState> p_state, const
}
Error FBXDocument::_parse_images(Ref<FBXState> p_state, const String &p_base_path) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
const ufbx_scene *fbx_scene = p_state->scene.get();
for (int texture_i = 0; texture_i < static_cast<int>(fbx_scene->texture_files.count); texture_i++) {
@@ -2118,7 +2118,6 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess>
Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
Ref<FBXState> state = p_state;
ERR_FAIL_COND_V(state.is_null(), nullptr);
- ERR_FAIL_NULL_V(state, nullptr);
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
p_state->set_bake_fps(p_bake_fps);
GLTFNodeIndex fbx_root = state->root_nodes.write[0];
@@ -2246,7 +2245,7 @@ Error FBXDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint3
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
- ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN);
+ ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
String base_path = p_base_path;
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index c72755642b..e3f2a61090 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -57,6 +57,7 @@
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
+#include "core/extension/gdextension_manager.h"
#include "editor/editor_paths.h"
#endif
@@ -953,7 +954,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(top->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = top->static_variables[E->value.index];
@@ -1726,10 +1728,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(script->valid) && E->value.getter) {
Callable::CallError err;
- r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true;
- }
+ const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
+ r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant();
+ return true;
}
r_ret = members[E->value.index];
return true;
@@ -1751,7 +1752,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(sptr->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = sptr->static_variables[E->value.index];
@@ -2177,9 +2179,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
global_array.write[globals[p_name]] = p_value;
return;
}
- globals[p_name] = global_array.size();
- global_array.push_back(p_value);
- _global_array = global_array.ptrw();
+
+ if (global_array_empty_indexes.size()) {
+ int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1];
+ globals[p_name] = index;
+ global_array.write[index] = p_value;
+ global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1);
+ } else {
+ globals[p_name] = global_array.size();
+ global_array.push_back(p_value);
+ _global_array = global_array.ptrw();
+ }
+}
+
+void GDScriptLanguage::_remove_global(const StringName &p_name) {
+ if (!globals.has(p_name)) {
+ return;
+ }
+ global_array_empty_indexes.push_back(globals[p_name]);
+ global_array.write[globals[p_name]] = Variant::NIL;
+ globals.erase(p_name);
}
void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
@@ -2237,11 +2256,40 @@ void GDScriptLanguage::init() {
_add_global(E.name, E.ptr);
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded));
+ GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading));
+ }
+#endif
+
#ifdef TESTS_ENABLED
GDScriptTests::GDScriptTestRunner::handle_cmdline();
#endif
}
+#ifdef TOOLS_ENABLED
+void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ if (globals.has(n)) {
+ continue;
+ }
+ Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
+ _add_global(n, nc);
+ }
+}
+
+void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ _remove_global(n);
+ }
+}
+#endif
+
String GDScriptLanguage::get_type() const {
return "GDScript";
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 6527a0ea4d..4d21651365 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -417,6 +417,7 @@ class GDScriptLanguage : public ScriptLanguage {
Vector<Variant> global_array;
HashMap<StringName, int> globals;
HashMap<StringName, Variant> named_globals;
+ Vector<int> global_array_empty_indexes;
struct CallLevel {
Variant *stack = nullptr;
@@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage {
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
+ void _remove_global(const StringName &p_name);
friend class GDScriptInstance;
@@ -467,6 +469,11 @@ class GDScriptLanguage : public ScriptLanguage {
HashMap<String, ObjectID> orphan_subclasses;
+#ifdef TOOLS_ENABLED
+ void _extension_loaded(const Ref<GDExtension> &p_extension);
+ void _extension_unloading(const Ref<GDExtension> &p_extension);
+#endif
+
public:
int calls;
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 2162a727b3..2de5811bca 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -150,7 +150,7 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V
GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
- ERR_FAIL_NULL(p_script.ptr());
+ ERR_FAIL_COND(p_script.is_null());
ERR_FAIL_NULL(p_function);
script = p_script;
captures = p_captures;
@@ -282,7 +282,7 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun
GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) :
function(p_function) {
- ERR_FAIL_NULL(p_self.ptr());
+ ERR_FAIL_COND(p_self.is_null());
ERR_FAIL_NULL(p_function);
reference = p_self;
object = p_self.ptr();
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index c4cef0287f..b6db6a940b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -260,6 +260,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet
context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
+ context.parser = this;
completion_context = context;
}
@@ -4794,9 +4795,9 @@ String GDScriptParser::DataType::to_string() const {
return class_type->fqcn;
case SCRIPT: {
if (is_meta_type) {
- return script_type != nullptr ? script_type->get_class_name().operator String() : "";
+ return script_type.is_valid() ? script_type->get_class_name().operator String() : "";
}
- String name = script_type != nullptr ? script_type->get_name() : "";
+ String name = script_type.is_valid() ? script_type->get_name() : "";
if (!name.is_empty()) {
return name;
}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
index 03d830741b..b636dbe580 100644
--- a/modules/gdscript/language_server/gdscript_language_protocol.cpp
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -196,7 +196,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(),
vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id));
Ref<LSPeer> peer = clients.get(latest_client_id);
- if (peer != nullptr) {
+ if (peer.is_valid()) {
String msg = Variant(request).to_json_string();
msg = format_output(msg);
(*peer)->res_queue.push_back(msg.utf8());
@@ -298,7 +298,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
- ERR_FAIL_NULL(peer);
+ ERR_FAIL_COND(peer.is_null());
Dictionary message = make_notification(p_method, p_params);
String msg = Variant(message).to_json_string();
@@ -319,7 +319,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari
}
ERR_FAIL_COND(!clients.has(p_client_id));
Ref<LSPeer> peer = clients.get(p_client_id);
- ERR_FAIL_NULL(peer);
+ ERR_FAIL_COND(peer.is_null());
Dictionary message = make_request(p_method, p_params, next_server_id);
next_server_id++;
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
index efd8ad6edb..60bcde4b8c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
@@ -3,14 +3,13 @@ const const_color: Color = 'red'
func func_color(arg_color: Color = 'blue') -> bool:
return arg_color == Color.BLUE
-@warning_ignore("assert_always_true")
func test():
- assert(const_color == Color.RED)
+ Utils.check(const_color == Color.RED)
- assert(func_color() == true)
- assert(func_color('blue') == true)
+ Utils.check(func_color() == true)
+ Utils.check(func_color('blue') == true)
var var_color: Color = 'green'
- assert(var_color == Color.GREEN)
+ Utils.check(var_color == Color.GREEN)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index bed9dd0e96..5318d11f33 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float
const const_packed_empty: PackedFloat64Array = []
const const_packed_ints: PackedFloat64Array = [52]
-@warning_ignore("assert_always_true")
func test():
- assert(typeof(const_float_int) == TYPE_FLOAT)
- assert(str(const_float_int) == '19')
- assert(typeof(const_float_plus) == TYPE_FLOAT)
- assert(str(const_float_plus) == '34')
- assert(typeof(const_float_cast) == TYPE_FLOAT)
- assert(str(const_float_cast) == '76')
+ Utils.check(typeof(const_float_int) == TYPE_FLOAT)
+ Utils.check(str(const_float_int) == '19')
+ Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
+ Utils.check(str(const_float_plus) == '34')
+ Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
+ Utils.check(str(const_float_cast) == '76')
- assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_empty) == '[]')
- assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_ints) == '[52]')
- assert(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- assert(str(const_packed_ints[0]) == '52')
+ Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_empty) == '[]')
+ Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
+ Utils.check(str(const_packed_ints[0]) == '52')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
index d2d9d04508..a569488d3c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
@@ -5,5 +5,5 @@ func test():
for value in range(E.E0, E.E3):
var inferable := value
total += inferable
- assert(total == 0 + 1 + 2)
+ Utils.check(total == 0 + 1 + 2)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
index 39f490c4b3..ec89226328 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
-
@export var test_1 = MyEnum
@export var test_2 = MyEnum.A
@export var test_3 := MyEnum
diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
index 4a7f10f1ee..9ce0782d5c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
@@ -3,5 +3,5 @@ func test():
var result := ''
for i in range(array.size(), 0, -1):
result += str(array[i - 1])
- assert(result == '963')
+ Utils.check(result == '963')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
index d678f3acfc..e0cbdacb38 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
@@ -2,11 +2,11 @@ func test():
var instance := Parent.new()
var result := instance.my_function(1)
print(result)
- assert(result == 1)
+ Utils.check(result == 1)
instance = Child.new()
result = instance.my_function(2)
print(result)
- assert(result == 0)
+ Utils.check(result == 0)
class Parent:
func my_function(par1: int) -> int:
diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
index 0b1576e66e..cbe8e9da34 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
@@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re
func test():
var converted_literal_int := convert_literal_int_to_float()
- assert(typeof(converted_literal_int) == TYPE_FLOAT)
- assert(converted_literal_int == 76.0)
+ Utils.check(typeof(converted_literal_int) == TYPE_FLOAT)
+ Utils.check(converted_literal_int == 76.0)
var converted_arg_int := convert_arg_int_to_float(36)
- assert(typeof(converted_arg_int) == TYPE_FLOAT)
- assert(converted_arg_int == 36.0)
+ Utils.check(typeof(converted_arg_int) == TYPE_FLOAT)
+ Utils.check(converted_arg_int == 36.0)
var converted_var_int := convert_var_int_to_float()
- assert(typeof(converted_var_int) == TYPE_FLOAT)
- assert(converted_var_int == 59.0)
+ Utils.check(typeof(converted_var_int) == TYPE_FLOAT)
+ Utils.check(converted_var_int == 59.0)
var converted_literal_array := convert_literal_array_to_packed()
- assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_literal_array) == '["46"]')
+ Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_literal_array) == '["46"]')
var converted_arg_array := convert_arg_array_to_packed(['91'])
- assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_arg_array) == '["91"]')
+ Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_arg_array) == '["91"]')
var converted_var_array := convert_var_array_to_packed()
- assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_var_array) == '["79"]')
+ Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_var_array) == '["79"]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
index 44ca5f4dd0..d49acaacd3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
@@ -2,7 +2,7 @@ func test():
var left_hard_int := 1
var right_hard_int := 2
var result_hard_int := left_hard_int if true else right_hard_int
- assert(result_hard_int == 1)
+ Utils.check(result_hard_int == 1)
@warning_ignore("inference_on_variant")
var left_hard_variant := 1 as Variant
@@ -10,6 +10,6 @@ func test():
var right_hard_variant := 2.0 as Variant
@warning_ignore("inference_on_variant")
var result_hard_variant := left_hard_variant if true else right_hard_variant
- assert(result_hard_variant == 1)
+ Utils.check(result_hard_variant == 1)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
index 12dc0b93df..ee30f01dfb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
@@ -4,124 +4,123 @@ class A extends RefCounted:
class B extends A:
pass
-@warning_ignore("assert_always_true")
func test():
var builtin: Variant = 3
- assert((builtin is Variant) == true)
- assert((builtin is int) == true)
- assert(is_instance_of(builtin, TYPE_INT) == true)
- assert((builtin is float) == false)
- assert(is_instance_of(builtin, TYPE_FLOAT) == false)
+ Utils.check((builtin is Variant) == true)
+ Utils.check((builtin is int) == true)
+ Utils.check(is_instance_of(builtin, TYPE_INT) == true)
+ Utils.check((builtin is float) == false)
+ Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false)
const const_builtin: Variant = 3
- assert((const_builtin is Variant) == true)
- assert((const_builtin is int) == true)
- assert(is_instance_of(const_builtin, TYPE_INT) == true)
- assert((const_builtin is float) == false)
- assert(is_instance_of(const_builtin, TYPE_FLOAT) == false)
+ Utils.check((const_builtin is Variant) == true)
+ Utils.check((const_builtin is int) == true)
+ Utils.check(is_instance_of(const_builtin, TYPE_INT) == true)
+ Utils.check((const_builtin is float) == false)
+ Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false)
var int_array: Variant = [] as Array[int]
- assert((int_array is Variant) == true)
- assert((int_array is Array) == true)
- assert(is_instance_of(int_array, TYPE_ARRAY) == true)
- assert((int_array is Array[int]) == true)
- assert((int_array is Array[float]) == false)
- assert((int_array is int) == false)
- assert(is_instance_of(int_array, TYPE_INT) == false)
+ Utils.check((int_array is Variant) == true)
+ Utils.check((int_array is Array) == true)
+ Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true)
+ Utils.check((int_array is Array[int]) == true)
+ Utils.check((int_array is Array[float]) == false)
+ Utils.check((int_array is int) == false)
+ Utils.check(is_instance_of(int_array, TYPE_INT) == false)
var const_int_array: Variant = [] as Array[int]
- assert((const_int_array is Variant) == true)
- assert((const_int_array is Array) == true)
- assert(is_instance_of(const_int_array, TYPE_ARRAY) == true)
- assert((const_int_array is Array[int]) == true)
- assert((const_int_array is Array[float]) == false)
- assert((const_int_array is int) == false)
- assert(is_instance_of(const_int_array, TYPE_INT) == false)
+ Utils.check((const_int_array is Variant) == true)
+ Utils.check((const_int_array is Array) == true)
+ Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true)
+ Utils.check((const_int_array is Array[int]) == true)
+ Utils.check((const_int_array is Array[float]) == false)
+ Utils.check((const_int_array is int) == false)
+ Utils.check(is_instance_of(const_int_array, TYPE_INT) == false)
var b_array: Variant = [] as Array[B]
- assert((b_array is Variant) == true)
- assert((b_array is Array) == true)
- assert(is_instance_of(b_array, TYPE_ARRAY) == true)
- assert((b_array is Array[B]) == true)
- assert((b_array is Array[A]) == false)
- assert((b_array is Array[int]) == false)
- assert((b_array is int) == false)
- assert(is_instance_of(b_array, TYPE_INT) == false)
+ Utils.check((b_array is Variant) == true)
+ Utils.check((b_array is Array) == true)
+ Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true)
+ Utils.check((b_array is Array[B]) == true)
+ Utils.check((b_array is Array[A]) == false)
+ Utils.check((b_array is Array[int]) == false)
+ Utils.check((b_array is int) == false)
+ Utils.check(is_instance_of(b_array, TYPE_INT) == false)
var const_b_array: Variant = [] as Array[B]
- assert((const_b_array is Variant) == true)
- assert((const_b_array is Array) == true)
- assert(is_instance_of(const_b_array, TYPE_ARRAY) == true)
- assert((const_b_array is Array[B]) == true)
- assert((const_b_array is Array[A]) == false)
- assert((const_b_array is Array[int]) == false)
- assert((const_b_array is int) == false)
- assert(is_instance_of(const_b_array, TYPE_INT) == false)
+ Utils.check((const_b_array is Variant) == true)
+ Utils.check((const_b_array is Array) == true)
+ Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true)
+ Utils.check((const_b_array is Array[B]) == true)
+ Utils.check((const_b_array is Array[A]) == false)
+ Utils.check((const_b_array is Array[int]) == false)
+ Utils.check((const_b_array is int) == false)
+ Utils.check(is_instance_of(const_b_array, TYPE_INT) == false)
var native: Variant = RefCounted.new()
- assert((native is Variant) == true)
- assert((native is Object) == true)
- assert(is_instance_of(native, TYPE_OBJECT) == true)
- assert(is_instance_of(native, Object) == true)
- assert((native is RefCounted) == true)
- assert(is_instance_of(native, RefCounted) == true)
- assert((native is Node) == false)
- assert(is_instance_of(native, Node) == false)
- assert((native is int) == false)
- assert(is_instance_of(native, TYPE_INT) == false)
+ Utils.check((native is Variant) == true)
+ Utils.check((native is Object) == true)
+ Utils.check(is_instance_of(native, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(native, Object) == true)
+ Utils.check((native is RefCounted) == true)
+ Utils.check(is_instance_of(native, RefCounted) == true)
+ Utils.check((native is Node) == false)
+ Utils.check(is_instance_of(native, Node) == false)
+ Utils.check((native is int) == false)
+ Utils.check(is_instance_of(native, TYPE_INT) == false)
var a_script: Variant = A.new()
- assert((a_script is Variant) == true)
- assert((a_script is Object) == true)
- assert(is_instance_of(a_script, TYPE_OBJECT) == true)
- assert(is_instance_of(a_script, Object) == true)
- assert((a_script is RefCounted) == true)
- assert(is_instance_of(a_script, RefCounted) == true)
- assert((a_script is A) == true)
- assert(is_instance_of(a_script, A) == true)
- assert((a_script is B) == false)
- assert(is_instance_of(a_script, B) == false)
- assert((a_script is Node) == false)
- assert(is_instance_of(a_script, Node) == false)
- assert((a_script is int) == false)
- assert(is_instance_of(a_script, TYPE_INT) == false)
+ Utils.check((a_script is Variant) == true)
+ Utils.check((a_script is Object) == true)
+ Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(a_script, Object) == true)
+ Utils.check((a_script is RefCounted) == true)
+ Utils.check(is_instance_of(a_script, RefCounted) == true)
+ Utils.check((a_script is A) == true)
+ Utils.check(is_instance_of(a_script, A) == true)
+ Utils.check((a_script is B) == false)
+ Utils.check(is_instance_of(a_script, B) == false)
+ Utils.check((a_script is Node) == false)
+ Utils.check(is_instance_of(a_script, Node) == false)
+ Utils.check((a_script is int) == false)
+ Utils.check(is_instance_of(a_script, TYPE_INT) == false)
var b_script: Variant = B.new()
- assert((b_script is Variant) == true)
- assert((b_script is Object) == true)
- assert(is_instance_of(b_script, TYPE_OBJECT) == true)
- assert(is_instance_of(b_script, Object) == true)
- assert((b_script is RefCounted) == true)
- assert(is_instance_of(b_script, RefCounted) == true)
- assert((b_script is A) == true)
- assert(is_instance_of(b_script, A) == true)
- assert((b_script is B) == true)
- assert(is_instance_of(b_script, B) == true)
- assert((b_script is Node) == false)
- assert(is_instance_of(b_script, Node) == false)
- assert((b_script is int) == false)
- assert(is_instance_of(b_script, TYPE_INT) == false)
+ Utils.check((b_script is Variant) == true)
+ Utils.check((b_script is Object) == true)
+ Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(b_script, Object) == true)
+ Utils.check((b_script is RefCounted) == true)
+ Utils.check(is_instance_of(b_script, RefCounted) == true)
+ Utils.check((b_script is A) == true)
+ Utils.check(is_instance_of(b_script, A) == true)
+ Utils.check((b_script is B) == true)
+ Utils.check(is_instance_of(b_script, B) == true)
+ Utils.check((b_script is Node) == false)
+ Utils.check(is_instance_of(b_script, Node) == false)
+ Utils.check((b_script is int) == false)
+ Utils.check(is_instance_of(b_script, TYPE_INT) == false)
var var_null: Variant = null
- assert((var_null is Variant) == true)
- assert((var_null is int) == false)
- assert(is_instance_of(var_null, TYPE_INT) == false)
- assert((var_null is Object) == false)
- assert(is_instance_of(var_null, TYPE_OBJECT) == false)
- assert((var_null is RefCounted) == false)
- assert(is_instance_of(var_null, RefCounted) == false)
- assert((var_null is A) == false)
- assert(is_instance_of(var_null, A) == false)
+ Utils.check((var_null is Variant) == true)
+ Utils.check((var_null is int) == false)
+ Utils.check(is_instance_of(var_null, TYPE_INT) == false)
+ Utils.check((var_null is Object) == false)
+ Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false)
+ Utils.check((var_null is RefCounted) == false)
+ Utils.check(is_instance_of(var_null, RefCounted) == false)
+ Utils.check((var_null is A) == false)
+ Utils.check(is_instance_of(var_null, A) == false)
const const_null: Variant = null
- assert((const_null is Variant) == true)
- assert((const_null is int) == false)
- assert(is_instance_of(const_null, TYPE_INT) == false)
- assert((const_null is Object) == false)
- assert(is_instance_of(const_null, TYPE_OBJECT) == false)
- assert((const_null is RefCounted) == false)
- assert(is_instance_of(const_null, RefCounted) == false)
- assert((const_null is A) == false)
- assert(is_instance_of(const_null, A) == false)
+ Utils.check((const_null is Variant) == true)
+ Utils.check((const_null is int) == false)
+ Utils.check(is_instance_of(const_null, TYPE_INT) == false)
+ Utils.check((const_null is Object) == false)
+ Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false)
+ Utils.check((const_null is RefCounted) == false)
+ Utils.check(is_instance_of(const_null, RefCounted) == false)
+ Utils.check((const_null is A) == false)
+ Utils.check(is_instance_of(const_null, A) == false)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index b000c82717..fe0274c27b 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -10,206 +10,205 @@ class Members:
var two: Array[int] = one
func check_passing() -> bool:
- assert(str(one) == '[104]')
- assert(str(two) == '[104]')
+ Utils.check(str(one) == '[104]')
+ Utils.check(str(two) == '[104]')
two.push_back(582)
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[104, 582]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[104, 582]')
two = [486]
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[486]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[486]')
return true
@warning_ignore("unsafe_method_access")
-@warning_ignore("assert_always_true")
@warning_ignore("return_value_discarded")
func test():
var untyped_basic = [459]
- assert(str(untyped_basic) == '[459]')
- assert(untyped_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(untyped_basic) == '[459]')
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL)
var inferred_basic := [366]
- assert(str(inferred_basic) == '[366]')
- assert(inferred_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(inferred_basic) == '[366]')
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL)
var typed_basic: Array = [521]
- assert(str(typed_basic) == '[521]')
- assert(typed_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(typed_basic) == '[521]')
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL)
var empty_floats: Array[float] = []
- assert(str(empty_floats) == '[]')
- assert(empty_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(empty_floats) == '[]')
+ Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT)
untyped_basic = empty_floats
- assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
inferred_basic = empty_floats
- assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
typed_basic = empty_floats
- assert(typed_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT)
empty_floats.push_back(705.0)
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- assert(str(empty_floats) == '[705, 430, 263, 518]')
- assert(str(untyped_basic) == '[705, 430, 263, 518]')
- assert(str(inferred_basic) == '[705, 430, 263, 518]')
- assert(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
+ Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- assert(str(filled_floats) == '[950, 170, 954, 693]')
- assert(filled_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- assert(str(casted_floats) == '[526]')
- assert(casted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(casted_floats) == '[526]')
+ Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- assert(str(returned_floats) == '[554]')
- assert(returned_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(returned_floats) == '[554]')
+ Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- assert(str(passed_floats) == '[663]')
- assert(passed_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(passed_floats) == '[663]')
+ Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- assert(str(default_floats) == '[364]')
- assert(default_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(default_floats) == '[364]')
+ Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- assert(str(converted_floats) == '[556, 498]')
- assert(converted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
const constant_basic = [228]
- assert(str(constant_basic) == '[228]')
- assert(constant_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(constant_basic) == '[228]')
+ Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- assert(str(constant_floats) == '[552]')
- assert(constant_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(constant_floats) == '[552]')
+ Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
var source_floats: Array[float] = [999.74]
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- assert(str(source_floats) == '[999]')
- assert(str(untyped_basic) == '[999]')
- assert(str(destination_floats) == '[999]')
- assert(destination_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(source_floats) == '[999]')
+ Utils.check(str(untyped_basic) == '[999]')
+ Utils.check(str(destination_floats) == '[999]')
+ Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- assert(str(duplicated_floats) == '[789]')
- assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
var b_objects: Array[B] = [B.new(), B.new() as A, null]
- assert(b_objects.size() == 3)
- assert(b_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(b_objects.get_typed_script() == B)
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_script() == B)
var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]]
- assert(a_objects.size() == 4)
- assert(a_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(a_objects.get_typed_script() == A)
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_script() == A)
var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects)
- assert(a_passed == 4)
+ Utils.check(a_passed == 4)
var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects)
- assert(b_passed == true)
+ Utils.check(b_passed == true)
var empty_strings: Array[String] = []
var empty_bools: Array[bool] = []
var empty_basic_one := []
var empty_basic_two := []
- assert(empty_strings == empty_bools)
- assert(empty_basic_one == empty_basic_two)
- assert(empty_strings.hash() == empty_bools.hash())
- assert(empty_basic_one.hash() == empty_basic_two.hash())
+ Utils.check(empty_strings == empty_bools)
+ Utils.check(empty_basic_one == empty_basic_two)
+ Utils.check(empty_strings.hash() == empty_bools.hash())
+ Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
var assign_source: Array[int] = [527]
var assign_target: Array[int] = []
assign_target.assign(assign_source)
- assert(str(assign_source) == '[527]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527]')
+ Utils.check(str(assign_target) == '[527]')
assign_source.push_back(657)
- assert(str(assign_source) == '[527, 657]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527, 657]')
+ Utils.check(str(assign_target) == '[527]')
var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one):
one.push_back(887)
two.push_back(198)
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[887, 198]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[887, 198]')
two = [130]
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[130]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[130]')
return true
).call()
- assert(defaults_passed == true)
+ Utils.check(defaults_passed == true)
var members := Members.new()
var members_passed := members.check_passing()
- assert(members_passed == true)
+ Utils.check(members_passed == true)
var resized_basic: Array = []
resized_basic.resize(1)
- assert(typeof(resized_basic[0]) == TYPE_NIL)
- assert(resized_basic[0] == null)
+ Utils.check(typeof(resized_basic[0]) == TYPE_NIL)
+ Utils.check(resized_basic[0] == null)
var resized_ints: Array[int] = []
resized_ints.resize(1)
- assert(typeof(resized_ints[0]) == TYPE_INT)
- assert(resized_ints[0] == 0)
+ Utils.check(typeof(resized_ints[0]) == TYPE_INT)
+ Utils.check(resized_ints[0] == 0)
var resized_arrays: Array[Array] = []
resized_arrays.resize(1)
- assert(typeof(resized_arrays[0]) == TYPE_ARRAY)
+ Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY)
resized_arrays[0].resize(1)
resized_arrays[0][0] = 523
- assert(str(resized_arrays) == '[[523]]')
+ Utils.check(str(resized_arrays) == '[[523]]')
var resized_objects: Array[Object] = []
resized_objects.resize(1)
- assert(typeof(resized_objects[0]) == TYPE_NIL)
- assert(resized_objects[0] == null)
+ Utils.check(typeof(resized_objects[0]) == TYPE_NIL)
+ Utils.check(resized_objects[0] == null)
var typed_enums: Array[E] = []
typed_enums.resize(1)
- assert(str(typed_enums) == '[0]')
+ Utils.check(str(typed_enums) == '[0]')
typed_enums[0] = E.E0
- assert(str(typed_enums) == '[391]')
- assert(typed_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed_enums) == '[391]')
+ Utils.check(typed_enums.get_typed_builtin() == TYPE_INT)
const const_enums: Array[E] = []
- assert(const_enums.get_typed_builtin() == TYPE_INT)
- assert(const_enums.get_typed_class_name() == &'')
+ Utils.check(const_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_class_name() == &'')
var a := A.new()
var typed_natives: Array[RefCounted] = [a]
var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A)
- assert(typed_scripts[0] == a)
+ Utils.check(typed_scripts[0] == a)
print('ok')
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd
index 7a7d6d953e..1e5d3fdcad 100644
--- a/modules/gdscript/tests/scripts/parser/features/annotations.gd
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd
@@ -1,7 +1,5 @@
extends Node
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("A", "B", "C") var test_1
@export_enum("A", "B", "C",) var test_2
diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd
index af24b32322..482d04a63b 100644
--- a/modules/gdscript/tests/scripts/parser/features/class.gd
+++ b/modules/gdscript/tests/scripts/parser/features/class.gd
@@ -18,8 +18,8 @@ func test():
test_instance.number = 42
var test_sub = TestSub.new()
- assert(test_sub.number == 25) # From Test.
- assert(test_sub.other_string == "bye") # From TestSub.
+ Utils.check(test_sub.number == 25) # From Test.
+ Utils.check(test_sub.other_string == "bye") # From TestSub.
var _test_constructor = TestConstructor.new()
_test_constructor = TestConstructor.new(500)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
index 0d97135a7b..cfda255905 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_dir var test_dir: Array[String]
@export_dir var test_dir_packed: PackedStringArray
@export_file var test_file: Array[String]
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
index 7f0737f4db..d50f0b2528 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("Red", "Green", "Blue") var test_untyped
@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 8bcb2bcb9a..1e134d0e0e 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,7 +1,6 @@
class_name ExportVariableTest
extends Node
-const Utils = preload("../../utils.notest.gd")
const PreloadedGlobalClass = preload("./export_variable_global.notest.gd")
const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
index 2fa45c1d7d..0ec118b6b7 100644
--- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
+++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
@@ -9,5 +9,5 @@ func test():
j_string += str(j)
return j_string
i_string += lambda.call()
- assert(i_string == '0202')
+ Utils.check(i_string == '0202')
print('ok')
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
index 9c67a152f5..736cda7f74 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
@@ -1,30 +1,32 @@
func test():
- # The assertions below should all evaluate to `true` for this test to pass.
- assert(true)
- assert(not false)
- assert(500)
- assert(not 0)
- assert(500.5)
- assert(not 0.0)
- assert("non-empty string")
- assert(["non-empty array"])
- assert({"non-empty": "dictionary"})
- assert(Vector2(1, 0))
- assert(Vector2i(-1, -1))
- assert(Vector3(0, 0, 0.0001))
- assert(Vector3i(0, 0, 10000))
+ # The checks below should all evaluate to `true` for this test to pass.
+ Utils.check(true)
+ Utils.check(not false)
+ Utils.check(500)
+ Utils.check(not 0)
+ Utils.check(500.5)
+ Utils.check(not 0.0)
+ Utils.check("non-empty string")
+ Utils.check(["non-empty array"])
+ Utils.check({"non-empty": "dictionary"})
+ Utils.check(Vector2(1, 0))
+ Utils.check(Vector2i(-1, -1))
+ Utils.check(Vector3(0, 0, 0.0001))
+ Utils.check(Vector3i(0, 0, 10000))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2(0, 0, 0, 1))
+ Utils.check(Rect2(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2(1, 1, 0, 0))
+ Utils.check(Rect2(1, 1, 0, 0))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2i(0, 0, 0, 1))
+ Utils.check(Rect2i(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2i(1, 1, 0, 0))
+ Utils.check(Rect2i(1, 1, 0, 0))
# A fully black color is only truthy if its alpha component is not equal to `1`.
- assert(Color(0, 0, 0, 0.5))
+ Utils.check(Color(0, 0, 0, 0.5))
+
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out
index 705524857b..1b47ed10dc 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.out
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out
@@ -1,65 +1,2 @@
GDTEST_OK
->> WARNING
->> Line: 3
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 4
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 5
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 6
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 7
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 8
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 9
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 12
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 13
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 14
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 15
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 18
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 21
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 24
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 27
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 30
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
+ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
index bd38259cec..6eec37d64d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
@@ -25,7 +25,7 @@ func test():
print("String in Array[StringName]: ", "abc" in stringname_array)
var packed_string_array: PackedStringArray = []
- assert(!packed_string_array.push_back("abc"))
+ Utils.check(!packed_string_array.push_back("abc"))
print("StringName in PackedStringArray: ", &"abc" in packed_string_array)
string_array.push_back("abc")
diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
index d1746979be..6aa863c05f 100644
--- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
@@ -1,10 +1,9 @@
const array: Array = [0]
const dictionary := {1: 2}
-@warning_ignore("assert_always_true")
func test():
- assert(array.is_read_only() == true)
- assert(str(array) == '[0]')
- assert(dictionary.is_read_only() == true)
- assert(str(dictionary) == '{ 1: 2 }')
+ Utils.check(array.is_read_only() == true)
+ Utils.check(str(array) == '[0]')
+ Utils.check(dictionary.is_read_only() == true)
+ Utils.check(str(dictionary) == '{ 1: 2 }')
print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
index a778fb1a94..0f2526667d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
@@ -2,8 +2,8 @@ class Foo extends Node:
func _init():
name = 'f'
var string: String = name
- assert(typeof(string) == TYPE_STRING)
- assert(string == 'f')
+ Utils.check(typeof(string) == TYPE_STRING)
+ Utils.check(string == 'f')
print('ok')
func test():
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
index 0851d939dc..9e67e75140 100644
--- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
@@ -6,15 +6,15 @@ extends Node
@onready var later_untyped = [1]
func test():
- assert(typeof(later_inferred) == TYPE_ARRAY)
- assert(later_inferred.size() == 0)
+ Utils.check(typeof(later_inferred) == TYPE_ARRAY)
+ Utils.check(later_inferred.size() == 0)
- assert(typeof(later_static) == TYPE_ARRAY)
- assert(later_static.size() == 0)
+ Utils.check(typeof(later_static) == TYPE_ARRAY)
+ Utils.check(later_static.size() == 0)
- assert(typeof(later_static_with_init) == TYPE_ARRAY)
- assert(later_static_with_init.size() == 0)
+ Utils.check(typeof(later_static_with_init) == TYPE_ARRAY)
+ Utils.check(later_static_with_init.size() == 0)
- assert(typeof(later_untyped) == TYPE_NIL)
+ Utils.check(typeof(later_untyped) == TYPE_NIL)
print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
index 0133d7fcfc..90df98e05b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
# GH-73843
@export_group("Resource")
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
index 4cb51f8512..48a9349bf8 100644
--- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
@@ -62,7 +62,7 @@ func test():
0 when side_effect():
print("will run the side effect call, but not this")
_:
- assert(global == 1)
+ Utils.check(global == 1)
print("side effect only ran once")
func side_effect():
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 42b29eee43..91d5a501c8 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -5,8 +5,6 @@ class MyClass:
enum MyEnum {}
-const Utils = preload("../../utils.notest.gd")
-
static var test_static_var_untyped
static var test_static_var_weak_null = null
static var test_static_var_weak_int = 1
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
index ee5c1e1267..4ddbeaec0b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
@@ -1,7 +1,5 @@
# GH-82169
-const Utils = preload("../../utils.notest.gd")
-
class A:
static var test_static_var_a1
static var test_static_var_a2
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
index fd23ea0db5..d6847768e6 100644
--- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
@@ -3,7 +3,6 @@ class MyClass:
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
const Other = preload("./metatypes.notest.gd")
var test_native := JSON
diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
index e1aba83507..dee36d3ae0 100644
--- a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
@@ -6,6 +6,6 @@ class MyObj:
func test():
var obj_1 = MyObj.new()
var obj_2 = MyObj.new()
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
obj_1.set(&"obj", obj_2)
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
index b07c40b6da..11a670a7fb 100644
--- a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
@@ -12,4 +12,4 @@ func test() -> void:
node1.add_child(node2)
add_child(node3)
- assert(get_node("_/Child") == $_/Child)
+ Utils.check(get_node("_/Child") == $_/Child)
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
index 691b611574..d72662736e 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
@@ -14,33 +14,33 @@ func test():
func test_construct(v, f):
@warning_ignore("unsafe_call_argument")
Vector2(v, v) # Built-in type construct.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_utility(v, f):
abs(v) # Utility function.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call(v, f):
@warning_ignore("unsafe_method_access")
v.angle() # Built-in method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call_validated(v: Vector2, f):
@warning_ignore("return_value_discarded")
v.abs() # Built-in method call validated.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call(v, f):
@warning_ignore("unsafe_method_access")
v.get_reference_count() # Native type method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind(v: Resource, f):
@warning_ignore("return_value_discarded")
v.duplicate() # Native type method call with MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind_validated(v: RefCounted, f):
@warning_ignore("return_value_discarded")
v.get_reference_count() # Native type method call with validated MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
index ec444b4ffa..859bfd7987 100644
--- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
@@ -1,6 +1,6 @@
func test():
var untyped: Variant = 32
var typed: Array[int] = [untyped]
- assert(typed.get_typed_builtin() == TYPE_INT)
- assert(str(typed) == '[32]')
+ Utils.check(typed.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '[32]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 7fdd6556ec..5d615d8557 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,3 +1,13 @@
+class_name Utils
+
+
+# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
+# for anything other than testing the `assert()` itself.
+static func check(condition: Variant) -> void:
+ if not condition:
+ printerr("Check failed.")
+
+
static func get_type(property: Dictionary, is_return: bool = false) -> String:
match property.type:
TYPE_NIL:
@@ -46,7 +56,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
while true:
if not hint_string.contains(":"):
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
var elem_type_hint: String = hint_string.get_slice(":", 0)
hint_string = hint_string.substr(elem_type_hint.length() + 1)
@@ -58,7 +68,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
type_hint_prefixes += "<%s>:" % type_string(elem_type)
else:
if elem_type_hint.count("/") != 1:
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
elem_type = elem_type_hint.get_slice("/", 0).to_int()
elem_hint = elem_type_hint.get_slice("/", 1).to_int()
type_hint_prefixes += "<%s>/<%s>:" % [
@@ -188,7 +198,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
PROPERTY_HINT_PASSWORD:
return "PROPERTY_HINT_PASSWORD"
- push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
return "<invalid hint>"
@@ -240,7 +250,7 @@ static func get_property_usage_string(usage: int) -> String:
usage &= ~flag[0]
if usage != PROPERTY_USAGE_NONE:
- push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
return "<invalid usage flags>"
return result.left(-1)
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index ebeed015e9..10534594d3 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -63,6 +63,13 @@
The [param bake_fps] parameter overrides the bake_fps in [param state].
</description>
</method>
+ <method name="get_supported_gltf_extensions" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns a list of all support glTF extensions, including extensions supported directly by the engine, and extensions supported by user plugins registering [GLTFDocumentExtension] classes.
+ [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered.
+ </description>
+ </method>
<method name="register_gltf_document_extension" qualifiers="static">
<return type="void" />
<param index="0" name="extension" type="GLTFDocumentExtension" />
diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp
index 9fdd6034a9..c6540ebb22 100644
--- a/modules/gltf/extensions/gltf_document_extension.cpp
+++ b/modules/gltf/extensions/gltf_document_extension.cpp
@@ -56,7 +56,7 @@ void GLTFDocumentExtension::_bind_methods() {
// Import process.
Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err);
return err;
@@ -69,16 +69,16 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() {
}
Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err);
return err;
}
Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(r_image, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(r_image.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_image_data, p_state, p_image_data, p_mime_type, r_image, err);
return err;
@@ -91,31 +91,31 @@ String GLTFDocumentExtension::get_image_file_extension() {
}
Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(r_gltf_texture.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_parse_texture_json, p_state, p_texture_json, r_gltf_texture, err);
return err;
}
Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
- ERR_FAIL_NULL_V(p_state, nullptr);
- ERR_FAIL_NULL_V(p_gltf_node, nullptr);
+ ERR_FAIL_COND_V(p_state.is_null(), nullptr);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr);
Node3D *ret_node = nullptr;
GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node);
return ret_node;
}
Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_post_parse, p_state, err);
return err;
}
Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err);
@@ -124,7 +124,7 @@ Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p
Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_import_post, p_state, p_root, err);
return err;
@@ -139,14 +139,14 @@ Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_ro
}
void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
- ERR_FAIL_NULL(p_state);
- ERR_FAIL_NULL(p_gltf_node);
+ ERR_FAIL_COND(p_state.is_null());
+ ERR_FAIL_COND(p_gltf_node.is_null());
ERR_FAIL_NULL(p_scene_node);
GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node);
}
Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_preserialize, p_state, err);
return err;
@@ -160,38 +160,38 @@ Vector<String> GLTFDocumentExtension::get_saveable_image_formats() {
PackedByteArray GLTFDocumentExtension::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) {
PackedByteArray ret;
- ERR_FAIL_NULL_V(p_state, ret);
- ERR_FAIL_NULL_V(p_image, ret);
+ ERR_FAIL_COND_V(p_state.is_null(), ret);
+ ERR_FAIL_COND_V(p_image.is_null(), ret);
GDVIRTUAL_CALL(_serialize_image_to_bytes, p_state, p_image, p_image_dict, p_image_format, p_lossy_quality, ret);
return ret;
}
Error GLTFDocumentExtension::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_image.is_null(), ERR_INVALID_PARAMETER);
Error ret = OK;
GDVIRTUAL_CALL(_save_image_at_path, p_state, p_image, p_file_path, p_image_format, p_lossy_quality, ret);
return ret;
}
Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_texture, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_texture.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_serialize_texture_json, p_state, p_texture_json, p_gltf_texture, p_image_format, err);
return err;
}
Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err);
return err;
}
Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
Error err = OK;
GDVIRTUAL_CALL(_export_post, p_state, err);
return err;
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index 64117349e0..cde30bce18 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -45,7 +45,7 @@ void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object,
Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_state, Node *p_root) {
ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
List<Node *> queue;
queue.push_back(p_root);
List<Node *> delete_queue;
diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
index 0340eb11b5..0f2246ce18 100644
--- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp
+++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp
@@ -229,7 +229,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_resource(const Ref<Shape3D> &p_shap
}
Ref<Shape3D> GLTFPhysicsShape::to_resource(bool p_cache_shapes) {
- if (!p_cache_shapes || _shape_cache == nullptr) {
+ if (!p_cache_shapes || _shape_cache.is_null()) {
if (shape_type == "box") {
Ref<BoxShape3D> box;
box.instantiate();
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index cd25b93e6c..4653df7afe 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -69,6 +69,24 @@
#include <stdlib.h>
#include <cstdint>
+static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) {
+ if (!p_extras.is_empty()) {
+ p_node->set_meta("extras", p_extras);
+ }
+}
+
+static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) {
+ if (p_node->has_meta("extras")) {
+ Dictionary node_extras = p_node->get_meta("extras");
+ if (p_json.has("extras")) {
+ Dictionary extras = p_json["extras"];
+ extras.merge(node_extras);
+ } else {
+ p_json["extras"] = node_extras;
+ }
+ }
+}
+
static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
Ref<ImporterMesh> importer_mesh;
importer_mesh.instantiate();
@@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat,
mat_name, p_mesh->surface_get_format(surface_i));
}
+ importer_mesh->merge_meta_from(*p_mesh);
return importer_mesh;
}
@@ -280,8 +299,8 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) {
}
Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_file, ERR_INVALID_PARAMETER);
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_file.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_file->get_position() != 0, ERR_FILE_CANT_READ);
uint32_t magic = p_file->get_32();
ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF
@@ -458,7 +477,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
if (extensions.is_empty()) {
node.erase("extensions");
}
-
+ _attach_meta_to_extras(gltf_node, node);
nodes.push_back(node);
}
if (!nodes.is_empty()) {
@@ -624,6 +643,10 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
}
}
+ if (n.has("extras")) {
+ _attach_extras_to_meta(n["extras"], node);
+ }
+
if (n.has("children")) {
const Array &children = n["children"];
for (int j = 0; j < children.size(); j++) {
@@ -2727,6 +2750,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Dictionary e;
e["targetNames"] = target_names;
+ gltf_mesh["extras"] = e;
+ _attach_meta_to_extras(import_mesh, gltf_mesh);
weights.resize(target_names.size());
for (int name_i = 0; name_i < target_names.size(); name_i++) {
@@ -2742,8 +2767,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED);
- gltf_mesh["extras"] = e;
-
gltf_mesh["primitives"] = primitives;
meshes.push_back(gltf_mesh);
@@ -2776,6 +2799,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array primitives = d["primitives"];
const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+ _attach_extras_to_meta(extras, mesh);
Ref<ImporterMesh> import_mesh;
import_mesh.instantiate();
String mesh_name = "mesh";
@@ -3258,7 +3282,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
const int material = p["material"];
ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT);
Ref<Material> mat3d = p_state->materials[material];
- ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT);
Ref<BaseMaterial3D> base_material = mat3d;
if (has_vertex_color && base_material.is_valid()) {
@@ -3274,7 +3298,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
}
mat = mat3d;
}
- ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT);
+ ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT);
mat_name = mat->get_name();
}
import_mesh->add_surface(primitive, array, morphs,
@@ -3577,7 +3601,7 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector<
}
Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_path) {
- ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER);
if (!p_state->json.has("images")) {
return OK;
}
@@ -4170,6 +4194,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
}
d["extensions"] = extensions;
+ _attach_meta_to_extras(material, d);
materials.push_back(d);
}
if (!materials.size()) {
@@ -4372,6 +4397,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
}
}
}
+
+ if (material_dict.has("extras")) {
+ _attach_extras_to_meta(material_dict["extras"], material);
+ }
p_state->materials.push_back(material);
}
@@ -5161,6 +5190,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s
return mi;
}
mi->set_mesh(import_mesh);
+ import_mesh->merge_meta_from(*mesh);
return mi;
}
@@ -5285,6 +5315,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_root = current_node_i;
p_state->root_nodes.push_back(gltf_root);
}
+ gltf_node->merge_meta_from(p_current);
_create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
_convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root);
@@ -5676,6 +5707,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
current_node->set_transform(gltf_node->transform);
}
+ current_node->merge_meta_from(*gltf_node);
+
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
_generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root);
@@ -6934,14 +6967,14 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) {
}
Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) {
- ERR_FAIL_NULL_V(p_material, Dictionary());
+ ERR_FAIL_COND_V(p_material.is_null(), Dictionary());
Vector3 offset = p_material->get_uv1_offset();
Vector3 scale = p_material->get_uv1_scale();
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
}
Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) {
- ERR_FAIL_NULL_V(p_material, Dictionary());
+ ERR_FAIL_COND_V(p_material.is_null(), Dictionary());
Vector3 offset = p_material->get_uv2_offset();
Vector3 scale = p_material->get_uv2_scale();
return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y));
@@ -7060,6 +7093,8 @@ void GLTFDocument::_bind_methods() {
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
&GLTFDocument::unregister_gltf_document_extension);
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("get_supported_gltf_extensions"),
+ &GLTFDocument::get_supported_gltf_extensions);
}
void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) {
@@ -7100,6 +7135,36 @@ Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extension
return all_document_extensions;
}
+Vector<String> GLTFDocument::get_supported_gltf_extensions() {
+ HashSet<String> set = get_supported_gltf_extensions_hashset();
+ Vector<String> vec;
+ for (const String &s : set) {
+ vec.append(s);
+ }
+ vec.sort();
+ return vec;
+}
+
+HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
+ HashSet<String> supported_extensions;
+ // If the extension is supported directly in GLTFDocument, list it here.
+ // Other built-in extensions are supported by GLTFDocumentExtension classes.
+ supported_extensions.insert("GODOT_single_root");
+ supported_extensions.insert("KHR_lights_punctual");
+ supported_extensions.insert("KHR_materials_emissive_strength");
+ supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
+ supported_extensions.insert("KHR_materials_unlit");
+ supported_extensions.insert("KHR_texture_transform");
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Vector<String> ext_supported_extensions = ext->get_supported_extensions();
+ for (int i = 0; i < ext_supported_extensions.size(); ++i) {
+ supported_extensions.insert(ext_supported_extensions[i]);
+ }
+ }
+ return supported_extensions;
+}
+
PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) {
Error err = _encode_buffer_glb(p_state, "");
if (r_err) {
@@ -7280,7 +7345,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se
PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, PackedByteArray());
+ ERR_FAIL_COND_V(state.is_null(), PackedByteArray());
// For buffers, set the state filename to an empty string, but
// don't touch the base path, in case the user set it manually.
state->filename = "";
@@ -7292,7 +7357,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) {
Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER);
state->base_path = p_path.get_base_dir();
state->filename = p_path.get_file();
Error err = _serialize(state);
@@ -7308,7 +7373,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
Ref<GLTFState> state = p_state;
- ERR_FAIL_NULL_V(state, nullptr);
+ ERR_FAIL_COND_V(state.is_null(), nullptr);
ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr);
Error err = OK;
p_state->set_bake_fps(p_bake_fps);
@@ -7426,7 +7491,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN);
- ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN);
+ ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN);
String base_path = p_base_path;
if (base_path.is_empty()) {
base_path = p_path.get_base_dir();
@@ -7443,7 +7508,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint
}
Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
- ERR_FAIL_NULL_V(p_state, ERR_PARSE_ERROR);
+ ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR);
if (p_state->json.has("extensionsUsed")) {
Vector<String> ext_array = p_state->json["extensionsUsed"];
p_state->extensions_used = ext_array;
@@ -7452,19 +7517,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
Vector<String> ext_array = p_state->json["extensionsRequired"];
p_state->extensions_required = ext_array;
}
- HashSet<String> supported_extensions;
- supported_extensions.insert("KHR_lights_punctual");
- supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
- supported_extensions.insert("KHR_texture_transform");
- supported_extensions.insert("KHR_materials_unlit");
- supported_extensions.insert("KHR_materials_emissive_strength");
- for (Ref<GLTFDocumentExtension> ext : document_extensions) {
- ERR_CONTINUE(ext.is_null());
- Vector<String> ext_supported_extensions = ext->get_supported_extensions();
- for (int i = 0; i < ext_supported_extensions.size(); ++i) {
- supported_extensions.insert(ext_supported_extensions[i]);
- }
- }
+ HashSet<String> supported_extensions = get_supported_gltf_extensions_hashset();
Error ret = OK;
for (int i = 0; i < p_state->extensions_required.size(); i++) {
if (!supported_extensions.has(p_state->extensions_required[i])) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index d37544750d..b3e6dcf54a 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -92,6 +92,8 @@ public:
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
static void unregister_all_gltf_document_extensions();
static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions();
+ static Vector<String> get_supported_gltf_extensions();
+ static HashSet<String> get_supported_gltf_extensions_hashset();
void set_naming_version(int p_version);
int get_naming_version() const;
diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h
new file mode 100644
index 0000000000..96aadf3023
--- /dev/null
+++ b/modules/gltf/tests/test_gltf_extras.h
@@ -0,0 +1,165 @@
+/**************************************************************************/
+/* test_gltf_extras.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_GLTF_EXTRAS_H
+#define TEST_GLTF_EXTRAS_H
+
+#include "tests/test_macros.h"
+
+#ifdef TOOLS_ENABLED
+
+#include "core/os/os.h"
+#include "editor/import/3d/resource_importer_scene.h"
+#include "modules/gltf/editor/editor_scene_importer_gltf.h"
+#include "modules/gltf/gltf_document.h"
+#include "modules/gltf/gltf_state.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/main/window.h"
+#include "scene/resources/3d/primitive_meshes.h"
+#include "scene/resources/material.h"
+#include "scene/resources/packed_scene.h"
+
+namespace TestGltfExtras {
+
+static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) {
+ Ref<GLTFDocument> doc;
+ doc.instantiate();
+ Ref<GLTFState> state;
+ state.instantiate();
+ Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS);
+ CHECK_MESSAGE(err == OK, "GLTF state generation failed.");
+ err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf");
+ CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed.");
+
+ // Setting up importers.
+ Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true));
+ ResourceFormatImporter::get_singleton()->add_importer(import_scene);
+ Ref<EditorSceneFormatImporterGLTF> import_gltf;
+ import_gltf.instantiate();
+ ResourceImporterScene::add_scene_importer(import_gltf);
+
+ // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint
+ // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp
+ GLTFDocument::unregister_all_gltf_document_extensions();
+
+ HashMap<StringName, Variant> options(20);
+ options["nodes/root_type"] = "";
+ options["nodes/root_name"] = "";
+ options["nodes/apply_root_scale"] = true;
+ options["nodes/root_scale"] = 1.0;
+ options["meshes/ensure_tangents"] = true;
+ options["meshes/generate_lods"] = false;
+ options["meshes/create_shadow_meshes"] = true;
+ options["meshes/light_baking"] = 1;
+ options["meshes/lightmap_texel_size"] = 0.2;
+ options["meshes/force_disable_compression"] = false;
+ options["skins/use_named_skins"] = true;
+ options["animation/import"] = true;
+ options["animation/fps"] = 30;
+ options["animation/trimming"] = false;
+ options["animation/remove_immutable_tracks"] = true;
+ options["import_script/path"] = "";
+ options["_subresources"] = Dictionary();
+ options["gltf/naming_version"] = 1;
+
+ // Process gltf file, note that this generates `.scn` resource from the 2nd argument.
+ err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr);
+ CHECK_MESSAGE(err == OK, "GLTF import failed.");
+ ResourceImporterScene::remove_scene_importer(import_gltf);
+
+ Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
+ CHECK_MESSAGE(err == OK, "Loading scene failed.");
+ Node *p_scene = packed_scene->instantiate();
+ return p_scene;
+}
+
+TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") {
+ // Setup scene.
+ Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D);
+ original_material->set_albedo(Color(1.0, .0, .0));
+ original_material->set_name("material");
+ Dictionary material_dict;
+ material_dict["node_type"] = "material";
+ original_material->set_meta("extras", material_dict);
+
+ Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh);
+ original_meshdata->set_name("planemesh");
+ Dictionary meshdata_dict;
+ meshdata_dict["node_type"] = "planemesh";
+ original_meshdata->set_meta("extras", meshdata_dict);
+ original_meshdata->surface_set_material(0, original_material);
+
+ MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D);
+ original_mesh_instance->set_mesh(original_meshdata);
+ original_mesh_instance->set_name("mesh_instance_3d");
+ Dictionary mesh_instance_dict;
+ mesh_instance_dict["node_type"] = "mesh_instance_3d";
+ original_mesh_instance->set_meta("extras", mesh_instance_dict);
+
+ Node3D *original = memnew(Node3D);
+ SceneTree::get_singleton()->get_root()->add_child(original);
+ original->add_child(original_mesh_instance);
+ original->set_name("node3d");
+ Dictionary node_dict;
+ node_dict["node_type"] = "node3d";
+ original->set_meta("extras", node_dict);
+ original->set_meta("meta_not_nested_under_extras", "should not propagate");
+
+ // Convert to GLFT and back.
+ String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras");
+ Node *loaded = _gltf_export_then_import(original, tempfile);
+
+ // Compare the results.
+ CHECK(loaded->get_name() == "node3d");
+ CHECK(Dictionary(loaded->get_meta("extras")).size() == 1);
+ CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d");
+ CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras"));
+ CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras"));
+
+ MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true));
+ CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d");
+ CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d");
+
+ Ref<Mesh> mesh = mesh_instance_3d->get_mesh();
+ CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh");
+
+ Ref<Material> material = mesh->surface_get_material(0);
+ CHECK(material->get_name() == "material");
+ CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material");
+
+ memdelete(original_mesh_instance);
+ memdelete(original);
+ memdelete(loaded);
+}
+} // namespace TestGltfExtras
+
+#endif // TOOLS_ENABLED
+
+#endif // TEST_GLTF_EXTRAS_H
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 47a5b23895..89495e2c83 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -44,6 +44,9 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
@@ -1043,10 +1046,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
if (rd == nullptr) {
#if defined(RD_ENABLED)
-#if defined(VULKAN_ENABLED)
- rcd = memnew(RenderingContextDriverVulkan);
+#if defined(METAL_ENABLED)
+ rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
+#if defined(VULKAN_ENABLED)
+ if (rcd == nullptr) {
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+ }
+#endif
#endif
if (rcd != nullptr && rd != nullptr) {
err = rcd->initialize();
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 5720f844bb..394213963a 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
mp3dec_frame_info_t frame_info;
mp3d_sample_t *buf_frame = nullptr;
- int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
if (samples_mixed) {
p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
@@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
if (beat_loop && (int)frames_mixed >= beat_length_frames) {
for (int i = 0; i < FADE_SIZE; i++) {
- samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
if (!samples_mixed) {
break;
@@ -138,7 +138,7 @@ void AudioStreamPlaybackMP3::seek(double p_time) {
}
frames_mixed = uint32_t(mp3_stream->sample_rate * p_time);
- mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
+ mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
}
void AudioStreamPlaybackMP3::tag_used_streams() {
@@ -181,10 +181,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const {
}
AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() {
- if (mp3d) {
- mp3dec_ex_close(mp3d);
- memfree(mp3d);
- }
+ mp3dec_ex_close(&mp3d);
}
Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
@@ -197,9 +194,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
mp3s.instantiate();
mp3s->mp3_stream = Ref<AudioStreamMP3>(this);
- mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t));
- int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
+ int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
mp3s->frames_mixed = 0;
mp3s->active = false;
@@ -224,15 +220,19 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) {
int src_data_len = p_data.size();
const uint8_t *src_datar = p_data.ptr();
- mp3dec_ex_t mp3d;
- int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
- ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t);
+ int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
+ if (err || mp3d->info.hz == 0) {
+ memdelete(mp3d);
+ ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ }
- channels = mp3d.info.channels;
- sample_rate = mp3d.info.hz;
- length = float(mp3d.samples) / (sample_rate * float(channels));
+ channels = mp3d->info.channels;
+ sample_rate = mp3d->info.hz;
+ length = float(mp3d->samples) / (sample_rate * float(channels));
- mp3dec_ex_close(&mp3d);
+ mp3dec_ex_close(mp3d);
+ memdelete(mp3d);
clear_data();
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index 81e8f8633c..39d389b8cd 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
bool looping_override = false;
bool looping = false;
- mp3dec_ex_t *mp3d = nullptr;
+ mp3dec_ex_t mp3d = {};
uint32_t frames_mixed = 0;
bool active = false;
int loops = 0;
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 6d561c1566..177859f270 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -2796,7 +2796,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const
if (GDMonoCache::godot_api_cache_updated) {
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr);
- ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
+ ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'.");
} else {
scr = Ref<CSharpScript>(memnew(CSharpScript));
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index b699765b8e..032d067ae4 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(errorLine);
@@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(warningLine);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index ebb2677361..d7877fa5fc 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -265,11 +265,6 @@ namespace GodotTools.Build
success = Publish(buildInfo);
}
- if (!success)
- {
- ShowBuildErrorDialog("Failed to publish .NET project");
- }
-
return success;
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index ede0600ac1..a5f24fb67b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -75,7 +75,19 @@ namespace GodotTools.Export
};
}
- private string? _maybeLastExportError;
+ private void AddExceptionMessage(EditorExportPlatform platform, Exception exception)
+ {
+ string? exceptionMessage = exception.Message;
+ if (string.IsNullOrEmpty(exceptionMessage))
+ {
+ exceptionMessage = $"Exception thrown: {exception.GetType().Name}";
+ }
+
+ platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage);
+
+ // We also print exceptions as we receive them to stderr.
+ Console.Error.WriteLine(exception);
+ }
// With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
@@ -92,8 +104,8 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
{
- _maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
- "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.";
+ GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
+ "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.");
throw new InvalidOperationException($"{path} is a C# file but no solution file exists.");
}
@@ -124,16 +136,7 @@ namespace GodotTools.Export
}
catch (Exception e)
{
- _maybeLastExportError = e.Message;
-
- // 'maybeLastExportError' cannot be null or empty if there was an error, so we
- // must consider the possibility of exceptions being thrown without a message.
- if (string.IsNullOrEmpty(_maybeLastExportError))
- _maybeLastExportError = $"Exception thrown: {e.GetType().Name}";
-
- GD.PushError($"Failed to export project: {_maybeLastExportError}");
- Console.Error.WriteLine(e);
- // TODO: Do something on error once _ExportBegin supports failing.
+ AddExceptionMessage(GetExportPlatform(), e);
}
}
@@ -144,7 +147,9 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
return;
- if (!DeterminePlatformFromFeatures(features, out string? platform))
+ string osName = GetExportPlatform().GetOsName();
+
+ if (!TryDeterminePlatformFromOSName(osName, out string? platform))
throw new NotSupportedException("Target platform not supported.");
if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
@@ -445,25 +450,22 @@ namespace GodotTools.Export
Directory.Delete(folder, recursive: true);
}
_tempFolders.Clear();
-
- // TODO: The following is just a workaround until the export plugins can be made to abort with errors
-
- // We check for empty as well, because it's set to empty after hot-reloading
- if (!string.IsNullOrEmpty(_maybeLastExportError))
- {
- string lastExportError = _maybeLastExportError;
- _maybeLastExportError = null;
-
- GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project");
- }
}
- private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform)
+ /// <summary>
+ /// Tries to determine the platform from the export preset's platform OS name.
+ /// </summary>
+ /// <param name="osName">Name of the export operating system.</param>
+ /// <param name="platform">Platform name for the recognized supported platform.</param>
+ /// <returns>
+ /// <see langword="true"/> when the platform OS name is recognized as a supported platform,
+ /// <see langword="false"/> otherwise.
+ /// </returns>
+ private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform)
{
- foreach (var feature in features)
+ if (OS.PlatformFeatureMap.TryGetValue(osName, out platform))
{
- if (OS.PlatformFeatureMap.TryGetValue(feature, out platform))
- return true;
+ return true;
}
platform = null;
diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp
index e93c9a5faf..9c37ac810a 100644
--- a/modules/mono/editor/hostfxr_resolver.cpp
+++ b/modules/mono/editor/hostfxr_resolver.cpp
@@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) {
#endif
}
+#ifndef WINDOWS_ENABLED
bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) {
Error err = OK;
Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err);
@@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_
r_dotnet_root = line;
return true;
}
+#endif
bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
#if defined(WINDOWS_ENABLED)
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 80e44011be..3935854a29 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -194,7 +194,7 @@ private:
if (!has_data) {
// 3. Extract the data to a temporary location to load from there.
Ref<DirAccess> da = DirAccess::create_for_path(packed_path);
- ERR_FAIL_NULL(da);
+ ERR_FAIL_COND(da.is_null());
ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK);
}
api_assemblies_dir = data_dir_root;
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index d5d4b465d8..212fd1ef6b 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -227,10 +227,10 @@ void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) {
sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs;
}
SyncInfo &info = sync_data[p_frame.synchronizer];
- if (info.incoming_syncs) {
+ if (p_frame.incoming_syncs) {
info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs;
}
- if (info.outgoing_syncs) {
+ if (p_frame.outgoing_syncs) {
info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs;
}
}
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 33b92f6266..78983187c7 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
- if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- finished_task_ids.push_back(E.key);
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
- NavMeshGeneratorTask2D *generator_task = E.value;
- DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
- baking_navmeshes.erase(generator_task->navigation_mesh);
- if (generator_task->callback.is_valid()) {
- generator_emit_callback(generator_task->callback);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
}
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator2D::cleanup() {
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ baking_navmeshes.clear();
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- NavMeshGeneratorTask2D *generator_task = E.value;
- memdelete(generator_task);
- }
- generator_tasks.clear();
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
- generator_rid_rwlock.write_lock();
- for (NavMeshGeometryParser2D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser2D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator2D::finish() {
@@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D");
generator_tasks.insert(generator_task->thread_task_id, generator_task);
- generator_task_mutex.unlock();
}
bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_polygon);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_polygon);
}
void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index d17724baa0..e92a9d304b 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
- if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- finished_task_ids.push_back(E.key);
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
- NavMeshGeneratorTask3D *generator_task = E.value;
- DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
- baking_navmeshes.erase(generator_task->navigation_mesh);
- if (generator_task->callback.is_valid()) {
- generator_emit_callback(generator_task->callback);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
}
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::cleanup() {
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ baking_navmeshes.clear();
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- NavMeshGeneratorTask3D *generator_task = E.value;
- memdelete(generator_task);
- }
- generator_tasks.clear();
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
- generator_rid_rwlock.write_lock();
- for (NavMeshGeometryParser3D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser3D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::finish() {
@@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
generator_tasks.insert(generator_task->thread_task_id, generator_task);
- generator_task_mutex.unlock();
}
bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_mesh);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_mesh);
}
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp
new file mode 100644
index 0000000000..70207f86ce
--- /dev/null
+++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp
@@ -0,0 +1,715 @@
+/**************************************************************************/
+/* nav_mesh_queries_3d.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. */
+/**************************************************************************/
+
+#ifndef _3D_DISABLED
+
+#include "nav_mesh_queries_3d.h"
+
+#include "../nav_base.h"
+
+#include "core/math/geometry_3d.h"
+
+#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
+
+#define APPEND_METADATA(poly) \
+ if (r_path_types) { \
+ r_path_types->push_back(poly->owner->get_type()); \
+ } \
+ if (r_path_rids) { \
+ r_path_rids->push_back(poly->owner->get_self()); \
+ } \
+ if (r_path_owners) { \
+ r_path_owners->push_back(poly->owner->get_owner_id()); \
+ }
+
+Vector3 NavMeshQueries3D::polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly) {
+ const LocalVector<gd::Polygon> &region_polygons = p_polygons;
+
+ if (region_polygons.is_empty()) {
+ return Vector3();
+ }
+
+ if (p_uniformly) {
+ real_t accumulated_area = 0;
+ RBMap<real_t, uint32_t> region_area_map;
+
+ for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) {
+ const gd::Polygon &region_polygon = region_polygons[rp_index];
+ real_t polyon_area = region_polygon.surface_area;
+
+ if (polyon_area == 0.0) {
+ continue;
+ }
+ region_area_map[accumulated_area] = rp_index;
+ accumulated_area += polyon_area;
+ }
+ if (region_area_map.is_empty() || accumulated_area == 0) {
+ // All polygons have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t region_area_map_pos = Math::random(real_t(0), accumulated_area);
+
+ RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos);
+ ERR_FAIL_COND_V(!region_E, Vector3());
+ uint32_t rrp_polygon_index = region_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3());
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ real_t accumulated_polygon_area = 0;
+ RBMap<real_t, uint32_t> polygon_area_map;
+
+ for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) {
+ real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area();
+
+ if (face_area == 0.0) {
+ continue;
+ }
+ polygon_area_map[accumulated_polygon_area] = rpp_index;
+ accumulated_polygon_area += face_area;
+ }
+ if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) {
+ // All faces have no real surface / no area.
+ return Vector3();
+ }
+
+ real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area);
+
+ RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos);
+ ERR_FAIL_COND_V(!polygon_E, Vector3());
+ uint32_t rrp_face_index = polygon_E->value;
+ ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3());
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+
+ } else {
+ uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1);
+
+ const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
+
+ uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1);
+
+ const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
+
+ Vector3 face_random_position = face.get_random_point_inside();
+ return face_random_position;
+ }
+}
+
+Vector<Vector3> NavMeshQueries3D::polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size) {
+ // Clear metadata outputs.
+ if (r_path_types) {
+ r_path_types->clear();
+ }
+ if (r_path_rids) {
+ r_path_rids->clear();
+ }
+ if (r_path_owners) {
+ r_path_owners->clear();
+ }
+
+ // Find the start poly and the end poly on this map.
+ const gd::Polygon *begin_poly = nullptr;
+ const gd::Polygon *end_poly = nullptr;
+ Vector3 begin_point;
+ Vector3 end_point;
+ real_t begin_d = FLT_MAX;
+ real_t end_d = FLT_MAX;
+ // Find the initial poly and the end poly on this map.
+ for (const gd::Polygon &p : p_polygons) {
+ // Only consider the polygon if it in a region with compatible layers.
+ if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) {
+ continue;
+ }
+
+ // For each face check the distance between the origin/destination
+ for (size_t point_id = 2; point_id < p.points.size(); point_id++) {
+ const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
+
+ Vector3 point = face.get_closest_point_to(p_origin);
+ real_t distance_to_point = point.distance_to(p_origin);
+ if (distance_to_point < begin_d) {
+ begin_d = distance_to_point;
+ begin_poly = &p;
+ begin_point = point;
+ }
+
+ point = face.get_closest_point_to(p_destination);
+ distance_to_point = point.distance_to(p_destination);
+ if (distance_to_point < end_d) {
+ end_d = distance_to_point;
+ end_poly = &p;
+ end_point = point;
+ }
+ }
+ }
+
+ // Check for trivial cases
+ if (!begin_poly || !end_poly) {
+ return Vector<Vector3>();
+ }
+ if (begin_poly == end_poly) {
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = end_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = end_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = end_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ // List of all reachable navigation polys.
+ LocalVector<gd::NavigationPoly> navigation_polys;
+ navigation_polys.resize(p_polygons.size() + p_link_polygons_size);
+
+ // Initialize the matching navigation polygon.
+ gd::NavigationPoly &begin_navigation_poly = navigation_polys[begin_poly->id];
+ begin_navigation_poly.poly = begin_poly;
+ begin_navigation_poly.entry = begin_point;
+ begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
+ begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
+
+ // Heap of polygons to travel next.
+ gd::Heap<gd::NavigationPoly *, gd::NavPolyTravelCostGreaterThan, gd::NavPolyHeapIndexer>
+ traversable_polys;
+ traversable_polys.reserve(p_polygons.size() * 0.25);
+
+ // This is an implementation of the A* algorithm.
+ int least_cost_id = begin_poly->id;
+ int prev_least_cost_id = -1;
+ bool found_route = false;
+
+ const gd::Polygon *reachable_end = nullptr;
+ real_t distance_to_reachable_end = FLT_MAX;
+ bool is_reachable = true;
+
+ while (true) {
+ // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
+ for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) {
+ // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
+ for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
+ const gd::Edge::Connection &connection = edge.connections[connection_index];
+
+ // Only consider the connection to another polygon if this polygon is in a region with compatible layers.
+ if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) {
+ continue;
+ }
+
+ const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id];
+ real_t poly_enter_cost = 0.0;
+ real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
+
+ if (prev_least_cost_id != -1 && navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self()) {
+ poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
+ }
+ prev_least_cost_id = least_cost_id;
+
+ Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
+ const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway);
+ const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance;
+
+ // Check if the neighbor polygon has already been processed.
+ gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id];
+ if (neighbor_poly.poly != nullptr) {
+ // If the neighbor polygon hasn't been traversed yet and the new path leading to
+ // it is shorter, update the polygon.
+ if (neighbor_poly.traversable_poly_index < traversable_polys.size() &&
+ new_traveled_distance < neighbor_poly.traveled_distance) {
+ neighbor_poly.back_navigation_poly_id = least_cost_id;
+ neighbor_poly.back_navigation_edge = connection.edge;
+ neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
+ neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
+ neighbor_poly.traveled_distance = new_traveled_distance;
+ neighbor_poly.distance_to_destination =
+ new_entry.distance_to(end_point) *
+ neighbor_poly.poly->owner->get_travel_cost();
+ neighbor_poly.entry = new_entry;
+
+ // Update the priority of the polygon in the heap.
+ traversable_polys.shift(neighbor_poly.traversable_poly_index);
+ }
+ } else {
+ // Initialize the matching navigation polygon.
+ neighbor_poly.poly = connection.polygon;
+ neighbor_poly.back_navigation_poly_id = least_cost_id;
+ neighbor_poly.back_navigation_edge = connection.edge;
+ neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start;
+ neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end;
+ neighbor_poly.traveled_distance = new_traveled_distance;
+ neighbor_poly.distance_to_destination =
+ new_entry.distance_to(end_point) *
+ neighbor_poly.poly->owner->get_travel_cost();
+ neighbor_poly.entry = new_entry;
+
+ // Add the polygon to the heap of polygons to traverse next.
+ traversable_polys.push(&neighbor_poly);
+ }
+ }
+ }
+
+ // When the heap of traversable polygons is empty at this point it means the end polygon is
+ // unreachable.
+ if (traversable_polys.is_empty()) {
+ // Thus use the further reachable polygon
+ ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
+ is_reachable = false;
+ if (reachable_end == nullptr) {
+ // The path is not found and there is not a way out.
+ break;
+ }
+
+ // Set as end point the furthest reachable point.
+ end_poly = reachable_end;
+ end_d = FLT_MAX;
+ for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) {
+ Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ }
+ }
+
+ // Search all faces of start polygon as well.
+ bool closest_point_on_start_poly = false;
+ for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
+ Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ closest_point_on_start_poly = true;
+ }
+ }
+
+ if (closest_point_on_start_poly) {
+ // No point to run PostProcessing when start and end convex polygon is the same.
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = begin_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = begin_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = begin_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ for (gd::NavigationPoly &nav_poly : navigation_polys) {
+ nav_poly.poly = nullptr;
+ }
+ navigation_polys[begin_poly->id].poly = begin_poly;
+
+ least_cost_id = begin_poly->id;
+ prev_least_cost_id = -1;
+
+ reachable_end = nullptr;
+
+ continue;
+ }
+
+ // Pop the polygon with the lowest travel cost from the heap of traversable polygons.
+ least_cost_id = traversable_polys.pop()->poly->id;
+
+ // Store the farthest reachable end polygon in case our goal is not reachable.
+ if (is_reachable) {
+ real_t distance = navigation_polys[least_cost_id].entry.distance_to(p_destination);
+ if (distance_to_reachable_end > distance) {
+ distance_to_reachable_end = distance;
+ reachable_end = navigation_polys[least_cost_id].poly;
+ }
+ }
+
+ // Check if we reached the end
+ if (navigation_polys[least_cost_id].poly == end_poly) {
+ found_route = true;
+ break;
+ }
+ }
+
+ // We did not find a route but we have both a start polygon and an end polygon at this point.
+ // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon.
+ if (!found_route) {
+ end_d = FLT_MAX;
+ // Search all faces of the start polygon for the closest point to our target position.
+ for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
+ Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
+ Vector3 spoint = f.get_closest_point_to(p_destination);
+ real_t dpoint = spoint.distance_to(p_destination);
+ if (dpoint < end_d) {
+ end_point = spoint;
+ end_d = dpoint;
+ }
+ }
+
+ if (r_path_types) {
+ r_path_types->resize(2);
+ r_path_types->write[0] = begin_poly->owner->get_type();
+ r_path_types->write[1] = begin_poly->owner->get_type();
+ }
+
+ if (r_path_rids) {
+ r_path_rids->resize(2);
+ (*r_path_rids)[0] = begin_poly->owner->get_self();
+ (*r_path_rids)[1] = begin_poly->owner->get_self();
+ }
+
+ if (r_path_owners) {
+ r_path_owners->resize(2);
+ r_path_owners->write[0] = begin_poly->owner->get_owner_id();
+ r_path_owners->write[1] = begin_poly->owner->get_owner_id();
+ }
+
+ Vector<Vector3> path;
+ path.resize(2);
+ path.write[0] = begin_point;
+ path.write[1] = end_point;
+ return path;
+ }
+
+ Vector<Vector3> path;
+ // Optimize the path.
+ if (p_optimize) {
+ // Set the apex poly/point to the end point
+ gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
+
+ Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end };
+ const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway);
+ if (end_point.is_equal_approx(back_edge_closest_point)) {
+ // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing.
+ // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon.
+ if (apex_poly->back_navigation_poly_id != -1) {
+ apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id];
+ }
+ }
+
+ Vector3 apex_point = end_point;
+
+ gd::NavigationPoly *left_poly = apex_poly;
+ Vector3 left_portal = apex_point;
+ gd::NavigationPoly *right_poly = apex_poly;
+ Vector3 right_portal = apex_point;
+
+ gd::NavigationPoly *p = apex_poly;
+
+ path.push_back(end_point);
+ APPEND_METADATA(end_poly);
+
+ while (p) {
+ // Set left and right points of the pathway between polygons.
+ Vector3 left = p->back_navigation_edge_pathway_start;
+ Vector3 right = p->back_navigation_edge_pathway_end;
+ if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(p_map_up) < 0) {
+ SWAP(left, right);
+ }
+
+ bool skip = false;
+ if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(p_map_up) >= 0) {
+ //process
+ if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(p_map_up) > 0) {
+ left_poly = p;
+ left_portal = left;
+ } else {
+ clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners, p_map_up);
+
+ apex_point = right_portal;
+ p = right_poly;
+ left_poly = p;
+ apex_poly = p;
+ left_portal = apex_point;
+ right_portal = apex_point;
+
+ path.push_back(apex_point);
+ APPEND_METADATA(apex_poly->poly);
+ skip = true;
+ }
+ }
+
+ if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(p_map_up) <= 0) {
+ //process
+ if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(p_map_up) < 0) {
+ right_poly = p;
+ right_portal = right;
+ } else {
+ clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners, p_map_up);
+
+ apex_point = left_portal;
+ p = left_poly;
+ right_poly = p;
+ apex_poly = p;
+ right_portal = apex_point;
+ left_portal = apex_point;
+
+ path.push_back(apex_point);
+ APPEND_METADATA(apex_poly->poly);
+ }
+ }
+
+ // Go to the previous polygon.
+ if (p->back_navigation_poly_id != -1) {
+ p = &navigation_polys[p->back_navigation_poly_id];
+ } else {
+ // The end
+ p = nullptr;
+ }
+ }
+
+ // If the last point is not the begin point, add it to the list.
+ if (path[path.size() - 1] != begin_point) {
+ path.push_back(begin_point);
+ APPEND_METADATA(begin_poly);
+ }
+
+ path.reverse();
+ if (r_path_types) {
+ r_path_types->reverse();
+ }
+ if (r_path_rids) {
+ r_path_rids->reverse();
+ }
+ if (r_path_owners) {
+ r_path_owners->reverse();
+ }
+
+ } else {
+ path.push_back(end_point);
+ APPEND_METADATA(end_poly);
+
+ // Add mid points
+ int np_id = least_cost_id;
+ while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) {
+ if (navigation_polys[np_id].back_navigation_edge != -1) {
+ int prev = navigation_polys[np_id].back_navigation_edge;
+ int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
+ Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
+
+ path.push_back(point);
+ APPEND_METADATA(navigation_polys[np_id].poly);
+ } else {
+ path.push_back(navigation_polys[np_id].entry);
+ APPEND_METADATA(navigation_polys[np_id].poly);
+ }
+
+ np_id = navigation_polys[np_id].back_navigation_poly_id;
+ }
+
+ path.push_back(begin_point);
+ APPEND_METADATA(begin_poly);
+
+ path.reverse();
+ if (r_path_types) {
+ r_path_types->reverse();
+ }
+ if (r_path_rids) {
+ r_path_rids->reverse();
+ }
+ if (r_path_owners) {
+ r_path_owners->reverse();
+ }
+ }
+
+ // Ensure post conditions (path arrays MUST match in size).
+ CRASH_COND(r_path_types && path.size() != r_path_types->size());
+ CRASH_COND(r_path_rids && path.size() != r_path_rids->size());
+ CRASH_COND(r_path_owners && path.size() != r_path_owners->size());
+
+ return path;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) {
+ bool use_collision = p_use_collision;
+ Vector3 closest_point;
+ real_t closest_point_distance = FLT_MAX;
+
+ for (const gd::Polygon &polygon : p_polygons) {
+ // For each face check the distance to the segment.
+ for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) {
+ const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos);
+ Vector3 intersection_point;
+ if (face.intersects_segment(p_from, p_to, &intersection_point)) {
+ const real_t d = p_from.distance_to(intersection_point);
+ if (!use_collision) {
+ closest_point = intersection_point;
+ use_collision = true;
+ closest_point_distance = d;
+ } else if (closest_point_distance > d) {
+ closest_point = intersection_point;
+ closest_point_distance = d;
+ }
+ }
+ // If segment does not itersect face, check the distance from segment's endpoints.
+ else if (!use_collision) {
+ const Vector3 p_from_closest = face.get_closest_point_to(p_from);
+ const real_t d_p_from = p_from.distance_to(p_from_closest);
+ if (closest_point_distance > d_p_from) {
+ closest_point = p_from_closest;
+ closest_point_distance = d_p_from;
+ }
+
+ const Vector3 p_to_closest = face.get_closest_point_to(p_to);
+ const real_t d_p_to = p_to.distance_to(p_to_closest);
+ if (closest_point_distance > d_p_to) {
+ closest_point = p_to_closest;
+ closest_point_distance = d_p_to;
+ }
+ }
+ }
+ // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment.
+ if (!use_collision) {
+ for (size_t point_id = 0; point_id < polygon.points.size(); point_id += 1) {
+ Vector3 a, b;
+
+ Geometry3D::get_closest_points_between_segments(
+ p_from,
+ p_to,
+ polygon.points[point_id].pos,
+ polygon.points[(point_id + 1) % polygon.points.size()].pos,
+ a,
+ b);
+
+ const real_t d = a.distance_to(b);
+ if (d < closest_point_distance) {
+ closest_point_distance = d;
+ closest_point = b;
+ }
+ }
+ }
+ }
+
+ return closest_point;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.point;
+}
+
+Vector3 NavMeshQueries3D::polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.normal;
+}
+
+gd::ClosestPointQueryResult NavMeshQueries3D::polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult result;
+ real_t closest_point_distance_squared = FLT_MAX;
+
+ for (const gd::Polygon &polygon : p_polygons) {
+ for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) {
+ const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos);
+ const Vector3 closest_point_on_face = face.get_closest_point_to(p_point);
+ const real_t distance_squared_to_point = closest_point_on_face.distance_squared_to(p_point);
+ if (distance_squared_to_point < closest_point_distance_squared) {
+ result.point = closest_point_on_face;
+ result.normal = face.get_plane().normal;
+ result.owner = polygon.owner->get_self();
+ closest_point_distance_squared = distance_squared_to_point;
+ }
+ }
+ }
+
+ return result;
+}
+
+RID NavMeshQueries3D::polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) {
+ gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point);
+ return cp.owner;
+}
+
+void NavMeshQueries3D::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up) {
+ Vector3 from = path[path.size() - 1];
+
+ if (from.is_equal_approx(p_to_point)) {
+ return;
+ }
+
+ Plane cut_plane;
+ cut_plane.normal = (from - p_to_point).cross(p_map_up);
+ if (cut_plane.normal == Vector3()) {
+ return;
+ }
+ cut_plane.normal.normalize();
+ cut_plane.d = cut_plane.normal.dot(from);
+
+ while (from_poly != p_to_poly) {
+ Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start;
+ Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end;
+
+ ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1);
+ from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id];
+
+ if (!pathway_start.is_equal_approx(pathway_end)) {
+ Vector3 inters;
+ if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) {
+ if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) {
+ path.push_back(inters);
+ APPEND_METADATA(from_poly->poly);
+ }
+ }
+ }
+ }
+}
+
+#endif // _3D_DISABLED
diff --git a/modules/navigation/3d/nav_mesh_queries_3d.h b/modules/navigation/3d/nav_mesh_queries_3d.h
new file mode 100644
index 0000000000..109bb2f971
--- /dev/null
+++ b/modules/navigation/3d/nav_mesh_queries_3d.h
@@ -0,0 +1,54 @@
+/**************************************************************************/
+/* nav_mesh_queries_3d.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 NAV_MESH_QUERIES_3D_H
+#define NAV_MESH_QUERIES_3D_H
+
+#ifndef _3D_DISABLED
+
+#include "../nav_map.h"
+
+class NavMeshQueries3D {
+public:
+ static Vector3 polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly);
+
+ static Vector<Vector3> polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size);
+ static Vector3 polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision);
+ static Vector3 polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static Vector3 polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static gd::ClosestPointQueryResult polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+ static RID polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point);
+
+ static void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up);
+};
+
+#endif // _3D_DISABLED
+
+#endif // NAV_MESH_QUERIES_3D_H
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 0c91e8dea3..dd77e81b45 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -35,25 +35,13 @@
#include "nav_obstacle.h"
#include "nav_region.h"
+#include "3d/nav_mesh_queries_3d.h"
+
#include "core/config/project_settings.h"
#include "core/object/worker_thread_pool.h"
#include <Obstacle2d.h>
-#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a)))
-
-// Helper macro
-#define APPEND_METADATA(poly) \
- if (r_path_types) { \
- r_path_types->push_back(poly->owner->get_type()); \
- } \
- if (r_path_rids) { \
- r_path_rids->push_back(poly->owner->get_self()); \
- } \
- if (r_path_owners) { \
- r_path_owners->push_back(poly->owner->get_owner_id()); \
- }
-
#ifdef DEBUG_ENABLED
#define NAVMAP_ITERATION_ZERO_ERROR_MSG() \
ERR_PRINT_ONCE("NavigationServer navigation map query failed because it was made before first map synchronization.\n\
@@ -142,462 +130,9 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p
return Vector<Vector3>();
}
- // Clear metadata outputs.
- if (r_path_types) {
- r_path_types->clear();
- }
- if (r_path_rids) {
- r_path_rids->clear();
- }
- if (r_path_owners) {
- r_path_owners->clear();
- }
-
- // Find the start poly and the end poly on this map.
- const gd::Polygon *begin_poly = nullptr;
- const gd::Polygon *end_poly = nullptr;
- Vector3 begin_point;
- Vector3 end_point;
- real_t begin_d = FLT_MAX;
- real_t end_d = FLT_MAX;
- // Find the initial poly and the end poly on this map.
- for (const gd::Polygon &p : polygons) {
- // Only consider the polygon if it in a region with compatible layers.
- if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) {
- continue;
- }
-
- // For each face check the distance between the origin/destination
- for (size_t point_id = 2; point_id < p.points.size(); point_id++) {
- const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
-
- Vector3 point = face.get_closest_point_to(p_origin);
- real_t distance_to_point = point.distance_to(p_origin);
- if (distance_to_point < begin_d) {
- begin_d = distance_to_point;
- begin_poly = &p;
- begin_point = point;
- }
-
- point = face.get_closest_point_to(p_destination);
- distance_to_point = point.distance_to(p_destination);
- if (distance_to_point < end_d) {
- end_d = distance_to_point;
- end_poly = &p;
- end_point = point;
- }
- }
- }
-
- // Check for trivial cases
- if (!begin_poly || !end_poly) {
- return Vector<Vector3>();
- }
- if (begin_poly == end_poly) {
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = end_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = end_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = end_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- // List of all reachable navigation polys.
- LocalVector<gd::NavigationPoly> navigation_polys;
- navigation_polys.reserve(polygons.size() * 0.75);
-
- // Add the start polygon to the reachable navigation polygons.
- gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly);
- begin_navigation_poly.self_id = 0;
- begin_navigation_poly.entry = begin_point;
- begin_navigation_poly.back_navigation_edge_pathway_start = begin_point;
- begin_navigation_poly.back_navigation_edge_pathway_end = begin_point;
- navigation_polys.push_back(begin_navigation_poly);
-
- // List of polygon IDs to visit.
- List<uint32_t> to_visit;
- to_visit.push_back(0);
-
- // This is an implementation of the A* algorithm.
- int least_cost_id = 0;
- int prev_least_cost_id = -1;
- bool found_route = false;
-
- const gd::Polygon *reachable_end = nullptr;
- real_t reachable_d = FLT_MAX;
- bool is_reachable = true;
-
- while (true) {
- // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance.
- for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) {
- // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon.
- for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) {
- const gd::Edge::Connection &connection = edge.connections[connection_index];
-
- // Only consider the connection to another polygon if this polygon is in a region with compatible layers.
- if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) {
- continue;
- }
-
- const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id];
- real_t poly_enter_cost = 0.0;
- real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost();
-
- if (prev_least_cost_id != -1 && (navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self())) {
- poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost();
- }
- prev_least_cost_id = least_cost_id;
-
- Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end };
- const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway);
- const real_t new_distance = (least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly.traveled_distance;
-
- int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon));
-
- if (already_visited_polygon_index != -1) {
- // Polygon already visited, check if we can reduce the travel cost.
- gd::NavigationPoly &avp = navigation_polys[already_visited_polygon_index];
- if (new_distance < avp.traveled_distance) {
- avp.back_navigation_poly_id = least_cost_id;
- avp.back_navigation_edge = connection.edge;
- avp.back_navigation_edge_pathway_start = connection.pathway_start;
- avp.back_navigation_edge_pathway_end = connection.pathway_end;
- avp.traveled_distance = new_distance;
- avp.entry = new_entry;
- }
- } else {
- // Add the neighbor polygon to the reachable ones.
- gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon);
- new_navigation_poly.self_id = navigation_polys.size();
- new_navigation_poly.back_navigation_poly_id = least_cost_id;
- new_navigation_poly.back_navigation_edge = connection.edge;
- new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start;
- new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end;
- new_navigation_poly.traveled_distance = new_distance;
- new_navigation_poly.entry = new_entry;
- navigation_polys.push_back(new_navigation_poly);
-
- // Add the neighbor polygon to the polygons to visit.
- to_visit.push_back(navigation_polys.size() - 1);
- }
- }
- }
-
- // Removes the least cost polygon from the list of polygons to visit so we can advance.
- to_visit.erase(least_cost_id);
-
- // When the list of polygons to visit is empty at this point it means the End Polygon is not reachable
- if (to_visit.size() == 0) {
- // Thus use the further reachable polygon
- ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons");
- is_reachable = false;
- if (reachable_end == nullptr) {
- // The path is not found and there is not a way out.
- break;
- }
-
- // Set as end point the furthest reachable point.
- end_poly = reachable_end;
- end_d = FLT_MAX;
- for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) {
- Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- }
- }
-
- // Search all faces of start polygon as well.
- bool closest_point_on_start_poly = false;
- for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
- Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- closest_point_on_start_poly = true;
- }
- }
-
- if (closest_point_on_start_poly) {
- // No point to run PostProcessing when start and end convex polygon is the same.
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = begin_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = begin_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = begin_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- // Reset open and navigation_polys
- gd::NavigationPoly np = navigation_polys[0];
- navigation_polys.clear();
- navigation_polys.push_back(np);
- to_visit.clear();
- to_visit.push_back(0);
- least_cost_id = 0;
- prev_least_cost_id = -1;
-
- reachable_end = nullptr;
-
- continue;
- }
-
- // Find the polygon with the minimum cost from the list of polygons to visit.
- least_cost_id = -1;
- real_t least_cost = FLT_MAX;
- for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) {
- gd::NavigationPoly *np = &navigation_polys[element->get()];
- real_t cost = np->traveled_distance;
- cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost());
- if (cost < least_cost) {
- least_cost_id = np->self_id;
- least_cost = cost;
- }
- }
-
- ERR_BREAK(least_cost_id == -1);
-
- // Stores the further reachable end polygon, in case our goal is not reachable.
- if (is_reachable) {
- real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination);
- if (reachable_d > d) {
- reachable_d = d;
- reachable_end = navigation_polys[least_cost_id].poly;
- }
- }
-
- // Check if we reached the end
- if (navigation_polys[least_cost_id].poly == end_poly) {
- found_route = true;
- break;
- }
- }
-
- // We did not find a route but we have both a start polygon and an end polygon at this point.
- // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon.
- if (!found_route) {
- end_d = FLT_MAX;
- // Search all faces of the start polygon for the closest point to our target position.
- for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) {
- Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos);
- Vector3 spoint = f.get_closest_point_to(p_destination);
- real_t dpoint = spoint.distance_to(p_destination);
- if (dpoint < end_d) {
- end_point = spoint;
- end_d = dpoint;
- }
- }
-
- if (r_path_types) {
- r_path_types->resize(2);
- r_path_types->write[0] = begin_poly->owner->get_type();
- r_path_types->write[1] = begin_poly->owner->get_type();
- }
-
- if (r_path_rids) {
- r_path_rids->resize(2);
- (*r_path_rids)[0] = begin_poly->owner->get_self();
- (*r_path_rids)[1] = begin_poly->owner->get_self();
- }
-
- if (r_path_owners) {
- r_path_owners->resize(2);
- r_path_owners->write[0] = begin_poly->owner->get_owner_id();
- r_path_owners->write[1] = begin_poly->owner->get_owner_id();
- }
-
- Vector<Vector3> path;
- path.resize(2);
- path.write[0] = begin_point;
- path.write[1] = end_point;
- return path;
- }
-
- Vector<Vector3> path;
- // Optimize the path.
- if (p_optimize) {
- // Set the apex poly/point to the end point
- gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id];
-
- Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end };
- const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway);
- if (end_point.is_equal_approx(back_edge_closest_point)) {
- // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing.
- // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon.
- if (apex_poly->back_navigation_poly_id != -1) {
- apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id];
- }
- }
-
- Vector3 apex_point = end_point;
-
- gd::NavigationPoly *left_poly = apex_poly;
- Vector3 left_portal = apex_point;
- gd::NavigationPoly *right_poly = apex_poly;
- Vector3 right_portal = apex_point;
-
- gd::NavigationPoly *p = apex_poly;
-
- path.push_back(end_point);
- APPEND_METADATA(end_poly);
-
- while (p) {
- // Set left and right points of the pathway between polygons.
- Vector3 left = p->back_navigation_edge_pathway_start;
- Vector3 right = p->back_navigation_edge_pathway_end;
- if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(up) < 0) {
- SWAP(left, right);
- }
-
- bool skip = false;
- if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(up) >= 0) {
- //process
- if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(up) > 0) {
- left_poly = p;
- left_portal = left;
- } else {
- clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners);
-
- apex_point = right_portal;
- p = right_poly;
- left_poly = p;
- apex_poly = p;
- left_portal = apex_point;
- right_portal = apex_point;
-
- path.push_back(apex_point);
- APPEND_METADATA(apex_poly->poly);
- skip = true;
- }
- }
-
- if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(up) <= 0) {
- //process
- if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(up) < 0) {
- right_poly = p;
- right_portal = right;
- } else {
- clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners);
-
- apex_point = left_portal;
- p = left_poly;
- right_poly = p;
- apex_poly = p;
- right_portal = apex_point;
- left_portal = apex_point;
-
- path.push_back(apex_point);
- APPEND_METADATA(apex_poly->poly);
- }
- }
-
- // Go to the previous polygon.
- if (p->back_navigation_poly_id != -1) {
- p = &navigation_polys[p->back_navigation_poly_id];
- } else {
- // The end
- p = nullptr;
- }
- }
-
- // If the last point is not the begin point, add it to the list.
- if (path[path.size() - 1] != begin_point) {
- path.push_back(begin_point);
- APPEND_METADATA(begin_poly);
- }
-
- path.reverse();
- if (r_path_types) {
- r_path_types->reverse();
- }
- if (r_path_rids) {
- r_path_rids->reverse();
- }
- if (r_path_owners) {
- r_path_owners->reverse();
- }
-
- } else {
- path.push_back(end_point);
- APPEND_METADATA(end_poly);
-
- // Add mid points
- int np_id = least_cost_id;
- while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) {
- if (navigation_polys[np_id].back_navigation_edge != -1) {
- int prev = navigation_polys[np_id].back_navigation_edge;
- int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size();
- Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5;
-
- path.push_back(point);
- APPEND_METADATA(navigation_polys[np_id].poly);
- } else {
- path.push_back(navigation_polys[np_id].entry);
- APPEND_METADATA(navigation_polys[np_id].poly);
- }
-
- np_id = navigation_polys[np_id].back_navigation_poly_id;
- }
-
- path.push_back(begin_point);
- APPEND_METADATA(begin_poly);
-
- path.reverse();
- if (r_path_types) {
- r_path_types->reverse();
- }
- if (r_path_rids) {
- r_path_rids->reverse();
- }
- if (r_path_owners) {
- r_path_owners->reverse();
- }
- }
-
- // Ensure post conditions (path arrays MUST match in size).
- CRASH_COND(r_path_types && path.size() != r_path_types->size());
- CRASH_COND(r_path_rids && path.size() != r_path_rids->size());
- CRASH_COND(r_path_owners && path.size() != r_path_owners->size());
-
- return path;
+ return NavMeshQueries3D::polygons_get_path(
+ polygons, p_origin, p_destination, p_optimize, p_navigation_layers,
+ r_path_types, r_path_rids, r_path_owners, up, link_polygons.size());
}
Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const {
@@ -607,66 +142,7 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector
return Vector3();
}
- bool use_collision = p_use_collision;
- Vector3 closest_point;
- real_t closest_point_d = FLT_MAX;
-
- for (const gd::Polygon &p : polygons) {
- // For each face check the distance to the segment
- for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) {
- const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
- Vector3 inters;
- if (f.intersects_segment(p_from, p_to, &inters)) {
- const real_t d = p_from.distance_to(inters);
- if (use_collision == false) {
- closest_point = inters;
- use_collision = true;
- closest_point_d = d;
- } else if (closest_point_d > d) {
- closest_point = inters;
- closest_point_d = d;
- }
- }
- // If segment does not itersect face, check the distance from segment's endpoints.
- else if (!use_collision) {
- const Vector3 p_from_closest = f.get_closest_point_to(p_from);
- const real_t d_p_from = p_from.distance_to(p_from_closest);
- if (closest_point_d > d_p_from) {
- closest_point = p_from_closest;
- closest_point_d = d_p_from;
- }
-
- const Vector3 p_to_closest = f.get_closest_point_to(p_to);
- const real_t d_p_to = p_to.distance_to(p_to_closest);
- if (closest_point_d > d_p_to) {
- closest_point = p_to_closest;
- closest_point_d = d_p_to;
- }
- }
- }
- // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment.
- if (!use_collision) {
- for (size_t point_id = 0; point_id < p.points.size(); point_id += 1) {
- Vector3 a, b;
-
- Geometry3D::get_closest_points_between_segments(
- p_from,
- p_to,
- p.points[point_id].pos,
- p.points[(point_id + 1) % p.points.size()].pos,
- a,
- b);
-
- const real_t d = a.distance_to(b);
- if (d < closest_point_d) {
- closest_point_d = d;
- closest_point = b;
- }
- }
- }
- }
-
- return closest_point;
+ return NavMeshQueries3D::polygons_get_closest_point_to_segment(polygons, p_from, p_to, p_use_collision);
}
Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
@@ -675,8 +151,8 @@ Vector3 NavMap::get_closest_point(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return Vector3();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.point;
+
+ return NavMeshQueries3D::polygons_get_closest_point(polygons, p_point);
}
Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
@@ -685,8 +161,8 @@ Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return Vector3();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.normal;
+
+ return NavMeshQueries3D::polygons_get_closest_point_normal(polygons, p_point);
}
RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
@@ -695,32 +171,14 @@ RID NavMap::get_closest_point_owner(const Vector3 &p_point) const {
NAVMAP_ITERATION_ZERO_ERROR_MSG();
return RID();
}
- gd::ClosestPointQueryResult cp = get_closest_point_info(p_point);
- return cp.owner;
+
+ return NavMeshQueries3D::polygons_get_closest_point_owner(polygons, p_point);
}
gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const {
RWLockRead read_lock(map_rwlock);
- gd::ClosestPointQueryResult result;
- real_t closest_point_ds = FLT_MAX;
-
- for (const gd::Polygon &p : polygons) {
- // For each face check the distance to the point
- for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) {
- const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos);
- const Vector3 inters = f.get_closest_point_to(p_point);
- const real_t ds = inters.distance_squared_to(p_point);
- if (ds < closest_point_ds) {
- result.point = inters;
- result.normal = f.get_plane().normal;
- result.owner = p.owner->get_self();
- closest_point_ds = ds;
- }
- }
- }
-
- return result;
+ return NavMeshQueries3D::polygons_get_closest_point_info(polygons, p_point);
}
void NavMap::add_region(NavRegion *p_region) {
@@ -943,29 +401,30 @@ void NavMap::sync() {
}
// Resize the polygon count.
- int count = 0;
+ int polygon_count = 0;
for (const NavRegion *region : regions) {
if (!region->get_enabled()) {
continue;
}
- count += region->get_polygons().size();
+ polygon_count += region->get_polygons().size();
}
- polygons.resize(count);
+ polygons.resize(polygon_count);
// Copy all region polygons in the map.
- count = 0;
+ polygon_count = 0;
for (const NavRegion *region : regions) {
if (!region->get_enabled()) {
continue;
}
const LocalVector<gd::Polygon> &polygons_source = region->get_polygons();
for (uint32_t n = 0; n < polygons_source.size(); n++) {
- polygons[count + n] = polygons_source[n];
+ polygons[polygon_count] = polygons_source[n];
+ polygons[polygon_count].id = polygon_count;
+ polygon_count++;
}
- count += region->get_polygons().size();
}
- _new_pm_polygon_count = polygons.size();
+ _new_pm_polygon_count = polygon_count;
// Group all edges per key.
HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections;
@@ -1136,6 +595,7 @@ void NavMap::sync() {
// If we have both a start and end point, then create a synthetic polygon to route through.
if (closest_start_polygon && closest_end_polygon) {
gd::Polygon &new_polygon = link_polygons[link_poly_idx++];
+ new_polygon.id = polygon_count++;
new_polygon.owner = link;
new_polygon.edges.clear();
@@ -1391,39 +851,6 @@ void NavMap::dispatch_callbacks() {
}
}
-void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const {
- Vector3 from = path[path.size() - 1];
-
- if (from.is_equal_approx(p_to_point)) {
- return;
- }
- Plane cut_plane;
- cut_plane.normal = (from - p_to_point).cross(up);
- if (cut_plane.normal == Vector3()) {
- return;
- }
- cut_plane.normal.normalize();
- cut_plane.d = cut_plane.normal.dot(from);
-
- while (from_poly != p_to_poly) {
- Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start;
- Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end;
-
- ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1);
- from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id];
-
- if (!pathway_start.is_equal_approx(pathway_end)) {
- Vector3 inters;
- if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) {
- if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) {
- path.push_back(inters);
- APPEND_METADATA(from_poly->poly);
- }
- }
- }
- }
-}
-
void NavMap::_update_merge_rasterizer_cell_dimensions() {
merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale;
merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index 82e8854b7a..b9120c04d9 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -36,6 +36,7 @@
#include "core/math/math_defs.h"
#include "core/object/worker_thread_pool.h"
+#include "servers/navigation/navigation_globals.h"
#include <KdTree2d.h>
#include <KdTree3d.h>
@@ -55,21 +56,21 @@ class NavMap : public NavRid {
/// To find the polygons edges the vertices are displaced in a grid where
/// each cell has the following cell_size and cell_height.
- real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
- real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+ real_t cell_size = NavigationDefaults3D::navmesh_cell_size;
+ real_t cell_height = NavigationDefaults3D::navmesh_cell_height;
// For the inter-region merging to work, internal rasterization is performed.
- float merge_rasterizer_cell_size = 0.25;
- float merge_rasterizer_cell_height = 0.25;
+ float merge_rasterizer_cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float merge_rasterizer_cell_height = NavigationDefaults3D::navmesh_cell_height;
// This value is used to control sensitivity of internal rasterizer.
float merge_rasterizer_cell_scale = 1.0;
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.
- real_t edge_connection_margin = 0.25;
+ real_t edge_connection_margin = NavigationDefaults3D::edge_connection_margin;
/// This value is used to limit how far links search to find polygons to connect to.
- real_t link_connection_radius = 1.0;
+ real_t link_connection_radius = NavigationDefaults3D::link_connection_radius;
bool regenerate_polygons = true;
bool regenerate_links = true;
@@ -231,7 +232,6 @@ private:
void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent);
void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent);
- void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const;
void _update_rvo_simulation();
void _update_rvo_obstacles_tree_2d();
void _update_rvo_agents_tree_2d();
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 85510bd416..7a44adecbc 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -32,6 +32,8 @@
#include "nav_map.h"
+#include "3d/nav_mesh_queries_3d.h"
+
void NavRegion::set_map(NavMap *p_map) {
if (map == p_map) {
return;
@@ -108,81 +110,7 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform
return Vector3();
}
- const LocalVector<gd::Polygon> &region_polygons = get_polygons();
-
- if (region_polygons.is_empty()) {
- return Vector3();
- }
-
- if (p_uniformly) {
- real_t accumulated_area = 0;
- RBMap<real_t, uint32_t> region_area_map;
-
- for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) {
- const gd::Polygon &region_polygon = region_polygons[rp_index];
- real_t polyon_area = region_polygon.surface_area;
-
- if (polyon_area == 0.0) {
- continue;
- }
- region_area_map[accumulated_area] = rp_index;
- accumulated_area += polyon_area;
- }
- if (region_area_map.is_empty() || accumulated_area == 0) {
- // All polygons have no real surface / no area.
- return Vector3();
- }
-
- real_t region_area_map_pos = Math::random(real_t(0), accumulated_area);
-
- RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos);
- ERR_FAIL_COND_V(!region_E, Vector3());
- uint32_t rrp_polygon_index = region_E->value;
- ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3());
-
- const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
-
- real_t accumulated_polygon_area = 0;
- RBMap<real_t, uint32_t> polygon_area_map;
-
- for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) {
- real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area();
-
- if (face_area == 0.0) {
- continue;
- }
- polygon_area_map[accumulated_polygon_area] = rpp_index;
- accumulated_polygon_area += face_area;
- }
- if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) {
- // All faces have no real surface / no area.
- return Vector3();
- }
-
- real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area);
-
- RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos);
- ERR_FAIL_COND_V(!polygon_E, Vector3());
- uint32_t rrp_face_index = polygon_E->value;
- ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3());
-
- const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
-
- Vector3 face_random_position = face.get_random_point_inside();
- return face_random_position;
-
- } else {
- uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1);
-
- const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index];
-
- uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1);
-
- const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos);
-
- Vector3 face_random_position = face.get_random_point_inside();
- return face_random_position;
- }
+ return NavMeshQueries3D::polygons_get_random_point(get_polygons(), p_navigation_layers, p_uniformly);
}
bool NavRegion::sync() {
diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h
index c3939e9979..ba4c44b748 100644
--- a/modules/navigation/nav_utils.h
+++ b/modules/navigation/nav_utils.h
@@ -98,6 +98,9 @@ struct Edge {
};
struct Polygon {
+ /// Id of the polygon in the map.
+ uint32_t id = UINT32_MAX;
+
/// Navigation region or link that contains this polygon.
const NavBase *owner = nullptr;
@@ -111,9 +114,11 @@ struct Polygon {
};
struct NavigationPoly {
- uint32_t self_id = 0;
/// This poly.
- const Polygon *poly;
+ const Polygon *poly = nullptr;
+
+ /// Index in the heap of traversable polygons.
+ uint32_t traversable_poly_index = UINT32_MAX;
/// Those 4 variables are used to travel the path backwards.
int back_navigation_poly_id = -1;
@@ -123,20 +128,44 @@ struct NavigationPoly {
/// The entry position of this poly.
Vector3 entry;
- /// The distance to the destination.
+ /// The distance traveled until now (g cost).
real_t traveled_distance = 0.0;
+ /// The distance to the destination (h cost).
+ real_t distance_to_destination = 0.0;
- NavigationPoly() { poly = nullptr; }
+ /// The total travel cost (f cost).
+ real_t total_travel_cost() const {
+ return traveled_distance + distance_to_destination;
+ }
- NavigationPoly(const Polygon *p_poly) :
- poly(p_poly) {}
+ bool operator==(const NavigationPoly &p_other) const {
+ return poly == p_other.poly;
+ }
- bool operator==(const NavigationPoly &other) const {
- return poly == other.poly;
+ bool operator!=(const NavigationPoly &p_other) const {
+ return !(*this == p_other);
}
+};
+
+struct NavPolyTravelCostGreaterThan {
+ // Returns `true` if the travel cost of `a` is higher than that of `b`.
+ bool operator()(const NavigationPoly *p_poly_a, const NavigationPoly *p_poly_b) const {
+ real_t f_cost_a = p_poly_a->total_travel_cost();
+ real_t h_cost_a = p_poly_a->distance_to_destination;
+ real_t f_cost_b = p_poly_b->total_travel_cost();
+ real_t h_cost_b = p_poly_b->distance_to_destination;
- bool operator!=(const NavigationPoly &other) const {
- return !operator==(other);
+ if (f_cost_a != f_cost_b) {
+ return f_cost_a > f_cost_b;
+ } else {
+ return h_cost_a > h_cost_b;
+ }
+ }
+};
+
+struct NavPolyHeapIndexer {
+ void operator()(NavigationPoly *p_poly, uint32_t p_heap_index) const {
+ p_poly->traversable_poly_index = p_heap_index;
}
};
@@ -146,6 +175,129 @@ struct ClosestPointQueryResult {
RID owner;
};
+template <typename T>
+struct NoopIndexer {
+ void operator()(const T &p_value, uint32_t p_index) {}
+};
+
+/**
+ * A max-heap implementation that notifies of element index changes.
+ */
+template <typename T, typename LessThan = Comparator<T>, typename Indexer = NoopIndexer<T>>
+class Heap {
+ LocalVector<T> _buffer;
+
+ LessThan _less_than;
+ Indexer _indexer;
+
+public:
+ void reserve(uint32_t p_size) {
+ _buffer.reserve(p_size);
+ }
+
+ uint32_t size() const {
+ return _buffer.size();
+ }
+
+ bool is_empty() const {
+ return _buffer.is_empty();
+ }
+
+ void push(const T &p_element) {
+ _buffer.push_back(p_element);
+ _indexer(p_element, _buffer.size() - 1);
+ _shift_up(_buffer.size() - 1);
+ }
+
+ T pop() {
+ ERR_FAIL_COND_V_MSG(_buffer.is_empty(), T(), "Can't pop an empty heap.");
+ T value = _buffer[0];
+ _indexer(value, UINT32_MAX);
+ if (_buffer.size() > 1) {
+ _buffer[0] = _buffer[_buffer.size() - 1];
+ _indexer(_buffer[0], 0);
+ _buffer.remove_at(_buffer.size() - 1);
+ _shift_down(0);
+ } else {
+ _buffer.remove_at(_buffer.size() - 1);
+ }
+ return value;
+ }
+
+ /**
+ * Update the position of the element in the heap if necessary.
+ */
+ void shift(uint32_t p_index) {
+ ERR_FAIL_UNSIGNED_INDEX_MSG(p_index, _buffer.size(), "Heap element index is out of range.");
+ if (!_shift_up(p_index)) {
+ _shift_down(p_index);
+ }
+ }
+
+ void clear() {
+ for (const T &value : _buffer) {
+ _indexer(value, UINT32_MAX);
+ }
+ _buffer.clear();
+ }
+
+ Heap() {}
+
+ Heap(const LessThan &p_less_than) :
+ _less_than(p_less_than) {}
+
+ Heap(const Indexer &p_indexer) :
+ _indexer(p_indexer) {}
+
+ Heap(const LessThan &p_less_than, const Indexer &p_indexer) :
+ _less_than(p_less_than), _indexer(p_indexer) {}
+
+private:
+ bool _shift_up(uint32_t p_index) {
+ T value = _buffer[p_index];
+ uint32_t current_index = p_index;
+ uint32_t parent_index = (current_index - 1) / 2;
+ while (current_index > 0 && _less_than(_buffer[parent_index], value)) {
+ _buffer[current_index] = _buffer[parent_index];
+ _indexer(_buffer[current_index], current_index);
+ current_index = parent_index;
+ parent_index = (current_index - 1) / 2;
+ }
+ if (current_index != p_index) {
+ _buffer[current_index] = value;
+ _indexer(value, current_index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool _shift_down(uint32_t p_index) {
+ T value = _buffer[p_index];
+ uint32_t current_index = p_index;
+ uint32_t child_index = 2 * current_index + 1;
+ while (child_index < _buffer.size()) {
+ if (child_index + 1 < _buffer.size() &&
+ _less_than(_buffer[child_index], _buffer[child_index + 1])) {
+ child_index++;
+ }
+ if (_less_than(_buffer[child_index], value)) {
+ break;
+ }
+ _buffer[current_index] = _buffer[child_index];
+ _indexer(_buffer[current_index], current_index);
+ current_index = child_index;
+ child_index = 2 * current_index + 1;
+ }
+ if (current_index != p_index) {
+ _buffer[current_index] = value;
+ _indexer(value, current_index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+};
} // namespace gd
#endif // NAV_UTILS_H
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 0960b2ad36..b55b1141e1 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -194,6 +194,9 @@ Ref<Image> NoiseTexture2D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi
void NoiseTexture2D::_update_texture() {
bool use_thread = true;
+#ifndef THREADS_ENABLED
+ use_thread = false;
+#endif
if (first_time) {
use_thread = false;
first_time = false;
diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp
index 9047491344..e3cca8a09f 100644
--- a/modules/noise/noise_texture_3d.cpp
+++ b/modules/noise/noise_texture_3d.cpp
@@ -187,6 +187,9 @@ Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi
void NoiseTexture3D::_update_texture() {
bool use_thread = true;
+#ifndef THREADS_ENABLED
+ use_thread = false;
+#endif
if (first_time) {
use_thread = false;
first_time = false;
diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h
index 938e8fd6ab..0d18d66e74 100644
--- a/modules/noise/tests/test_noise_texture_2d.h
+++ b/modules/noise/tests/test_noise_texture_2d.h
@@ -135,7 +135,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_noise(noise);
CHECK(noise_texture->get_noise() == noise);
noise_texture->set_noise(nullptr);
- CHECK(noise_texture->get_noise() == nullptr);
+ CHECK(noise_texture->get_noise().is_null());
noise_texture->set_width(8);
noise_texture->set_height(4);
@@ -190,7 +190,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_color_ramp(gradient);
CHECK(noise_texture->get_color_ramp() == gradient);
noise_texture->set_color_ramp(nullptr);
- CHECK(noise_texture->get_color_ramp() == nullptr);
+ CHECK(noise_texture->get_color_ramp().is_null());
}
TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") {
diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h
index b708eac43b..434cd20a08 100644
--- a/modules/noise/tests/test_noise_texture_3d.h
+++ b/modules/noise/tests/test_noise_texture_3d.h
@@ -133,7 +133,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_noise(noise);
CHECK(noise_texture->get_noise() == noise);
noise_texture->set_noise(nullptr);
- CHECK(noise_texture->get_noise() == nullptr);
+ CHECK(noise_texture->get_noise().is_null());
noise_texture->set_width(8);
noise_texture->set_height(4);
@@ -174,7 +174,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") {
noise_texture->set_color_ramp(gradient);
CHECK(noise_texture->get_color_ramp() == gradient);
noise_texture->set_color_ramp(nullptr);
- CHECK(noise_texture->get_color_ramp() == nullptr);
+ CHECK(noise_texture->get_color_ramp().is_null());
}
TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") {
diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h
index af58e2487b..7e8e456341 100644
--- a/modules/regex/tests/test_regex.h
+++ b/modules/regex/tests/test_regex.h
@@ -80,32 +80,32 @@ TEST_CASE("[RegEx] Searching") {
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "ea");
match = re.search(s, 1, 2);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "e");
match = re.search(s, 2, 4);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "a");
match = re.search(s, 3, 5);
- CHECK(match == nullptr);
+ CHECK(match.is_null());
match = re.search(s, 6, 2);
- CHECK(match == nullptr);
+ CHECK(match.is_null());
const Array all_results = re.search_all(s);
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "ea");
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == "i");
CHECK(re.compile(numerics) == OK);
CHECK(re.is_valid());
- CHECK(re.search(s) == nullptr);
+ CHECK(re.search(s).is_null());
CHECK(re.search_all(s).size() == 0);
}
@@ -168,7 +168,7 @@ TEST_CASE("[RegEx] Uninitialized use") {
RegEx re;
ERR_PRINT_OFF;
- CHECK(re.search(s) == nullptr);
+ CHECK(re.search(s).is_null());
CHECK(re.search_all(s).size() == 0);
CHECK(re.sub(s, "") == "");
CHECK(re.get_group_count() == 0);
@@ -237,10 +237,10 @@ TEST_CASE("[RegEx] Invalid end position") {
const Array all_results = re.search_all(s, 0, 10);
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String("o"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String("o"));
CHECK(re.sub(s, "", true, 0, 10) == "Gdt");
@@ -251,7 +251,7 @@ TEST_CASE("[RegEx] Get match string list") {
RegEx re("(Go)(dot)");
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
PackedStringArray result;
result.append("Godot");
result.append("Go");
@@ -265,14 +265,14 @@ TEST_CASE("[RegEx] Match start and end positions") {
RegEx re1("pattern");
REQUIRE(re1.is_valid());
Ref<RegExMatch> match = re1.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 6);
CHECK(match->get_end(0) == 13);
RegEx re2("(?<vowel>[aeiou])");
REQUIRE(re2.is_valid());
match = re2.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start("vowel") == 2);
CHECK(match->get_end("vowel") == 3);
}
@@ -307,7 +307,7 @@ TEST_CASE("[RegEx] Simple lookahead") {
RegEx re("o(?=t)");
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
}
@@ -325,12 +325,12 @@ TEST_CASE("[RegEx] Lookahead groups empty matches") {
CHECK(all_results.size() == 2);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String(""));
CHECK(match->get_string(1) == String("12"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_string(0) == String(""));
CHECK(match->get_string(1) == String("2"));
}
@@ -341,7 +341,7 @@ TEST_CASE("[RegEx] Simple lookbehind") {
RegEx re("(?<=d)o");
REQUIRE(re.is_valid());
Ref<RegExMatch> match = re.search(s);
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
}
@@ -355,22 +355,22 @@ TEST_CASE("[RegEx] Simple lookbehind search all") {
CHECK(all_results.size() == 4);
Ref<RegExMatch> match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 1);
CHECK(match->get_end(0) == 2);
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 3);
CHECK(match->get_end(0) == 4);
match = all_results[2];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 7);
CHECK(match->get_end(0) == 8);
match = all_results[3];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 9);
CHECK(match->get_end(0) == 10);
}
@@ -386,7 +386,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(all_results.size() == 3);
match = all_results[0];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 2);
CHECK(match->get_end(0) == 2);
CHECK(match->get_start(1) == 1);
@@ -395,7 +395,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(match->get_string(1) == String("b"));
match = all_results[1];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 6);
CHECK(match->get_end(0) == 6);
CHECK(match->get_start(1) == 5);
@@ -404,7 +404,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") {
CHECK(match->get_string(1) == String("b"));
match = all_results[2];
- REQUIRE(match != nullptr);
+ REQUIRE(match.is_valid());
CHECK(match->get_start(0) == 8);
CHECK(match->get_end(0) == 8);
CHECK(match->get_start(1) == 7);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index b1dc53018a..3322300dda 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -136,11 +136,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_h_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return bm_font->face->glyph_map[p_glyph].advance.x * 64;
+ return E->value.advance.x * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) {
@@ -150,11 +151,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return -bm_font->face->glyph_map[p_glyph].advance.y * 64;
+ return -E->value.advance.y * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) {
@@ -178,11 +180,12 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
- *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32;
+ *r_x = E->value.advance.x * 32;
*r_y = -bm_font->face->ascent * 64;
return true;
@@ -195,14 +198,15 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_extents(hb_font_t *p_font, void *p_
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
r_extents->x_bearing = 0;
r_extents->y_bearing = 0;
- r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64;
- r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64;
+ r_extents->width = E->value.rect.size.x * 64;
+ r_extents->height = E->value.rect.size.y * 64;
return true;
}
@@ -1188,18 +1192,21 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeAdvanced *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeAdvanced *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -1235,7 +1242,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -1339,17 +1347,22 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
@@ -1840,7 +1853,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
// Init bitmap font.
fd->hb_handle = _bmp_font_create(fd, nullptr);
}
- p_font_data->cache[p_size] = fd;
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1864,9 +1878,10 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr);
- return fd->cache[size]->hb_handle;
+ return ffsd->hb_handle;
}
RID TextServerAdvanced::_create_font() {
@@ -1989,7 +2004,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1999,7 +2015,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -2009,7 +2026,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -2019,7 +2037,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -2029,7 +2048,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -2039,7 +2059,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -2049,7 +2070,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -2059,7 +2081,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -2069,7 +2092,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -2079,7 +2103,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -2089,9 +2114,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
- hb_face_t *hb_face = hb_font_get_face(fd->cache[size]->hb_handle);
+ hb_face_t *hb_face = hb_font_get_face(ffsd->hb_handle);
unsigned int num_entries = 0;
const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries);
@@ -2599,8 +2625,9 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -2610,18 +2637,19 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -2631,8 +2659,9 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -2642,18 +2671,19 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -2664,8 +2694,9 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -2675,18 +2706,19 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -2697,8 +2729,9 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -2708,18 +2741,19 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -2730,13 +2764,15 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -2746,18 +2782,19 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -2768,9 +2805,10 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -2779,8 +2817,9 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -2789,10 +2828,11 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
@@ -2802,13 +2842,14 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -2829,10 +2870,11 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -2843,13 +2885,14 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -2862,10 +2905,11 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -2887,10 +2931,11 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -2903,9 +2948,10 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -2914,9 +2960,10 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
@@ -2940,22 +2987,22 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -2963,17 +3010,17 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -2984,12 +3031,13 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2999,32 +3047,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -3035,12 +3083,13 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3050,32 +3099,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -3086,12 +3135,13 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3101,22 +3151,23 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
@@ -3126,12 +3177,13 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3141,22 +3193,23 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
@@ -3166,12 +3219,13 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3181,27 +3235,28 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3214,7 +3269,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -3228,27 +3283,28 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3261,7 +3317,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -3275,7 +3331,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -3283,20 +3340,20 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3306,13 +3363,13 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -3331,7 +3388,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
@@ -3347,8 +3405,9 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -3358,8 +3417,9 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -3369,8 +3429,9 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -3380,9 +3441,10 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -3398,9 +3460,9 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
+ FT_Get_Kerning(ffsd->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3426,14 +3488,15 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
if (p_variation_selector) {
- return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector);
+ return FT_Face_GetCharVariantIndex(ffsd->face, p_char, p_variation_selector);
} else {
- return FT_Get_Char_Index(fd->cache[size]->face, p_char);
+ return FT_Get_Char_Index(ffsd->face, p_char);
}
} else {
return (int64_t)p_char;
@@ -3449,23 +3512,24 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->inv_glyph_map.is_empty()) {
- FT_Face face = fd->cache[size]->face;
+ if (ffsd->inv_glyph_map.is_empty()) {
+ FT_Face face = ffsd->face;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
- fd->cache[size]->inv_glyph_map[gindex] = charcode;
+ ffsd->inv_glyph_map[gindex] = charcode;
}
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
}
- if (fd->cache[size]->inv_glyph_map.has(p_glyph_index)) {
- return fd->cache[size]->inv_glyph_map[p_glyph_index];
+ if (ffsd->inv_glyph_map.has(p_glyph_index)) {
+ return ffsd->inv_glyph_map[p_glyph_index];
} else {
return 0;
}
@@ -3482,17 +3546,19 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -3500,30 +3566,30 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -3533,10 +3599,12 @@ PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -3567,25 +3635,27 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
- int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i);
- if (fd->cache[size]->face) {
+ int32_t idx = FT_Get_Char_Index(ffsd->face, i);
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3600,24 +3670,26 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3634,16 +3706,17 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3660,24 +3733,24 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3690,12 +3763,12 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
double scale = _font_get_scale(p_font_rid, p_size);
Point2 cpos = p_pos;
@@ -3708,8 +3781,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3723,9 +3796,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3742,16 +3815,17 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3768,24 +3842,24 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && fd->cache[size]->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3798,12 +3872,12 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -3816,8 +3890,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3831,9 +3905,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3898,7 +3972,8 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
return fd->script_support_overrides[p_script];
} else {
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), false);
return fd->supported_scripts.has(hb_tag_from_string(p_script.ascii().get_data(), -1));
}
}
@@ -3945,7 +4020,8 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -3963,7 +4039,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_features;
}
@@ -3973,7 +4050,8 @@ Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -4048,7 +4126,7 @@ int64_t TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd,
}
void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -4404,7 +4482,7 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -4548,7 +4626,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -4576,7 +4654,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
}
bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const {
- if (p_new_sd->valid) {
+ if (p_new_sd->valid.is_set()) {
return true;
}
@@ -4725,7 +4803,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
_realign(p_new_sd);
}
- p_new_sd->valid = true;
+ p_new_sd->valid.set();
return true;
}
@@ -4743,7 +4821,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -4900,7 +4978,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -5117,7 +5195,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -5144,7 +5222,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5355,7 +5433,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans;
if (p_sd->parent != RID()) {
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5403,7 +5481,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -5417,7 +5495,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -5688,7 +5766,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -6068,7 +6146,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -6128,7 +6206,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
gl.index = glyph_info[i].codepoint;
if (gl.index != 0) {
- _ensure_glyph(fd, fss, gl.index | mod);
+ FontGlyph fgl;
+ _ensure_glyph(fd, fss, gl.index | mod, fgl);
if (subpos) {
gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale);
} else if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
@@ -6240,7 +6319,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -6248,13 +6327,13 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
if (sd->parent != RID()) {
_shaped_text_shape(sd->parent);
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND_V(!parent_sd->valid, false);
+ ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false);
ERR_FAIL_COND_V(!_shape_substr(sd, parent_sd, sd->start, sd->end - sd->start), false);
return true;
}
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -6447,16 +6526,16 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
}
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ // Atomic read is safe and faster.
+ return sd->valid.is_set();
}
const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -6464,7 +6543,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -6475,7 +6554,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -6486,7 +6565,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6526,7 +6605,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -6547,7 +6626,7 @@ int64_t TextServerAdvanced::_shaped_text_get_object_glyph(const RID &p_shaped, c
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), -1);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
const ShapedTextDataAdvanced::EmbeddedObject &obj = sd->objects[p_key];
@@ -6566,7 +6645,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -6581,7 +6660,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -6592,7 +6671,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -6603,7 +6682,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width);
@@ -6614,7 +6693,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6626,7 +6705,7 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -7424,10 +7503,15 @@ bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const {
return u_isalpha(p_unicode);
}
+void TextServerAdvanced::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerAdvanced::TextServerAdvanced() {
_insert_num_systems_lang();
_insert_feature_sets();
_bmp_create_font_funcs();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings));
}
void TextServerAdvanced::_cleanup() {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index fdebb8e4cd..448be9ebe4 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -74,6 +74,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -85,6 +86,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -151,6 +153,9 @@ class TextServerAdvanced : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, FeatureInfo> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_num_systems_lang();
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false);
@@ -327,7 +332,7 @@ class TextServerAdvanced : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeAdvanced *> cache;
bool face_init = false;
HashSet<uint32_t> supported_scripts;
@@ -359,8 +364,8 @@ class TextServerAdvanced : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -487,7 +492,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
int base_para_direction = UBIDI_DEFAULT_LTR;
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index a7ddfc719e..540ba19cac 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -624,18 +624,21 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeFallback *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeFallback *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -673,7 +676,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -777,20 +781,26 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
+ r_cache_for_size = nullptr;
FontForSizeFallback *fd = memnew(FontForSizeFallback);
fd->size = p_size;
if (p_font_data->data_ptr && (p_font_data->data_size > 0)) {
@@ -973,7 +983,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
}
- p_font_data->cache[p_size] = fd;
+
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1041,7 +1053,8 @@ void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1119,7 +1132,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -1129,7 +1143,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -1139,7 +1154,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -1149,7 +1165,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -1159,7 +1176,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -1169,7 +1187,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -1179,7 +1198,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -1189,7 +1209,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -1199,7 +1220,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -1607,8 +1629,9 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -1618,18 +1641,19 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -1639,8 +1663,9 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -1650,18 +1675,19 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -1672,8 +1698,9 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -1683,18 +1710,19 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -1705,8 +1733,9 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -1716,18 +1745,19 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -1738,13 +1768,14 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -1754,18 +1785,19 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -1776,9 +1808,10 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -1787,8 +1820,9 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -1797,10 +1831,11 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
@@ -1810,13 +1845,14 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -1837,10 +1873,11 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -1851,13 +1888,14 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -1870,10 +1908,11 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -1895,10 +1934,11 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -1911,9 +1951,10 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -1922,9 +1963,10 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
@@ -1934,22 +1976,22 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -1957,17 +1999,17 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -1978,12 +2020,13 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -1993,32 +2036,32 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -2029,12 +2072,13 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2044,32 +2088,32 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -2080,12 +2124,13 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2095,22 +2140,23 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
@@ -2120,12 +2166,13 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2135,22 +2182,23 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
@@ -2160,12 +2208,13 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2175,27 +2224,28 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2208,7 +2258,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -2222,27 +2272,28 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2255,7 +2306,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -2269,7 +2320,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -2277,20 +2329,20 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, FT_Get_Char_Index(ffsd->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2300,13 +2352,13 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -2325,10 +2377,11 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
- for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
+ for (const KeyValue<Vector2i, Vector2> &E : ffsd->kerning_map) {
ret.push_back(E.key);
}
return ret;
@@ -2341,8 +2394,9 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -2352,8 +2406,9 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -2363,8 +2418,9 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -2374,9 +2430,10 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -2392,11 +2449,11 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x);
- int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y);
- FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
+ int32_t glyph_a = FT_Get_Char_Index(ffsd->face, p_glyph_pair.x);
+ int32_t glyph_b = FT_Get_Char_Index(ffsd->face, p_glyph_pair.y);
+ FT_Get_Kerning(ffsd->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2431,17 +2488,19 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -2449,30 +2508,30 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -2482,10 +2541,12 @@ PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -2516,25 +2577,27 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = i;
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2549,24 +2612,26 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2583,16 +2648,17 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2609,24 +2675,24 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2639,12 +2705,12 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2657,8 +2723,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2672,9 +2738,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2691,16 +2757,17 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2717,24 +2784,24 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2747,12 +2814,12 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2765,8 +2832,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2780,9 +2847,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2872,7 +2939,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->script_support_overrides.erase(p_script);
}
@@ -2894,7 +2962,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -2916,7 +2985,8 @@ Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -2953,7 +3023,7 @@ void TextServerFallback::_font_set_global_oversampling(double p_oversampling) {
/*************************************************************************/
void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -3192,7 +3262,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64
span.font_size = p_size;
span.features = p_opentype_features;
- sd->valid = false;
+ sd->valid.clear();
}
bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
@@ -3288,7 +3358,7 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -3431,7 +3501,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -3514,7 +3584,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
_realign(new_sd);
}
- new_sd->valid = true;
+ new_sd->valid.set();
return shaped_owner.make_rid(new_sd);
}
@@ -3532,7 +3602,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -3641,7 +3711,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3697,7 +3767,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -3761,7 +3831,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3940,7 +4010,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -3967,7 +4037,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -4161,7 +4231,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -4178,7 +4248,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
sd->glyphs.clear();
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -4307,16 +4377,15 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
// Align embedded objects to baseline.
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ return sd->valid.is_set();
}
const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -4324,7 +4393,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -4335,7 +4404,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -4346,7 +4415,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4380,7 +4449,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -4417,7 +4486,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -4432,7 +4501,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -4443,7 +4512,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -4454,7 +4523,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->width);
@@ -4465,7 +4534,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4477,7 +4546,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4489,7 +4558,7 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4626,8 +4695,13 @@ PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_str
return ret;
}
+void TextServerFallback::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerFallback::TextServerFallback() {
_insert_feature_sets();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings));
};
void TextServerFallback::_cleanup() {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 1b76c6fa0f..ee1f72401f 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -72,6 +72,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -83,6 +84,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, StringName> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag);
@@ -278,7 +283,7 @@ class TextServerFallback : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeFallback *> cache;
bool face_init = false;
Dictionary supported_varaitions;
@@ -308,8 +313,8 @@ class TextServerFallback : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -432,7 +437,7 @@ class TextServerFallback : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp
index 70f0ea346b..6bdb261b50 100644
--- a/modules/upnp/upnp.cpp
+++ b/modules/upnp/upnp.cpp
@@ -229,14 +229,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const {
}
void UPNP::add_device(Ref<UPNPDevice> device) {
- ERR_FAIL_NULL(device);
+ ERR_FAIL_COND(device.is_null());
devices.push_back(device);
}
void UPNP::set_device(int index, Ref<UPNPDevice> device) {
ERR_FAIL_INDEX(index, devices.size());
- ERR_FAIL_NULL(device);
+ ERR_FAIL_COND(device.is_null());
devices.set(index, device);
}
@@ -257,7 +257,7 @@ Ref<UPNPDevice> UPNP::get_gateway() const {
for (int i = 0; i < devices.size(); i++) {
Ref<UPNPDevice> dev = get_device(i);
- if (dev != nullptr && dev->is_valid_gateway()) {
+ if (dev.is_valid() && dev->is_valid_gateway()) {
return dev;
}
}
@@ -292,7 +292,7 @@ bool UPNP::is_discover_ipv6() const {
String UPNP::query_external_address() const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return "";
}
@@ -302,7 +302,7 @@ String UPNP::query_external_address() const {
int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return UPNP_RESULT_NO_GATEWAY;
}
@@ -312,7 +312,7 @@ int UPNP::add_port_mapping(int port, int port_internal, String desc, String prot
int UPNP::delete_port_mapping(int port, String proto) const {
Ref<UPNPDevice> dev = get_gateway();
- if (dev == nullptr) {
+ if (dev.is_null()) {
return UPNP_RESULT_NO_GATEWAY;
}
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index e96d9da7a9..4e182f9787 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -48,7 +48,7 @@ Error ZIPPacker::close() {
Error err = zipClose(zf, nullptr) == ZIP_OK ? OK : FAILED;
if (err == OK) {
- DEV_ASSERT(fa == nullptr);
+ DEV_ASSERT(fa.is_null());
zf = nullptr;
}
diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp
index 123d1e5d46..76f48edb69 100644
--- a/modules/zip/zip_reader.cpp
+++ b/modules/zip/zip_reader.cpp
@@ -48,7 +48,7 @@ Error ZIPReader::close() {
Error err = unzClose(uzf) == UNZ_OK ? OK : FAILED;
if (err == OK) {
- DEV_ASSERT(fa == nullptr);
+ DEV_ASSERT(fa.is_null());
uzf = nullptr;
}
diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp
index 60c369951c..6920f801e5 100644
--- a/platform/android/api/api.cpp
+++ b/platform/android/api/api.cpp
@@ -49,6 +49,7 @@ void register_android_api() {
#endif
GDREGISTER_CLASS(JavaClass);
+ GDREGISTER_CLASS(JavaObject);
GDREGISTER_CLASS(JavaClassWrapper);
Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton()));
}
@@ -59,6 +60,16 @@ void unregister_android_api() {
#endif
}
+void JavaClass::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_java_class_name"), &JavaClass::get_java_class_name);
+ ClassDB::bind_method(D_METHOD("get_java_method_list"), &JavaClass::get_java_method_list);
+ ClassDB::bind_method(D_METHOD("get_java_parent_class"), &JavaClass::get_java_parent_class);
+}
+
+void JavaObject::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_java_class"), &JavaObject::get_java_class);
+}
+
void JavaClassWrapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap);
}
@@ -69,13 +80,32 @@ Variant JavaClass::callp(const StringName &, const Variant **, int, Callable::Ca
return Variant();
}
+String JavaClass::get_java_class_name() const {
+ return "";
+}
+
+TypedArray<Dictionary> JavaClass::get_java_method_list() const {
+ return TypedArray<Dictionary>();
+}
+
+Ref<JavaClass> JavaClass::get_java_parent_class() const {
+ return Ref<JavaClass>();
+}
+
JavaClass::JavaClass() {
}
+JavaClass::~JavaClass() {
+}
+
Variant JavaObject::callp(const StringName &, const Variant **, int, Callable::CallError &) {
return Variant();
}
+Ref<JavaClass> JavaObject::get_java_class() const {
+ return Ref<JavaClass>();
+}
+
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
Ref<JavaClass> JavaClassWrapper::wrap(const String &) {
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h
index e21a331ab9..52df1644be 100644
--- a/platform/android/api/java_class_wrapper.h
+++ b/platform/android/api/java_class_wrapper.h
@@ -32,6 +32,7 @@
#define JAVA_CLASS_WRAPPER_H
#include "core/object/ref_counted.h"
+#include "core/variant/typed_array.h"
#ifdef ANDROID_ENABLED
#include <android/log.h>
@@ -67,6 +68,7 @@ class JavaClass : public RefCounted {
struct MethodInfo {
bool _static = false;
+ bool _constructor = false;
Vector<uint32_t> param_types;
Vector<StringName> param_sigs;
uint32_t return_type = 0;
@@ -174,14 +176,29 @@ class JavaClass : public RefCounted {
bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret);
friend class JavaClassWrapper;
+ friend class JavaObject;
+ String java_class_name;
+ String java_constructor_name;
HashMap<StringName, List<MethodInfo>> methods;
jclass _class;
#endif
+protected:
+ static void _bind_methods();
+
public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ String get_java_class_name() const;
+ TypedArray<Dictionary> get_java_method_list() const;
+ Ref<JavaClass> get_java_parent_class() const;
+
+#ifdef ANDROID_ENABLED
+ virtual String to_string() override;
+#endif
+
JavaClass();
+ ~JavaClass();
};
class JavaObject : public RefCounted {
@@ -191,14 +208,24 @@ class JavaObject : public RefCounted {
Ref<JavaClass> base_class;
friend class JavaClass;
- jobject instance;
+ jobject instance = nullptr;
#endif
+protected:
+ static void _bind_methods();
+
public:
virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ Ref<JavaClass> get_java_class() const;
+
#ifdef ANDROID_ENABLED
- JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance);
+ virtual String to_string() override;
+
+ jobject get_instance() { return instance; }
+
+ JavaObject();
+ JavaObject(const Ref<JavaClass> &p_base, jobject p_instance);
~JavaObject();
#endif
};
@@ -209,13 +236,17 @@ class JavaClassWrapper : public Object {
#ifdef ANDROID_ENABLED
RBMap<String, Ref<JavaClass>> class_cache;
friend class JavaClass;
- jmethodID getDeclaredMethods;
- jmethodID getFields;
- jmethodID getParameterTypes;
- jmethodID getReturnType;
- jmethodID getModifiers;
- jmethodID getName;
+ jmethodID Class_getDeclaredConstructors;
+ jmethodID Class_getDeclaredMethods;
+ jmethodID Class_getFields;
jmethodID Class_getName;
+ jmethodID Class_getSuperclass;
+ jmethodID Constructor_getParameterTypes;
+ jmethodID Constructor_getModifiers;
+ jmethodID Method_getParameterTypes;
+ jmethodID Method_getReturnType;
+ jmethodID Method_getModifiers;
+ jmethodID Method_getName;
jmethodID Field_getName;
jmethodID Field_getModifiers;
jmethodID Field_get;
@@ -242,6 +273,8 @@ public:
Ref<JavaClass> wrap(const String &p_class);
#ifdef ANDROID_ENABLED
+ Ref<JavaClass> wrap_jclass(jclass p_class);
+
JavaClassWrapper(jobject p_activity = nullptr);
#else
JavaClassWrapper();
diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h
index 087fd1bace..06afc4eb78 100644
--- a/platform/android/api/jni_singleton.h
+++ b/platform/android/api/jni_singleton.h
@@ -180,6 +180,11 @@ public:
env->DeleteLocalRef(obj);
} break;
+ case Variant::OBJECT: {
+ jobject obj = env->CallObjectMethodA(instance, E->get().method, v);
+ ret = _jobject_to_variant(env, obj);
+ env->DeleteLocalRef(obj);
+ } break;
default: {
env->PopLocalFrame(nullptr);
ERR_FAIL_V(Variant());
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 8dc0e869d0..5bb520bd73 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win
}
void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) {
- // Not supported on Android.
+ OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
}
DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const {
- return WINDOW_MODE_FULLSCREEN;
+ if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else {
+ return WINDOW_MODE_MAXIMIZED;
+ }
}
bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const {
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 0fdaca4839..f8ac591a78 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const {
return devices[p_index].architecture;
}
-Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
String can_export_error;
@@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
}
const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug");
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug;
if (use_reverse) {
- p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
+ p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
}
String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
@@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
OS::get_singleton()->execute(adb, args, &output, &rv, true);
print_verbose(output);
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
int dbg_port = EDITOR_GET("network/debug/remote_port");
args.clear();
args.push_back("-s");
@@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
print_line("Reverse result: " + itos(rv));
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int fs_port = EDITOR_GET("filesystem/file_server/port");
args.clear();
@@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor
return err;
}
-void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) {
String cmdline = p_preset->get("command_line/extra_args");
Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
for (int i = 0; i < command_line_strings.size(); i++) {
@@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
}
}
- gen_export_flags(command_line_strings, p_flags);
+ command_line_strings.append_array(gen_export_flags(p_flags));
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
@@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
bool immersive = p_preset->get("screen/immersive_mode");
if (immersive) {
- command_line_strings.push_back("--use_immersive");
+ command_line_strings.push_back("--fullscreen");
}
bool debug_opengl = p_preset->get("graphics/opengl_debug");
@@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor
return have_plugins_changed || has_build_dir_changed || first_build;
}
-Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
int export_format = int(p_preset->get("gradle_build/export_format"));
bool should_sign = p_preset->get("package/signed");
return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags);
}
-Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) {
+Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build"));
String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : "";
- bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG);
bool apk_expansion = p_preset->get("apk_expansion/enable");
Vector<ABI> enabled_abis = get_enabled_abis(p_preset);
@@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
user_data.assets_directory = assets_directory;
user_data.libs_directory = gradle_build_directory.path_join("libs");
user_data.debug = p_debug;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so);
} else {
err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
@@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
err = OK;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 97bbd0c7bc..708288fbf4 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -214,7 +214,7 @@ public:
virtual String get_device_architecture(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
@@ -242,7 +242,7 @@ public:
Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags);
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags);
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep);
@@ -253,9 +253,9 @@ public:
static String join_list(const List<String> &p_parts, const String &p_separator);
static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch);
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
- Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags);
+ Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void get_platform_features(List<String> *r_features) const override;
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index ae336d6f9d..59b669eabb 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -113,87 +113,6 @@ bool FileAccessAndroid::eof_reached() const {
return eof;
}
-uint8_t FileAccessAndroid::get_8() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint8_t byte;
- AAsset_read(asset, &byte, 1);
- pos++;
- return byte;
-}
-
-uint16_t FileAccessAndroid::get_16() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint16_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 2);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
-
- return bytes;
-}
-
-uint32_t FileAccessAndroid::get_32() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint32_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 4);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
-
- return bytes;
-}
-
-uint64_t FileAccessAndroid::get_64() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint64_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 8);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
-
- return bytes;
-}
-
uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -209,6 +128,7 @@ uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const
pos = len;
}
}
+
return r;
}
@@ -220,19 +140,7 @@ void FileAccessAndroid::flush() {
ERR_FAIL();
}
-void FileAccessAndroid::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_16(uint16_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_32(uint32_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_64(uint64_t p_dest) {
+void FileAccessAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index b465a92c78..3224ab50b9 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -68,19 +68,12 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
- virtual uint8_t get_8() const override; // get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; // get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; // store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; // return true if a file exists
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 9ae48dfb10..8b52a00ed8 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -169,43 +169,6 @@ void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
}
}
-uint8_t FileAccessFilesystemJAndroid::get_8() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint8_t byte;
- get_buffer(&byte, 1);
- return byte;
-}
-
-uint16_t FileAccessFilesystemJAndroid::get_16() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint16_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2);
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
- return bytes;
-}
-
-uint32_t FileAccessFilesystemJAndroid::get_32() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint32_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4);
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
- return bytes;
-}
-
-uint64_t FileAccessFilesystemJAndroid::get_64() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint64_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8);
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
- return bytes;
-}
-
String FileAccessFilesystemJAndroid::get_line() const {
ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use.");
@@ -271,31 +234,6 @@ uint64_t FileAccessFilesystemJAndroid::get_buffer(uint8_t *p_dst, uint64_t p_len
}
}
-void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) {
- store_buffer(&p_dest, 1);
-}
-
-void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2);
-}
-
-void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4);
-}
-
-void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8);
-}
-
void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (_file_write) {
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 2795ac02ac..1345b72fa6 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -78,20 +78,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual Error resize(int64_t p_length) override;
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual String get_line() const override; ///< get a line
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index f9a3e10680..b8b4233636 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -37,7 +37,7 @@ ext {
// Return the keystore file used for signing the release build.
getGodotKeystoreFile = { ->
def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
- if (keyStore == null) {
+ if (keyStore == null || keyStore.isEmpty()) {
return null
}
return file(keyStore)
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
index ba1185d647..b16e62149a 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
@@ -176,7 +176,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
registerMessenger(senderId, senderMessenger)
// Register ourselves to the sender so that it can communicate with us.
- registerSelfTo(pm, senderMessenger, editor.getEditorId())
+ registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)
}
/**
@@ -185,7 +185,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
*/
fun getMessageDispatcherPayload(): Bundle {
return Bundle().apply {
- putInt(KEY_EDITOR_ID, editor.getEditorId())
+ putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId)
putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler))
}
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 5d6da06f97..1995a38c2a 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -40,6 +40,7 @@ import android.content.pm.PackageManager
import android.os.*
import android.util.Log
import android.view.View
+import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -78,6 +79,8 @@ open class GodotEditor : GodotActivity() {
protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested"
// Command line arguments
+ private const val FULLSCREEN_ARG = "--fullscreen"
+ private const val FULLSCREEN_ARG_SHORT = "-f"
private const val EDITOR_ARG = "--editor"
private const val EDITOR_ARG_SHORT = "-e"
private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
@@ -116,11 +119,16 @@ open class GodotEditor : GodotActivity() {
override fun getGodotAppLayout() = R.layout.godot_editor_layout
- internal open fun getEditorId() = EDITOR_MAIN_INFO.windowId
+ internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
+ // Prevent the editor window from showing in the display cutout
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) {
+ window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+ }
+
// We exclude certain permissions from the set we request at startup, as they'll be
// requested on demand based on use-cases.
PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
@@ -213,10 +221,24 @@ open class GodotEditor : GodotActivity() {
}
protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent {
+ val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO &&
+ godot?.isInImmersiveMode() == true &&
+ !args.contains(FULLSCREEN_ARG) &&
+ !args.contains(FULLSCREEN_ARG_SHORT)
+ ) {
+ // If we're launching an editor window (project manager or editor) and we're in
+ // fullscreen mode, we want to remain in fullscreen mode.
+ // This doesn't apply to the play / game window since for that window fullscreen is
+ // controlled by the game logic.
+ args + FULLSCREEN_ARG
+ } else {
+ args
+ }
+
val newInstance = Intent()
.setComponent(ComponentName(this, editorWindowInfo.windowClassName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
+ .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs)
val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy)
val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) {
@@ -235,7 +257,7 @@ open class GodotEditor : GodotActivity() {
}
} else if (launchPolicy == LaunchPolicy.SAME) {
if (isPiPAvailable &&
- (args.contains(BREAKPOINTS_ARG) || args.contains(BREAKPOINTS_ARG_SHORT))) {
+ (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) {
Log.v(TAG, "Launching in PiP mode because of breakpoints")
newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true)
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
index 33fcbf9030..6b4bf255f2 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
@@ -128,7 +128,7 @@ class GodotGame : GodotEditor() {
override fun getGodotAppLayout() = R.layout.godot_game_layout
- override fun getEditorId() = RUN_GAME_INFO.windowId
+ override fun getEditorWindowInfo() = RUN_GAME_INFO
override fun overrideOrientationRequest() = false
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 49e8ffb008..38bd336e2d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -42,13 +42,16 @@ import android.hardware.Sensor
import android.hardware.SensorManager
import android.os.*
import android.util.Log
+import android.util.TypedValue
import android.view.*
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.error.Error
import org.godotengine.godot.input.GodotEditText
@@ -105,36 +108,26 @@ class Godot(private val context: Context) {
GodotPluginRegistry.getPluginRegistry()
}
- private val accelerometer_enabled = AtomicBoolean(false)
+ private val accelerometerEnabled = AtomicBoolean(false)
private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
- private val gravity_enabled = AtomicBoolean(false)
+ private val gravityEnabled = AtomicBoolean(false)
private val mGravity: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
- private val magnetometer_enabled = AtomicBoolean(false)
+ private val magnetometerEnabled = AtomicBoolean(false)
private val mMagnetometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
}
- private val gyroscope_enabled = AtomicBoolean(false)
+ private val gyroscopeEnabled = AtomicBoolean(false)
private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
- private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
- if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
- val decorView = requireActivity().window.decorView
- decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }}
-
val tts = GodotTTS(context)
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
@@ -185,7 +178,7 @@ class Godot(private val context: Context) {
private var xrMode = XRMode.REGULAR
private var expansionPackPath: String = ""
private var useApkExpansion = false
- private var useImmersive = false
+ private val useImmersive = AtomicBoolean(false)
private var useDebugOpengl = false
private var darkMode = false
@@ -254,15 +247,9 @@ class Godot(private val context: Context) {
xrMode = XRMode.OPENXR
} else if (commandLine[i] == "--debug_opengl") {
useDebugOpengl = true
- } else if (commandLine[i] == "--use_immersive") {
- useImmersive = true
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- registerUiChangeListener()
+ } else if (commandLine[i] == "--fullscreen") {
+ useImmersive.set(true)
+ newArgs.add(commandLine[i])
} else if (commandLine[i] == "--use_apk_expansion") {
useApkExpansion = true
} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
@@ -336,6 +323,54 @@ class Godot(private val context: Context) {
}
/**
+ * Toggle immersive mode.
+ * Must be called from the UI thread.
+ */
+ private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) {
+ val activity = getActivity() ?: return
+ val window = activity.window ?: return
+
+ if (!useImmersive.compareAndSet(!enabled, enabled) && !override) {
+ return
+ }
+
+ WindowCompat.setDecorFitsSystemWindows(window, !enabled)
+ val controller = WindowInsetsControllerCompat(window, window.decorView)
+ if (enabled) {
+ controller.hide(WindowInsetsCompat.Type.systemBars())
+ controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ } else {
+ val fullScreenThemeValue = TypedValue()
+ val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) {
+ fullScreenThemeValue.data == 0
+ } else {
+ // Fallback to checking the editor build
+ !isEditorBuild()
+ }
+
+ val types = if (hasStatusBar) {
+ WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars()
+ } else {
+ WindowInsetsCompat.Type.navigationBars()
+ }
+ controller.show(types)
+ }
+ }
+
+ /**
+ * Invoked from the render thread to toggle the immersive mode.
+ */
+ @Keep
+ private fun nativeEnableImmersiveMode(enabled: Boolean) {
+ runOnUiThread {
+ enableImmersiveMode(enabled)
+ }
+ }
+
+ @Keep
+ fun isInImmersiveMode() = useImmersive.get()
+
+ /**
* Initializes the native layer of the Godot engine.
*
* This must be preceded by [onCreate] and followed by [onInitRenderView] to complete
@@ -552,15 +587,7 @@ class Godot(private val context: Context) {
renderView?.onActivityResumed()
registerSensorsIfNeeded()
- if (useImmersive) {
- val window = requireActivity().window
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }
+ enableImmersiveMode(useImmersive.get(), true)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainResume()
}
@@ -571,16 +598,16 @@ class Godot(private val context: Context) {
return
}
- if (accelerometer_enabled.get() && mAccelerometer != null) {
+ if (accelerometerEnabled.get() && mAccelerometer != null) {
mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gravity_enabled.get() && mGravity != null) {
+ if (gravityEnabled.get() && mGravity != null) {
mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
}
- if (magnetometer_enabled.get() && mMagnetometer != null) {
+ if (magnetometerEnabled.get() && mMagnetometer != null) {
mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gyroscope_enabled.get() && mGyroscope != null) {
+ if (gyroscopeEnabled.get() && mGyroscope != null) {
mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
}
}
@@ -696,10 +723,10 @@ class Godot(private val context: Context) {
Log.v(TAG, "OnGodotMainLoopStarted")
godotMainLoopStarted.set(true)
- accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
- gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
- gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
- magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
+ accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
+ gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
+ gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
+ magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
runOnUiThread {
registerSensorsIfNeeded()
@@ -724,11 +751,6 @@ class Godot(private val context: Context) {
primaryHost?.onGodotRestartRequested(this)
}
- private fun registerUiChangeListener() {
- val decorView = requireActivity().window.decorView
- decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
- }
-
fun alert(
@StringRes messageResId: Int,
@StringRes titleResId: Int,
diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp
index 2b727e4ada..c92717e922 100644
--- a/platform/android/java_class_wrapper.cpp
+++ b/platform/android/java_class_wrapper.cpp
@@ -44,7 +44,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
MethodInfo *method = nullptr;
for (MethodInfo &E : M->value) {
- if (!p_instance && !E._static) {
+ if (!p_instance && !E._static && !E._constructor) {
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
continue;
}
@@ -102,15 +102,19 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
}
} break;
case ARG_TYPE_CLASS: {
- if (p_args[i]->get_type() != Variant::OBJECT) {
+ if (p_args[i]->get_type() != Variant::OBJECT && p_args[i]->get_type() != Variant::NIL) {
arg_expected = Variant::OBJECT;
} else {
Ref<RefCounted> ref = *p_args[i];
- if (!ref.is_null()) {
+ if (ref.is_valid()) {
if (Object::cast_to<JavaObject>(ref.ptr())) {
Ref<JavaObject> jo = ref;
//could be faster
- jclass c = env->FindClass(E.param_sigs[i].operator String().utf8().get_data());
+ String cn = E.param_sigs[i].operator String();
+ if (cn.begins_with("L") && cn.ends_with(";")) {
+ cn = cn.substr(1, cn.length() - 2);
+ }
+ jclass c = env->FindClass(cn.utf8().get_data());
if (!c || !env->IsInstanceOf(jo->instance, c)) {
arg_expected = Variant::OBJECT;
} else {
@@ -458,7 +462,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
} break;
default: {
jobject obj;
- if (method->_static) {
+ if (method->_constructor) {
+ obj = env->NewObject(_class, method->method, argv);
+ } else if (method->_static) {
obj = env->CallStaticObjectMethodA(_class, method->method, argv);
} else {
obj = env->CallObjectMethodA(p_instance->instance, method->method, argv);
@@ -487,7 +493,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method,
Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
Variant ret;
- bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret);
+
+ String method = (p_method == java_constructor_name) ? "<init>" : p_method;
+ bool found = _call_method(nullptr, method, p_args, p_argcount, r_error, ret);
if (found) {
return ret;
}
@@ -495,19 +503,156 @@ Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int
return RefCounted::callp(p_method, p_args, p_argcount, r_error);
}
+String JavaClass::get_java_class_name() const {
+ return java_class_name;
+}
+
+TypedArray<Dictionary> JavaClass::get_java_method_list() const {
+ TypedArray<Dictionary> method_list;
+
+ for (const KeyValue<StringName, List<MethodInfo>> &item : methods) {
+ for (const MethodInfo &mi : item.value) {
+ Dictionary method;
+
+ method["name"] = mi._constructor ? java_constructor_name : String(item.key);
+ method["id"] = (uint64_t)mi.method;
+ method["default_args"] = Array();
+ method["flags"] = METHOD_FLAGS_DEFAULT & (mi._static || mi._constructor ? METHOD_FLAG_STATIC : METHOD_FLAG_NORMAL);
+
+ {
+ Array a;
+
+ for (uint32_t argtype : mi.param_types) {
+ Dictionary d;
+
+ Variant::Type t = Variant::NIL;
+ float likelihood = 0.0;
+ _convert_to_variant_type(argtype, t, likelihood);
+ d["type"] = t;
+ if (t == Variant::OBJECT) {
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ d["hint"] = 0;
+ d["hint_string"] = "";
+ }
+
+ a.push_back(d);
+ }
+
+ method["args"] = a;
+ }
+
+ {
+ Dictionary d;
+
+ if (mi._constructor) {
+ d["type"] = Variant::OBJECT;
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ Variant::Type t = Variant::NIL;
+ float likelihood = 0.0;
+ _convert_to_variant_type(mi.return_type, t, likelihood);
+ d["type"] = t;
+ if (t == Variant::OBJECT) {
+ d["hint"] = PROPERTY_HINT_RESOURCE_TYPE;
+ d["hint_string"] = "JavaObject";
+ } else {
+ d["hint"] = 0;
+ d["hint_string"] = "";
+ }
+ }
+
+ method["return_type"] = d;
+ }
+
+ method_list.push_back(method);
+ }
+ }
+
+ return method_list;
+}
+
+Ref<JavaClass> JavaClass::get_java_parent_class() const {
+ ERR_FAIL_NULL_V(_class, Ref<JavaClass>());
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref<JavaClass>());
+
+ jclass superclass = (jclass)env->CallObjectMethod(_class, JavaClassWrapper::singleton->Class_getSuperclass);
+ if (!superclass) {
+ return Ref<JavaClass>();
+ }
+
+ Ref<JavaClass> ret = JavaClassWrapper::singleton->wrap_jclass(superclass);
+ env->DeleteLocalRef(superclass);
+ return ret;
+}
+
+String JavaClass::to_string() {
+ return "<JavaClass:" + java_class_name + ">";
+}
+
JavaClass::JavaClass() {
}
+JavaClass::~JavaClass() {
+ if (_class) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(_class);
+ }
+}
+
/////////////////////
Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- return Variant();
+ if (instance) {
+ Ref<JavaClass> c = base_class;
+ while (c.is_valid()) {
+ Variant ret;
+ bool found = c->_call_method(this, p_method, p_args, p_argcount, r_error, ret);
+ if (found) {
+ return ret;
+ }
+ c = c->get_java_parent_class();
+ }
+ }
+
+ return RefCounted::callp(p_method, p_args, p_argcount, r_error);
+}
+
+Ref<JavaClass> JavaObject::get_java_class() const {
+ return base_class;
}
-JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance) {
+String JavaObject::to_string() {
+ if (base_class.is_valid() && instance) {
+ return "<JavaObject:" + base_class->java_class_name + " \"" + (String)call("toString") + "\">";
+ }
+ return RefCounted::to_string();
+}
+
+JavaObject::JavaObject() {
+}
+
+JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject p_instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ base_class = p_base;
+ instance = env->NewGlobalRef(p_instance);
}
JavaObject::~JavaObject() {
+ if (instance) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+
+ env->DeleteGlobalRef(instance);
+ }
}
////////////////////
@@ -649,6 +794,16 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
return true;
} break;
case ARG_TYPE_CLASS: {
+ jclass java_class = env->GetObjectClass(obj);
+ Ref<JavaClass> java_class_wrapped = JavaClassWrapper::singleton->wrap_jclass(java_class);
+ env->DeleteLocalRef(java_class);
+
+ if (java_class_wrapped.is_valid()) {
+ Ref<JavaObject> ret = Ref<JavaObject>(memnew(JavaObject(java_class_wrapped, obj)));
+ var = ret;
+ return true;
+ }
+
return false;
} break;
case ARG_ARRAY_BIT | ARG_TYPE_VOID: {
@@ -966,43 +1121,67 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va
}
Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
- if (class_cache.has(p_class)) {
- return class_cache[p_class];
+ String class_name_dots = p_class.replace("/", ".");
+ if (class_cache.has(class_name_dots)) {
+ return class_cache[class_name_dots];
}
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, Ref<JavaClass>());
- jclass bclass = env->FindClass(p_class.utf8().get_data());
+ jclass bclass = env->FindClass(class_name_dots.replace(".", "/").utf8().get_data());
ERR_FAIL_NULL_V(bclass, Ref<JavaClass>());
- jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, getDeclaredMethods);
+ jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors);
+ ERR_FAIL_NULL_V(constructors, Ref<JavaClass>());
+ jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods);
ERR_FAIL_NULL_V(methods, Ref<JavaClass>());
Ref<JavaClass> java_class = memnew(JavaClass);
+ java_class->java_class_name = class_name_dots;
+ Vector<String> class_name_parts = class_name_dots.split(".");
+ java_class->java_constructor_name = class_name_parts[class_name_parts.size() - 1];
+ java_class->_class = (jclass)env->NewGlobalRef(bclass);
+ class_cache[class_name_dots] = java_class;
+
+ LocalVector<jobject> methods_and_constructors;
+ int constructor_count = env->GetArrayLength(constructors);
+ int method_count = env->GetArrayLength(methods);
+ methods_and_constructors.resize(method_count + constructor_count);
+ for (int i = 0; i < constructor_count; i++) {
+ methods_and_constructors[i] = env->GetObjectArrayElement(constructors, i);
+ }
+ for (int i = 0; i < method_count; i++) {
+ methods_and_constructors[constructor_count + i] = env->GetObjectArrayElement(methods, i);
+ }
- int count = env->GetArrayLength(methods);
-
- for (int i = 0; i < count; i++) {
- jobject obj = env->GetObjectArrayElement(methods, i);
+ for (int i = 0; i < (int)methods_and_constructors.size(); i++) {
+ jobject obj = methods_and_constructors[i];
ERR_CONTINUE(!obj);
- jstring name = (jstring)env->CallObjectMethod(obj, getName);
- String str_method = jstring_to_string(name, env);
- env->DeleteLocalRef(name);
+ bool is_constructor = i < constructor_count;
+
+ String str_method;
+ if (is_constructor) {
+ str_method = "<init>";
+ } else {
+ jstring name = (jstring)env->CallObjectMethod(obj, Method_getName);
+ str_method = jstring_to_string(name, env);
+ env->DeleteLocalRef(name);
+ }
Vector<String> params;
- jint mods = env->CallIntMethod(obj, getModifiers);
+ jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers);
if (!(mods & 0x0001)) {
env->DeleteLocalRef(obj);
continue; //not public bye
}
- jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, getParameterTypes);
- int count2 = env->GetArrayLength(param_types);
+ jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, is_constructor ? Constructor_getParameterTypes : Method_getParameterTypes);
+ int count = env->GetArrayLength(param_types);
if (!java_class->methods.has(str_method)) {
java_class->methods[str_method] = List<JavaClass::MethodInfo>();
@@ -1010,10 +1189,11 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
JavaClass::MethodInfo mi;
mi._static = (mods & 0x8) != 0;
+ mi._constructor = is_constructor;
bool valid = true;
String signature = "(";
- for (int j = 0; j < count2; j++) {
+ for (int j = 0; j < count; j++) {
jobject obj2 = env->GetObjectArrayElement(param_types, j);
String strsig;
uint32_t sig = 0;
@@ -1029,7 +1209,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
}
if (!valid) {
- print_line("Method can't be bound (unsupported arguments): " + p_class + "::" + str_method);
+ print_line("Method can't be bound (unsupported arguments): " + class_name_dots + "::" + str_method);
env->DeleteLocalRef(obj);
env->DeleteLocalRef(param_types);
continue;
@@ -1037,21 +1217,28 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
signature += ")";
- jobject return_type = (jobject)env->CallObjectMethod(obj, getReturnType);
+ if (is_constructor) {
+ signature += "V";
+ mi.return_type = JavaClass::ARG_TYPE_CLASS;
+ } else {
+ jobject return_type = (jobject)env->CallObjectMethod(obj, Method_getReturnType);
+
+ String strsig;
+ uint32_t sig = 0;
+ if (!_get_type_sig(env, return_type, sig, strsig)) {
+ print_line("Method can't be bound (unsupported return type): " + class_name_dots + "::" + str_method);
+ env->DeleteLocalRef(obj);
+ env->DeleteLocalRef(param_types);
+ env->DeleteLocalRef(return_type);
+ continue;
+ }
+
+ signature += strsig;
+ mi.return_type = sig;
- String strsig;
- uint32_t sig = 0;
- if (!_get_type_sig(env, return_type, sig, strsig)) {
- print_line("Method can't be bound (unsupported return type): " + p_class + "::" + str_method);
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(param_types);
env->DeleteLocalRef(return_type);
- continue;
}
- signature += strsig;
- mi.return_type = sig;
-
bool discard = false;
for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) {
@@ -1103,14 +1290,14 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
env->DeleteLocalRef(obj);
env->DeleteLocalRef(param_types);
- env->DeleteLocalRef(return_type);
}
+ env->DeleteLocalRef(constructors);
env->DeleteLocalRef(methods);
- jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, getFields);
+ jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, Class_getFields);
- count = env->GetArrayLength(fields);
+ int count = env->GetArrayLength(fields);
for (int i = 0; i < count; i++) {
jobject obj = env->GetObjectArrayElement(fields, i);
@@ -1146,7 +1333,18 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) {
env->DeleteLocalRef(fields);
- return Ref<JavaClass>();
+ return java_class;
+}
+
+Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, Ref<JavaClass>());
+
+ jstring class_name = (jstring)env->CallObjectMethod(p_class, Class_getName);
+ String class_name_string = jstring_to_string(class_name, env);
+ env->DeleteLocalRef(class_name);
+
+ return wrap(class_name_string);
}
JavaClassWrapper *JavaClassWrapper::singleton = nullptr;
@@ -1158,16 +1356,23 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) {
ERR_FAIL_NULL(env);
jclass bclass = env->FindClass("java/lang/Class");
- getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
- getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
+ Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;");
+ Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;");
+ Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;");
Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ Class_getSuperclass = env->GetMethodID(bclass, "getSuperclass", "()Ljava/lang/Class;");
+ env->DeleteLocalRef(bclass);
+
+ bclass = env->FindClass("java/lang/reflect/Constructor");
+ Constructor_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
+ Constructor_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Method");
- getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
- getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
- getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
- getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
+ Method_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;");
+ Method_getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;");
+ Method_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;");
+ Method_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I");
env->DeleteLocalRef(bclass);
bclass = env->FindClass("java/lang/reflect/Field");
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 1114969de8..390677df22 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -51,7 +51,10 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "main/main.h"
+
+#ifndef _3D_DISABLED
#include "servers/xr_server.h"
+#endif // _3D_DISABLED
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -271,14 +274,16 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
if (step.get() == STEP_SHOW_LOGO) {
- bool xr_enabled;
+ bool xr_enabled = false;
+#ifndef _3D_DISABLED
+ // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
+ // so we skip this step if xr is enabled.
if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
xr_enabled = GLOBAL_GET("xr/shaders/enabled");
} else {
xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON;
}
- // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
- // so we skip this step if xr is enabled.
+#endif // _3D_DISABLED
if (!xr_enabled) {
Main::setup_boot_logo();
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index f1759af54a..d3b30e4589 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
_sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
_verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I");
+ _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V");
+ _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) {
return ERR_UNCONFIGURED;
}
}
+
+void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) {
+ if (_enable_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled);
+ }
+}
+
+bool GodotJavaWrapper::is_in_immersive_mode() {
+ if (_is_in_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 6b66565981..51d7f98541 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -77,6 +77,8 @@ private:
jmethodID _has_feature = nullptr;
jmethodID _sign_apk = nullptr;
jmethodID _verify_apk = nullptr;
+ jmethodID _enable_immersive_mode = nullptr;
+ jmethodID _is_in_immersive_mode = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -122,6 +124,9 @@ public:
// Sign and verify apks
Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password);
Error verify_apk(const String &p_apk_path);
+
+ void enable_immersive_mode(bool p_enabled);
+ bool is_in_immersive_mode();
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp
index fc97d6eca4..4c17d03c60 100644
--- a/platform/android/jni_utils.cpp
+++ b/platform/android/jni_utils.cpp
@@ -30,6 +30,8 @@
#include "jni_utils.h"
+#include "api/java_class_wrapper.h"
+
jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) {
jvalret v;
@@ -185,6 +187,16 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a
v.obj = arr;
} break;
+ case Variant::OBJECT: {
+ Ref<JavaObject> generic_object = *p_arg;
+ if (generic_object.is_valid()) {
+ jobject obj = env->NewLocalRef(generic_object->get_instance());
+ v.val.l = obj;
+ v.obj = obj;
+ } else {
+ v.val.i = 0;
+ }
+ } break;
default: {
v.val.i = 0;
@@ -358,9 +370,11 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) {
return ret;
}
+ Ref<JavaObject> generic_object(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap(name), obj)));
+
env->DeleteLocalRef(c);
- return Variant();
+ return generic_object;
}
Variant::Type get_jni_type(const String &p_type) {
@@ -395,10 +409,10 @@ Variant::Type get_jni_type(const String &p_type) {
idx++;
}
- return Variant::NIL;
+ return Variant::OBJECT;
}
-const char *get_jni_sig(const String &p_type) {
+String get_jni_sig(const String &p_type) {
static struct {
const char *name;
const char *sig;
@@ -430,5 +444,5 @@ const char *get_jni_sig(const String &p_type) {
idx++;
}
- return "Ljava/lang/Object;";
+ return "L" + p_type.replace(".", "/") + ";";
}
diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h
index c608f9ebaa..631acd1cef 100644
--- a/platform/android/jni_utils.h
+++ b/platform/android/jni_utils.h
@@ -52,6 +52,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj);
Variant::Type get_jni_type(const String &p_type);
-const char *get_jni_sig(const String &p_type);
+String get_jni_sig(const String &p_type);
#endif // JNI_UTILS_H
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index e4b5392c4e..b99e825540 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false);
}
-Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) {
+Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String dest_dir = p_path.get_base_dir() + "/";
@@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() {
}
#endif
-Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
#ifdef MACOS_ENABLED
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
@@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
cmd_args_list.push_back("--remote-fs");
@@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
cmd_args_list.push_back("--remote-debug");
cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
@@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
cmd_args_list.push_back("--debug-collisions");
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
cmd_args_list.push_back("--debug-navigation");
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index 1964906c27..db7c0553dd 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
- Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick);
+ Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick);
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
@@ -169,7 +169,7 @@ public:
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual bool poll_export() override {
bool dc = devices_changed.is_set();
@@ -202,7 +202,7 @@ public:
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 0032b898d2..69ba742f72 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP
return OK;
}
-Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h
index bbc55b82ce..1d9ef01d1a 100644
--- a/platform/linuxbsd/export/export_plugin.h
+++ b/platform/linuxbsd/export/export_plugin.h
@@ -76,7 +76,7 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
virtual bool is_executable(const String &p_path) const override;
@@ -87,7 +87,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformLinuxBSD();
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 2b98fda0d5..94a748e414 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter,
append_dbus_string(&struct_iter, p_filter_names[i]);
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
- const String &flt = p_filter_exts[i];
+ const String &flt_orig = p_filter_exts[i];
+ String flt;
+ for (int j = 0; j < flt_orig.length(); j++) {
+ if (is_unicode_letter(flt_orig[j])) {
+ flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j]));
+ } else {
+ flt += flt_orig[j];
+ }
+ }
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 93096fcdcc..d1d83fe4ce 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -1429,6 +1429,7 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
if (fallback) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");
rendering_driver = "opengl3_es";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Could not initialize OpenGL.");
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 8a2f83be2d..e602963c54 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -6231,6 +6231,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (fallback) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES.");
rendering_driver = "opengl3_es";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Could not initialize OpenGL.");
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 989a9dcf6c..52dc51bc96 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -3615,6 +3615,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
WARN_PRINT("Your video card drivers seem not to support GLES3 / ANGLE or ANGLE dynamic libraries (libEGL.dylib and libGLESv2.dylib) are missing, switching to native OpenGL.");
#endif
rendering_driver = "opengl3";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
} else {
r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Could not initialize ANGLE OpenGL.");
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 290b0082fc..8372600ae9 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1505,7 +1505,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres
return OK;
}
-Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -2511,7 +2511,7 @@ void EditorExportPlatformMacOS::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -2573,8 +2573,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -2583,7 +2582,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 062a2e5f95..5457c687d3 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -147,7 +147,7 @@ public:
virtual bool is_executable(const String &p_path) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
@@ -167,7 +167,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformMacOS();
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d8c1b6033d..5faab74d7b 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_
}
}
-void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
+void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
// Engine.js config
Dictionary config;
Array libs;
for (int i = 0; i < p_shared_objects.size(); i++) {
libs.push_back(p_shared_objects[i].path.get_file());
}
- Vector<String> flags;
- gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
+ Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
Array args;
for (int i = 0; i < flags.size(); i++) {
args.push_back(flags[i]);
@@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo
return list;
}
-Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String custom_debug = p_preset->get("custom_template/debug");
@@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
return "";
}
-Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
+Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
const uint16_t bind_port = EDITOR_GET("export/web/http_port");
// Resolve host if needed.
const String bind_host = EDITOR_GET("export/web/http_host");
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 2f67d8107f..3c743e2e74 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template);
- void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
+ void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr);
Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects);
Error _write_or_error(const uint8_t *p_content, int p_len, String p_path);
@@ -120,14 +120,14 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool poll_export() override;
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
virtual void get_platform_features(List<String> *r_features) const override {
diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp
index 2abe285d31..a6015092e8 100644
--- a/platform/windows/crash_handler_windows_seh.cpp
+++ b/platform/windows/crash_handler_windows_seh.cpp
@@ -118,7 +118,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
HANDLE process = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
DWORD offset_from_symbol = 0;
- IMAGEHLP_LINE64 line = { 0 };
+ IMAGEHLP_LINE64 line = {};
std::vector<module_data> modules;
DWORD cbNeeded;
std::vector<HMODULE> module_handles(1);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 82a98655c0..92ac921cee 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -381,6 +381,15 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
## Compile/link flags
+ if env["use_llvm"]:
+ env["CC"] = "clang-cl"
+ env["CXX"] = "clang-cl"
+ env["LINK"] = "lld-link"
+ env["AR"] = "llvm-lib"
+
+ env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"])
+ env.extra_suffix = ".llvm" + env.extra_suffix
+
env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable.
if env["silence_msvc"] and not env.GetOption("clean"):
@@ -465,7 +474,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
- env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
# Once it was thought that only debug builds would be too large,
# but this has recently stopped being true. See the mingw function
# for notes on why this shouldn't be enabled for gcc
@@ -590,6 +598,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["target"] in ["editor", "template_debug"]:
LIBS += ["psapi", "dbghelp"]
+ if env["use_llvm"]:
+ LIBS += [f"clang_rt.builtins-{env['arch']}"]
+
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
if vcvars_msvc_config:
@@ -605,14 +616,22 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["lto"] != "none":
if env["lto"] == "thin":
- print_error("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
- sys.exit(255)
- env.AppendUnique(CCFLAGS=["/GL"])
- env.AppendUnique(ARFLAGS=["/LTCG"])
- if env["progress"]:
- env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ if not env["use_llvm"]:
+ print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
+ sys.exit(255)
+
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+ elif env["use_llvm"]:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
else:
- env.AppendUnique(LINKFLAGS=["/LTCG"])
+ env.AppendUnique(CCFLAGS=["/GL"])
+ env.AppendUnique(ARFLAGS=["/LTCG"])
+ if env["progress"]:
+ env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ else:
+ env.AppendUnique(LINKFLAGS=["/LTCG"])
if vcvars_msvc_config:
env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")])
@@ -630,6 +649,61 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+def get_ar_version(env):
+ ret = {
+ "major": -1,
+ "minor": -1,
+ "patch": -1,
+ "is_llvm": False,
+ }
+ try:
+ output = (
+ subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt"))
+ .strip()
+ .decode("utf-8")
+ )
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't check version of `ar`.")
+ return ret
+
+ match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(:?\.(\d+))?", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ if match[3]:
+ ret["patch"] = int(match[3])
+ else:
+ ret["patch"] = 0
+ return ret
+
+ match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ ret["patch"] = int(match[3])
+ ret["is_llvm"] = True
+ return ret
+
+ print_warning("Couldn't parse version of `ar`.")
+ return ret
+
+
+def get_is_ar_thin_supported(env):
+ """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14."""
+ ar_version = get_ar_version(env)
+ if ar_version["major"] == -1:
+ return False
+
+ if ar_version["is_llvm"]:
+ return ar_version["major"] >= 14
+
+ if ar_version["major"] == 2:
+ return ar_version["minor"] >= 38
+
+ print_warning("Unknown Binutils `ar` version.")
+ return False
+
+
def configure_mingw(env: "SConsEnvironment"):
# Workaround for MinGW. See:
# https://www.scons.org/wiki/LongCmdLinesOnWin32
@@ -762,7 +836,8 @@ def configure_mingw(env: "SConsEnvironment"):
if env["use_llvm"] and os.name == "nt" and methods._colorize:
env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"])
- env.Append(ARFLAGS=["--thin"])
+ if get_is_ar_thin_supported(env):
+ env.Append(ARFLAGS=["--thin"])
env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"])
env.Append(
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 8bcd556c22..602ca5f52e 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -6150,6 +6150,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (rendering_context->initialize() == OK) {
WARN_PRINT("Your video card drivers seem not to support Direct3D 12, switching to Vulkan.");
rendering_driver = "vulkan";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
failed = false;
}
}
@@ -6163,6 +6164,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
if (rendering_context->initialize() == OK) {
WARN_PRINT("Your video card drivers seem not to support Vulkan, switching to Direct3D 12.");
rendering_driver = "d3d12";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
failed = false;
}
}
@@ -6241,6 +6243,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
}
rendering_driver = "opengl3_angle";
+ OS::get_singleton()->set_current_rendering_driver_name(rendering_driver);
}
}
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index b465bd4ecd..8d3f4bb269 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
}
-Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (p_preset->get("application/modify_resources")) {
_rcedit_add_data(p_preset, p_path, false);
String wrapper_path = p_path.get_basename() + ".console.exe";
@@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 6ccb4a15a7..e86aac83d4 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC {
String _get_exe_arch(const String &p_path) const;
public:
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) const override;
@@ -95,7 +95,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformWindows();
diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp
index 9e0b9eed8a..30743c6900 100644
--- a/platform/windows/windows_utils.cpp
+++ b/platform/windows/windows_utils.cpp
@@ -155,7 +155,11 @@ Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) {
} else if (!FileAccess::exists(copy_pdb_path)) {
copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file());
}
- ERR_FAIL_COND_V_MSG(!FileAccess::exists(copy_pdb_path), FAILED, vformat("File '%s' does not exist.", copy_pdb_path));
+ if (!FileAccess::exists(copy_pdb_path)) {
+ // The PDB file may be distributed separately on purpose, so we don't consider this an error.
+ WARN_VERBOSE(vformat("PDB file '%s' for library '%s' was not found, skipping copy/rename.", copy_pdb_path, p_dll_path));
+ return ERR_SKIP;
+ }
String new_pdb_base_name = p_dll_path.get_file().get_basename() + "_";
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 014419573c..1cab29f383 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -463,7 +463,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
name = animation;
}
- ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
+ ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
@@ -541,7 +541,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed));
- if (frames == nullptr) {
+ if (frames.is_null()) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 89a0479de3..7c60e47e64 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -276,10 +276,6 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer2D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -385,8 +381,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance);
@@ -415,7 +410,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance");
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index 9dd9d4a376..fdb2d2cdd0 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -47,9 +47,18 @@ void Parallax2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
- autoscroll_offset += autoscroll * get_process_delta_time();
- autoscroll_offset = autoscroll_offset.posmodv(repeat_size);
+ Point2 offset = scroll_offset;
+ offset += autoscroll * get_process_delta_time();
+ if (repeat_size.x) {
+ offset.x = Math::fposmod(offset.x, repeat_size.x);
+ }
+
+ if (repeat_size.y) {
+ offset.y = Math::fposmod(offset.y, repeat_size.y);
+ }
+
+ scroll_offset = offset;
_update_scroll();
} break;
@@ -106,14 +115,14 @@ void Parallax2D::_update_scroll() {
scroll_ofs *= scroll_scale;
if (repeat_size.x) {
- real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x);
+ real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x, repeat_size.x * get_scale().x);
scroll_ofs.x = screen_offset.x - mod;
} else {
scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x;
}
if (repeat_size.y) {
- real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y);
+ real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y, repeat_size.y * get_scale().y);
scroll_ofs.y = screen_offset.y - mod;
} else {
scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y;
@@ -193,7 +202,6 @@ void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) {
}
autoscroll = p_autoscroll;
- autoscroll_offset = Point2();
_update_process();
_update_scroll();
diff --git a/scene/2d/parallax_2d.h b/scene/2d/parallax_2d.h
index 5fbc3a20c8..f15e3fa9ff 100644
--- a/scene/2d/parallax_2d.h
+++ b/scene/2d/parallax_2d.h
@@ -47,7 +47,6 @@ class Parallax2D : public Node2D {
Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT);
Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT);
Point2 autoscroll;
- Point2 autoscroll_offset;
bool follow_viewport = true;
bool ignore_camera_scroll = false;
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index b0ad7ec110..ebb03e4e73 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -443,13 +443,15 @@ void TileMapLayer::_rendering_notification(int p_what) {
Transform2D tilemap_xform = get_global_transform();
for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) {
const CellData &cell_data = kv.value;
- for (const RID &occluder : cell_data.occluders) {
- if (occluder.is_null()) {
- continue;
+ for (const LocalVector<RID> &polygons : cell_data.occluders) {
+ for (const RID &rid : polygons) {
+ if (rid.is_null()) {
+ continue;
+ }
+ Transform2D xform(0, tile_set->map_to_local(kv.key));
+ rs->canvas_light_occluder_attach_to_canvas(rid, get_canvas());
+ rs->canvas_light_occluder_set_transform(rid, tilemap_xform * xform);
}
- Transform2D xform(0, tile_set->map_to_local(kv.key));
- rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
- rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform);
}
}
}
@@ -557,8 +559,10 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
RenderingServer *rs = RenderingServer::get_singleton();
// Free the occluders.
- for (const RID &rid : r_cell_data.occluders) {
- rs->free(rid);
+ for (const LocalVector<RID> &polygons : r_cell_data.occluders) {
+ for (const RID &rid : polygons) {
+ rs->free(rid);
+ }
}
r_cell_data.occluders.clear();
}
@@ -566,11 +570,12 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) {
void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
RenderingServer *rs = RenderingServer::get_singleton();
- // Free unused occluders then resize the occluders array.
+ // Free unused occluders then resize the occluder array.
for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) {
- RID occluder_id = r_cell_data.occluders[i];
- if (occluder_id.is_valid()) {
- rs->free(occluder_id);
+ for (const RID &occluder_id : r_cell_data.occluders[i]) {
+ if (occluder_id.is_valid()) {
+ rs->free(occluder_id);
+ }
}
}
r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count());
@@ -598,30 +603,42 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) {
// Create, update or clear occluders.
bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated();
for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) {
- Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index);
-
- RID &occluder = r_cell_data.occluders[occlusion_layer_index];
+ LocalVector<RID> &occluders = r_cell_data.occluders[occlusion_layer_index];
- if (occluder_polygon.is_valid()) {
- // Create or update occluder.
- Transform2D xform;
- xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
- if (!occluder.is_valid()) {
- occluder = rs->canvas_light_occluder_create();
- if (needs_set_not_interpolated) {
- rs->canvas_light_occluder_set_interpolated(occluder, false);
- }
+ // Free unused occluders then resize the occluders array.
+ for (uint32_t i = tile_data->get_occluder_polygons_count(occlusion_layer_index); i < r_cell_data.occluders[occlusion_layer_index].size(); i++) {
+ RID occluder_id = occluders[i];
+ if (occluder_id.is_valid()) {
+ rs->free(occluder_id);
}
- rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
- rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid());
- rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
- rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
- rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
- } else {
- // Clear occluder.
- if (occluder.is_valid()) {
- rs->free(occluder);
- occluder = RID();
+ }
+ occluders.resize(tile_data->get_occluder_polygons_count(occlusion_layer_index));
+
+ for (uint32_t occlusion_polygon_index = 0; occlusion_polygon_index < occluders.size(); occlusion_polygon_index++) {
+ RID &occluder = occluders[occlusion_polygon_index];
+ Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index);
+ if (occluder_polygon.is_valid()) {
+ // Create or update occluder.
+
+ Transform2D xform;
+ xform.set_origin(tile_set->map_to_local(r_cell_data.coords));
+ if (!occluder.is_valid()) {
+ occluder = rs->canvas_light_occluder_create();
+ if (needs_set_not_interpolated) {
+ rs->canvas_light_occluder_set_interpolated(occluder, false);
+ }
+ }
+ rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform);
+ rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index, flip_h, flip_v, transpose)->get_rid());
+ rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas());
+ rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index));
+ rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index));
+ } else {
+ // Clear occluder.
+ if (occluder.is_valid()) {
+ rs->free(occluder);
+ occluder = RID();
+ }
}
}
}
@@ -957,7 +974,7 @@ void TileMapLayer::_navigation_update(bool p_force_cleanup) {
// Create a dedicated map for each layer.
RID new_layer_map = ns->map_create();
// Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error.
- ns->map_set_cell_size(new_layer_map, 1.0);
+ ns->map_set_cell_size(new_layer_map, NavigationDefaults2D::navmesh_cell_size);
ns->map_set_active(new_layer_map, true);
navigation_map_override = new_layer_map;
}
@@ -1709,11 +1726,13 @@ void TileMapLayer::_physics_interpolated_changed() {
}
for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) {
- for (const RID &occluder : E.value.occluders) {
- if (occluder.is_valid()) {
- rs->canvas_light_occluder_set_interpolated(occluder, interpolated);
- if (needs_reset) {
- rs->canvas_light_occluder_reset_physics_interpolation(occluder);
+ for (const LocalVector<RID> &polygons : E.value.occluders) {
+ for (const RID &occluder_id : polygons) {
+ if (occluder_id.is_valid()) {
+ rs->canvas_light_occluder_set_interpolated(occluder_id, interpolated);
+ if (needs_reset) {
+ rs->canvas_light_occluder_reset_physics_interpolation(occluder_id);
+ }
}
}
}
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index 2a986667bd..cc0a5b49fb 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -108,7 +108,7 @@ struct CellData {
// Rendering.
Ref<RenderingQuadrant> rendering_quadrant;
SelfList<CellData> rendering_quadrant_list_element;
- LocalVector<RID> occluders;
+ LocalVector<LocalVector<RID>> occluders;
// Physics.
LocalVector<RID> bodies;
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 4d3f494ccf..591528b915 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer3D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
@@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance");
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 03fe5e1fad..acbc443a93 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) {
emission_ring_inner_radius = p_radius;
}
+void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+}
+
void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {
scale_curve_x = p_scale_curve;
}
@@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t CPUParticles3D::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const {
return emission_shape;
}
@@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
}
} break;
case EMISSION_SHAPE_RING: {
+ real_t radius_clamped = MAX(0.001, emission_ring_radius);
+ real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);
+ real_t y_pos = Math::randf();
+ real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5);
+ y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew);
real_t ring_random_angle = Math::randf() * Math_TAU;
- real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);
Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
Vector3 ortho_axis;
if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
@@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
ortho_axis = ortho_axis.normalized();
ortho_axis.rotate(axis, ring_random_angle);
ortho_axis = ortho_axis.normalized();
- p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis;
+ p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity);
@@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_GROUP("Particle Flags", "particle_flag_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
@@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() {
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_gravity(Vector3(0, -9.8, 0));
diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h
index 82ea4bbef3..978bb64e71 100644
--- a/scene/3d/cpu_particles_3d.h
+++ b/scene/3d/cpu_particles_3d.h
@@ -178,6 +178,7 @@ private:
real_t emission_ring_height = 0.0;
real_t emission_ring_radius = 0.0;
real_t emission_ring_inner_radius = 0.0;
+ real_t emission_ring_cone_angle = 0.0;
Ref<Curve> scale_curve_x;
Ref<Curve> scale_curve_y;
@@ -282,6 +283,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_scale_curve_x(Ref<Curve> p_scale_curve);
void set_scale_curve_y(Ref<Curve> p_scale_curve);
void set_scale_curve_z(Ref<Curve> p_scale_curve);
@@ -297,6 +299,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
Ref<Curve> get_scale_curve_z() const;
diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp
index 85bf8846b9..987117a7a0 100644
--- a/scene/3d/mesh_instance_3d.cpp
+++ b/scene/3d/mesh_instance_3d.cpp
@@ -517,12 +517,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p
Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) {
Ref<ArrayMesh> source_mesh = get_mesh();
- ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh.");
Ref<ArrayMesh> bake_mesh;
if (p_existing.is_valid()) {
- ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
+ ERR_FAIL_COND_V_MSG(p_existing.is_null(), Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh.");
ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh.");
bake_mesh = p_existing;
diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp
index ffe79e0892..510d887c83 100644
--- a/scene/3d/physical_bone_simulator_3d.cpp
+++ b/scene/3d/physical_bone_simulator_3d.cpp
@@ -285,11 +285,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no
}
void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
+ _pose_updated();
+
simulating = true;
_reset_physical_bones_state();
- _pose_updated();
-
Vector<int> sim_bones;
if (p_bones.size() > 0) {
sim_bones.resize(p_bones.size());
@@ -357,47 +357,16 @@ void PhysicalBoneSimulator3D::_process_modification() {
if (!skeleton) {
return;
}
- if (!enabled) {
- for (int i = 0; i < bones.size(); i++) {
- if (bones[i].physical_bone) {
- if (bones[i].physical_bone->is_simulating_physics() == false) {
- bones[i].physical_bone->reset_to_rest_position();
- }
- }
+ ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
+ for (int i = 0; i < skeleton->get_bone_count(); i++) {
+ if (!bones[i].physical_bone) {
+ continue;
}
- } else {
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
+ if (bones[i].physical_bone->is_simulating_physics() == false) {
+ bones[i].physical_bone->reset_to_rest_position();
+ } else if (simulating) {
skeleton->set_bone_global_pose(i, bones[i].global_pose);
}
-
- // TODO:
- // The above method is performance heavy and needs to be improved.
- // Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved,
- // but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side.
- // However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order.
- /*
- ERR_FAIL_COND(skeleton->get_bone_count() != bones.size());
- LocalVector<Transform3D> local_poses;
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- Transform3D pt;
- if (skeleton->get_bone_parent(i) >= 0) {
- pt = get_bone_global_pose(skeleton->get_bone_parent(i));
- }
- local_poses.push_back(pt.affine_inverse() * bones[i].global_pose);
- }
- for (int i = 0; i < skeleton->get_bone_count(); i++) {
- if (!bones[i].physical_bone) {
- continue;
- }
- skeleton->set_bone_pose_position(i, local_poses[i].origin);
- skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion());
- skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale());
- }
- */
}
}
diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h
index ee900e0e77..ddc9cd569a 100644
--- a/scene/3d/physical_bone_simulator_3d.h
+++ b/scene/3d/physical_bone_simulator_3d.h
@@ -41,7 +41,6 @@ class PhysicalBoneSimulator3D : public SkeletonModifier3D {
GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D);
bool simulating = false;
- bool enabled = true;
struct SimulatedBone {
int parent;
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index c23032d3b9..b4c321cf5f 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const {
return m_raycastInfo.m_isInContact;
}
+Vector3 VehicleWheel3D::get_contact_point() const {
+ return m_raycastInfo.m_contactPointWS;
+}
+
+Vector3 VehicleWheel3D::get_contact_normal() const {
+ return m_raycastInfo.m_contactNormalWS;
+}
+
Node3D *VehicleWheel3D::get_contact_body() const {
return m_raycastInfo.m_groundObject;
}
@@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact);
ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body);
+ ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point);
+ ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal);
ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence);
ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence);
diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h
index def9984440..24f120ed26 100644
--- a/scene/3d/physics/vehicle_body_3d.h
+++ b/scene/3d/physics/vehicle_body_3d.h
@@ -130,6 +130,10 @@ public:
bool is_in_contact() const;
+ Vector3 get_contact_point() const;
+
+ Vector3 get_contact_normal() const;
+
Node3D *get_contact_body() const;
void set_roll_influence(real_t p_value);
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 50218a6d86..42460eec4c 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
// Properly setup UVs for impostor textures (AtlasTexture).
Ref<AtlasTexture> atlas_tex = p_texture;
- if (atlas_tex != nullptr) {
+ if (atlas_tex.is_valid()) {
src_tsize[0] = atlas_tex->get_atlas()->get_width();
src_tsize[1] = atlas_tex->get_atlas()->get_height();
}
@@ -1324,7 +1324,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
name = animation;
}
- ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name));
+ ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name));
ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name));
if (frames->get_frame_count(name) == 0) {
@@ -1402,7 +1402,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
emit_signal(SceneStringName(animation_changed));
- if (frames == nullptr) {
+ if (frames.is_null()) {
animation = StringName();
stop();
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h
index 6ea1cfdbb0..08d018eee9 100644
--- a/scene/3d/voxelizer.h
+++ b/scene/3d/voxelizer.h
@@ -35,9 +35,8 @@
class Voxelizer {
private:
- enum {
+ enum : uint32_t {
CHILD_EMPTY = 0xFFFFFFFF
-
};
struct Cell {
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 20dd12f8c3..0ab4dbe470 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -947,14 +947,6 @@ 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 res;
- if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
- return res;
- }
- return _post_process_key_value(p_anim, p_track, p_value, p_object_id, 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)) {
@@ -974,6 +966,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in
return p_value;
}
+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) {
+ if (is_GDVIRTUAL_CALL_post_process_key_value) {
+ Variant res;
+ if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
+ return res;
+ }
+ is_GDVIRTUAL_CALL_post_process_key_value = false;
+ }
+ return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
+}
+
void AnimationMixer::_blend_init() {
// Check all tracks, see if they need modification.
root_motion_position = Vector3(0, 0, 0);
@@ -1080,22 +1083,26 @@ void AnimationMixer::_blend_calc_total_weight() {
for (const AnimationInstance &ai : animation_instances) {
Ref<Animation> a = ai.animation_data.animation;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
- Vector<Animation::TypeHash> processed_hashes;
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ 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;
+ processed_hashes.clear();
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ for (const Animation::Track *animation_track : tracks) {
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash) || processed_hashes.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == 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_cache[thash];
+ TrackCache *track = *track_ptr;
int blend_idx = track_map[track->path];
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
track->total_weight += blend;
processed_hashes.push_back(thash);
}
@@ -1115,26 +1122,33 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag;
bool is_external_seeking = ai.playback_info.is_external_seeking;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
+ const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
+ int track_weights_count = ai.playback_info.track_weights.size();
bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
bool seeked_backward = signbit(p_delta);
#ifndef _3D_DISABLED
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
-
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ Animation::Track *const *tracks_ptr = tracks.ptr();
+ real_t a_length = a->get_length();
+ int count = tracks.size();
+ for (int i = 0; i < count; i++) {
+ const Animation::Track *animation_track = tracks_ptr[i];
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == nullptr) {
continue; // No path, but avoid error spamming.
}
- TrackCache *track = track_cache[thash];
- ERR_CONTINUE(!track_map.has(track->path));
- int blend_idx = track_map[track->path];
+ 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;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
if (!deterministic) {
// If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
@@ -1143,8 +1157,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
blend = blend / track->total_weight;
}
- Animation::TrackType ttype = a->track_get_type(i);
- track->root_motion = root_motion_track == a->track_get_path(i);
+ Animation::TrackType ttype = animation_track->type;
+ track->root_motion = root_motion_track == animation_track->path;
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
@@ -1161,26 +1175,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1195,7 +1209,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
- a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]);
+ a->try_position_track_interpolate(i, (double)a_length, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
@@ -1210,7 +1224,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, 0, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
@@ -1221,7 +1235,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 loc;
@@ -1249,26 +1263,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1283,7 +1297,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
- a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
+ a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = 0;
@@ -1297,7 +1311,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, 0, &rot[1]);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]);
@@ -1308,7 +1322,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_rotation_track_interpolate(i, time, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Quaternion rot;
@@ -1336,26 +1350,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1370,7 +1384,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
- a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
+ a->try_scale_track_interpolate(i, (double)a_length, &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
prev_time = 0;
@@ -1385,7 +1399,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, 0, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]);
@@ -1396,7 +1410,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, time, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 scale;
@@ -1560,7 +1574,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
- track_info.length = a->get_length();
+ track_info.length = a_length;
track_info.time = time;
track_info.volume += blend;
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
@@ -1679,7 +1693,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop.
} break;
case Animation::LOOP_PINGPONG: {
- at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
+ at_anim_pos = Math::pingpong(time - pos, (double)a_length);
} break;
default:
break;
@@ -1717,6 +1731,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
}
}
+ is_GDVIRTUAL_CALL_post_process_key_value = true;
}
void AnimationMixer::_blend_apply() {
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index c029c68ae1..5482197fbd 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -48,6 +48,7 @@ class AnimationMixer : public Node {
#endif // TOOLS_ENABLED
bool reset_on_save = true;
+ bool is_GDVIRTUAL_CALL_post_process_key_value = true;
public:
enum AnimationCallbackModeProcess {
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index c5a6a99d07..867bbda4b3 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -203,10 +203,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
}
for (const KeyValue<NodePath, bool> &E : filter) {
- if (!process_state->track_map.has(E.key)) {
+ const HashMap<NodePath, int> &map = *process_state->track_map;
+ if (!map.has(E.key)) {
continue;
}
- int idx = process_state->track_map[E.key];
+ int idx = map[E.key];
blendw[idx] = 1.0; // Filtered goes to one.
}
@@ -618,7 +619,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
process_state.valid = true;
process_state.invalid_reasons = "";
process_state.last_pass = process_pass;
- process_state.track_map = p_track_map;
+ process_state.track_map = &p_track_map;
// Init node state for root AnimationNode.
root_animation_node->node_state.track_weights.resize(p_track_count);
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index aa497ff1d6..d4b7bf31c9 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -106,7 +106,7 @@ public:
// 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;
- HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
+ const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index f8bbd704f4..e1fd8abede 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -579,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) {
Object *target_instance = ObjectDB::get_instance(target);
if (!target_instance) {
+ _finish();
return false;
}
elapsed_time += r_delta;
@@ -706,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
@@ -770,6 +772,7 @@ bool MethodTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 183c4af950..d4b44a8b69 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -154,10 +154,6 @@ void AudioStreamPlayer::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer::set_stream_paused(bool p_pause) {
internal->set_stream_paused(p_pause);
}
@@ -249,8 +245,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mix_target", "mix_target"), &AudioStreamPlayer::set_mix_target);
ClassDB::bind_method(D_METHOD("get_mix_target"), &AudioStreamPlayer::get_mix_target);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer::_set_playing);
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused);
@@ -267,7 +262,7 @@ void AudioStreamPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target");
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 9bb6130742..bd4128384f 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -547,7 +547,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (font_size_it && font_size_it->font_size > 0) {
font_size = font_size_it->font_size;
}
- l.text_buf->add_string("\n", font, font_size);
+ l.text_buf->add_string(String::chr(0x200B), font, font_size);
txt += "\n";
l.char_count++;
remaining_characters--;
@@ -817,6 +817,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
int line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
Size2 ctrl_size = get_size();
// Draw text.
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
@@ -824,7 +826,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
off.y += theme_cache.line_separation;
}
- if (p_ofs.y + off.y >= ctrl_size.height) {
+ if (p_ofs.y + off.y >= ctrl_size.height - v_limit) {
break;
}
@@ -1890,10 +1892,12 @@ void RichTextLabel::_notification(int p_what) {
visible_paragraph_count = 0;
visible_line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
// New cache draw.
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
int processed_glyphs = 0;
- while (ofs.y < size.height && from_line < to_line) {
+ while (ofs.y < size.height - v_limit && from_line < to_line) {
MutexLock lock(main->lines[from_line].text_buf->get_mutex());
visible_paragraph_count++;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 7592f4b7c3..edd25d1d5c 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3274,12 +3274,10 @@ void Tree::value_editor_changed(double p_value) {
return;
}
- TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col];
- c.val = p_value;
+ const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col];
- line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step)));
+ line_editor->set_text(String::num(p_value, Math::range_step_decimals(c.step)));
- item_edited(popup_edited_item_col, popup_edited_item);
queue_redraw();
}
@@ -4490,9 +4488,16 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous
}
void Tree::item_changed(int p_column, TreeItem *p_item) {
- if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
- p_item->cells.write[p_column].dirty = true;
- columns.write[p_column].cached_minimum_width_dirty = true;
+ if (p_item != nullptr) {
+ if (p_column >= 0 && p_column < p_item->cells.size()) {
+ p_item->cells.write[p_column].dirty = true;
+ columns.write[p_column].cached_minimum_width_dirty = true;
+ } else if (p_column == -1) {
+ for (int i = 0; i < p_item->cells.size(); i++) {
+ p_item->cells.write[i].dirty = true;
+ columns.write[i].cached_minimum_width_dirty = true;
+ }
+ }
}
queue_redraw();
}
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index a370ef2d18..10ca19dbfd 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -5048,6 +5048,9 @@ Viewport::~Viewport() {
for (ViewportTexture *E : viewport_textures) {
E->vp = nullptr;
}
+ if (world_2d.is_valid()) {
+ world_2d->remove_viewport(this);
+ }
ERR_FAIL_NULL(RenderingServer::get_singleton());
RenderingServer::get_singleton()->free(viewport);
}
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
index ee6fc61af3..e3f14539a8 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
@@ -176,7 +176,7 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec
}
void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) {
- ERR_FAIL_NULL(p_other_geometry);
+ ERR_FAIL_COND(p_other_geometry.is_null());
Vector<Vector<Vector2>> other_traversable_outlines;
Vector<Vector<Vector2>> other_obstruction_outlines;
diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index 86bda47ace..ed2c606c55 100644
--- a/scene/resources/2d/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
@@ -33,6 +33,7 @@
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationPolygon : public Resource {
GDCLASS(NavigationPolygon, Resource);
@@ -50,7 +51,7 @@ class NavigationPolygon : public Resource {
// Navigation mesh
Ref<NavigationMesh> navigation_mesh;
- real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size.
+ real_t cell_size = NavigationDefaults2D::navmesh_cell_size;
real_t border_size = 0.0f;
Rect2 baking_rect;
diff --git a/scene/resources/2d/tile_set.compat.inc b/scene/resources/2d/tile_set.compat.inc
index 873ae3aa93..58e33f7b35 100644
--- a/scene/resources/2d/tile_set.compat.inc
+++ b/scene/resources/2d/tile_set.compat.inc
@@ -37,7 +37,7 @@ Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p
}
Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const {
- return get_occluder(p_layer_id, false, false, false);
+ return get_occluder_polygon(p_layer_id, 0, false, false, false);
}
void TileData::_bind_compatibility_methods() {
diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp
index aa55e3746c..229e18be23 100644
--- a/scene/resources/2d/tile_set.cpp
+++ b/scene/resources/2d/tile_set.cpp
@@ -3444,7 +3444,8 @@ void TileSet::_compatibility_conversion() {
polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
}
occluder->set_polygon(polygon);
- tile_data->set_occluder(0, occluder);
+ tile_data->add_occluder_polygon(0);
+ tile_data->set_occluder_polygon(0, 0, occluder);
}
if (ctd->navigation.is_valid()) {
if (get_navigation_layers_count() < 1) {
@@ -3558,7 +3559,8 @@ void TileSet::_compatibility_conversion() {
polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0);
}
occluder->set_polygon(polygon);
- tile_data->set_occluder(0, occluder);
+ tile_data->add_occluder_polygon(0);
+ tile_data->set_occluder_polygon(0, 0, occluder);
}
if (ctd->autotile_navpoly_map.has(coords)) {
if (get_navigation_layers_count() < 1) {
@@ -6220,33 +6222,86 @@ int TileData::get_y_sort_origin() const {
return y_sort_origin;
}
+#ifndef DISABLE_DEPRECATED
void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) {
ERR_FAIL_INDEX(p_layer_id, occluders.size());
- occluders.write[p_layer_id].occluder = p_occluder_polygon;
- occluders.write[p_layer_id].transformed_occluders.clear();
+ if (get_occluder_polygons_count(p_layer_id) == 0) {
+ add_occluder_polygon(p_layer_id);
+ }
+ set_occluder_polygon(p_layer_id, 0, p_occluder_polygon);
emit_signal(CoreStringName(changed));
}
Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+ if (get_occluder_polygons_count(p_layer_id) == 0) {
+ return Ref<OccluderPolygon2D>();
+ }
+ return get_occluder_polygon(p_layer_id, 0, p_flip_h, p_flip_v, p_transpose);
+}
+#endif // DISABLE_DEPRECATED
+
+void TileData::set_occluder_polygons_count(int p_layer_id, int p_polygons_count) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_COND(p_polygons_count < 0);
+ if (p_polygons_count == occluders.write[p_layer_id].polygons.size()) {
+ return;
+ }
+ occluders.write[p_layer_id].polygons.resize(p_polygons_count);
+ notify_property_list_changed();
+ emit_signal(CoreStringName(changed));
+}
+
+int TileData::get_occluder_polygons_count(int p_layer_id) const {
+ ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), 0);
+ return occluders[p_layer_id].polygons.size();
+}
+
+void TileData::add_occluder_polygon(int p_layer_id) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ occluders.write[p_layer_id].polygons.push_back(OcclusionLayerTileData::PolygonOccluderTileData());
+ emit_signal(CoreStringName(changed));
+}
+
+void TileData::remove_occluder_polygon(int p_layer_id, int p_polygon_index) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+ occluders.write[p_layer_id].polygons.remove_at(p_polygon_index);
+ emit_signal(CoreStringName(changed));
+}
+
+void TileData::set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon) {
+ ERR_FAIL_INDEX(p_layer_id, occluders.size());
+ ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size());
+
+ OcclusionLayerTileData::PolygonOccluderTileData &polygon_occluder_tile_data = occluders.write[p_layer_id].polygons.write[p_polygon_index];
+ polygon_occluder_tile_data.occluder_polygon = p_occluder_polygon;
+ polygon_occluder_tile_data.transformed_polygon_occluders.clear();
+ emit_signal(CoreStringName(changed));
+}
+
+Ref<OccluderPolygon2D> TileData::get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const {
+ ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>());
+ ERR_FAIL_INDEX_V(p_polygon_index, occluders[p_layer_id].polygons.size(), Ref<OccluderPolygon2D>());
const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id];
+ const Ref<OccluderPolygon2D> &occluder_polygon = layer_tile_data.polygons[p_polygon_index].occluder_polygon;
int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2;
if (key == 0) {
- return layer_tile_data.occluder;
+ return occluder_polygon;
}
- if (layer_tile_data.occluder.is_null()) {
+ if (occluder_polygon.is_null()) {
return Ref<OccluderPolygon2D>();
}
- HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key);
+ HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders.find(key);
if (!I) {
Ref<OccluderPolygon2D> transformed_polygon;
transformed_polygon.instantiate();
- transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose));
- layer_tile_data.transformed_occluders[key] = transformed_polygon;
+ transformed_polygon->set_polygon(get_transformed_vertices(occluder_polygon->get_polygon(), p_flip_h, p_flip_v, p_transpose));
+ layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders[key] = transformed_polygon;
return transformed_polygon;
} else {
return I->value;
@@ -6594,13 +6649,37 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
#endif
Vector<String> components = String(p_name).split("/", true, 2);
-
- if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
- if (components[1] == "polygon") {
- Ref<OccluderPolygon2D> polygon = p_value;
+ if (components.size() == 2) {
+ if (components[1] == "polygon") {
+ // Kept for compatibility.
+ Ref<OccluderPolygon2D> polygon = p_value;
+ if (layer_index >= occluders.size()) {
+ if (tile_set) {
+ return false;
+ } else {
+ occluders.resize(layer_index + 1);
+ }
+ }
+ if (get_occluder_polygons_count(layer_index) == 0) {
+ add_occluder_polygon(layer_index);
+ }
+ set_occluder_polygon(layer_index, 0, polygon);
+ return true;
+ } else if (components[1] == "polygons_count") {
+ if (p_value.get_type() != Variant::INT) {
+ return false;
+ }
+ set_occluder_polygons_count(layer_index, p_value);
+ return true;
+ }
+ } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
if (layer_index >= occluders.size()) {
if (tile_set) {
@@ -6609,8 +6688,16 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
occluders.resize(layer_index + 1);
}
}
- set_occluder(layer_index, polygon);
- return true;
+
+ if (polygon_index >= occluders[layer_index].polygons.size()) {
+ occluders.write[layer_index].polygons.resize(polygon_index + 1);
+ }
+
+ if (components[2] == "polygon") {
+ Ref<OccluderPolygon2D> polygon = p_value;
+ set_occluder_polygon(layer_index, polygon_index, polygon);
+ return true;
+ }
}
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
@@ -6638,6 +6725,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
} else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
int polygon_index = components[1].trim_prefix("polygon_").to_int();
ERR_FAIL_COND_V(polygon_index < 0, false);
@@ -6724,16 +6812,36 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const {
Vector<String> components = String(p_name).split("/", true, 2);
if (tile_set) {
- if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
+ if (components.size() >= 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) {
// Occlusion layers.
int layer_index = components[0].trim_prefix("occlusion_layer_").to_int();
ERR_FAIL_COND_V(layer_index < 0, false);
if (layer_index >= occluders.size()) {
return false;
}
- if (components[1] == "polygon") {
- r_ret = get_occluder(layer_index);
- return true;
+ if (components.size() == 2) {
+ if (components[1] == "polygon") {
+ // Kept for compatibility.
+ if (occluders[layer_index].polygons.is_empty()) {
+ return false;
+ }
+ r_ret = get_occluder_polygon(layer_index, 0);
+ return true;
+ } else if (components[1] == "polygons_count") {
+ r_ret = get_occluder_polygons_count(layer_index);
+ return true;
+ }
+ } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) {
+ // Polygons.
+ int polygon_index = components[1].trim_prefix("polygon_").to_int();
+ ERR_FAIL_COND_V(polygon_index < 0, false);
+ if (polygon_index >= occluders[layer_index].polygons.size()) {
+ return false;
+ }
+ if (components[2] == "polygon") {
+ r_ret = get_occluder_polygon(layer_index, polygon_index);
+ return true;
+ }
}
} else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) {
// Physics layers.
@@ -6813,12 +6921,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const {
// Occlusion layers.
p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP));
for (int i = 0; i < occluders.size(); i++) {
- // occlusion_layer_%d/polygon
- property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
- if (occluders[i].occluder.is_null()) {
- property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+ for (int j = 0; j < occluders[i].polygons.size(); j++) {
+ // occlusion_layer_%d/polygon_%d/polygon
+ property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon_%d/%s", i, j, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT);
+ if (occluders[i].polygons[j].occluder_polygon.is_null()) {
+ property_info.usage ^= PROPERTY_USAGE_STORAGE;
+ }
+ p_list->push_back(property_info);
}
- p_list->push_back(property_info);
}
// Physics layers.
@@ -6923,8 +7034,17 @@ void TileData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin);
ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin);
+ ClassDB::bind_method(D_METHOD("set_occluder_polygons_count", "layer_id", "polygons_count"), &TileData::set_occluder_polygons_count);
+ ClassDB::bind_method(D_METHOD("get_occluder_polygons_count", "layer_id"), &TileData::get_occluder_polygons_count);
+ ClassDB::bind_method(D_METHOD("add_occluder_polygon", "layer_id"), &TileData::add_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("remove_occluder_polygon", "layer_id", "polygon_index"), &TileData::remove_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("set_occluder_polygon", "layer_id", "polygon_index", "polygon"), &TileData::set_occluder_polygon);
+ ClassDB::bind_method(D_METHOD("get_occluder_polygon", "layer_id", "polygon_index", "flip_h", "flip_v", "transpose"), &TileData::get_occluder_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+
+#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder);
ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+#endif // DISABLE_DEPRECATED
// Physics.
ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity);
diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h
index 51df972c8d..931495d020 100644
--- a/scene/resources/2d/tile_set.h
+++ b/scene/resources/2d/tile_set.h
@@ -841,8 +841,11 @@ private:
int z_index = 0;
int y_sort_origin = 0;
struct OcclusionLayerTileData {
- Ref<OccluderPolygon2D> occluder;
- mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders;
+ struct PolygonOccluderTileData {
+ Ref<OccluderPolygon2D> occluder_polygon;
+ mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_polygon_occluders;
+ };
+ Vector<PolygonOccluderTileData> polygons;
};
Vector<OcclusionLayerTileData> occluders;
@@ -941,8 +944,17 @@ public:
void set_y_sort_origin(int p_y_sort_origin);
int get_y_sort_origin() const;
+#ifndef DISABLE_DEPRECATED
void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon);
Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
+#endif // DISABLE_DEPRECATED
+
+ void set_occluder_polygons_count(int p_layer_id, int p_polygons_count);
+ int get_occluder_polygons_count(int p_layer_id) const;
+ void add_occluder_polygon(int p_layer_id);
+ void remove_occluder_polygon(int p_layer_id, int p_polygon_index);
+ void set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon);
+ Ref<OccluderPolygon2D> get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const;
// Physics
void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity);
diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp
index 5e4f1970ee..92246b50db 100644
--- a/scene/resources/3d/fog_material.cpp
+++ b/scene/resources/3d/fog_material.cpp
@@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() {
}
void FogMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader.is_null()) {
shader = RS::get_singleton()->shader_create();
@@ -165,7 +165,6 @@ void fog() {
}
)");
}
- shader_mutex.unlock();
}
FogMaterial::FogMaterial() {
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
index c55e25fcae..59366592ce 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
@@ -223,7 +223,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f
}
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
- ERR_FAIL_NULL(p_other_geometry);
+ ERR_FAIL_COND(p_other_geometry.is_null());
Vector<float> other_vertices;
Vector<int> other_indices;
diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp
index 640261d615..c470db5d7f 100644
--- a/scene/resources/3d/sky_material.cpp
+++ b/scene/resources/3d/sky_material.cpp
@@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() {
}
void ProceduralSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -354,7 +354,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
- shader_mutex.unlock();
}
ProceduralSkyMaterial::ProceduralSkyMaterial() {
@@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() {
}
void PanoramaSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -484,8 +483,6 @@ void sky() {
i ? "filter_linear" : "filter_nearest"));
}
}
-
- shader_mutex.unlock();
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
@@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() {
}
void PhysicalSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -785,8 +782,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
-
- shader_mutex.unlock();
}
PhysicalSkyMaterial::PhysicalSkyMaterial() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 9e6d34959a..0c29790ea4 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,7 +45,7 @@ public:
static inline String PARAMETERS_BASE_PATH = "parameters/";
- enum TrackType {
+ enum TrackType : uint8_t {
TYPE_VALUE, // Set a value in a property, can be interpolated.
TYPE_POSITION_3D, // Position 3D track, can be compressed.
TYPE_ROTATION_3D, // Rotation 3D track, can be compressed.
@@ -57,7 +57,7 @@ public:
TYPE_ANIMATION,
};
- enum InterpolationType {
+ enum InterpolationType : uint8_t {
INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR,
INTERPOLATION_CUBIC,
@@ -65,26 +65,26 @@ public:
INTERPOLATION_CUBIC_ANGLE,
};
- enum UpdateMode {
+ enum UpdateMode : uint8_t {
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_CAPTURE,
};
- enum LoopMode {
+ enum LoopMode : uint8_t {
LOOP_NONE,
LOOP_LINEAR,
LOOP_PINGPONG,
};
// LoopedFlag is used in Animataion to "process the keys at both ends correct".
- enum LoopedFlag {
+ enum LoopedFlag : uint8_t {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
LOOPED_FLAG_START,
};
- enum FindMode {
+ enum FindMode : uint8_t {
FIND_MODE_NEAREST,
FIND_MODE_APPROX,
FIND_MODE_EXACT,
@@ -104,7 +104,6 @@ public:
};
#endif // TOOLS_ENABLED
-private:
struct Track {
TrackType type = TrackType::TYPE_ANIMATION;
InterpolationType interpolation = INTERPOLATION_LINEAR;
@@ -117,6 +116,7 @@ private:
virtual ~Track() {}
};
+private:
struct Key {
real_t transition = 1.0;
double time = 0.0; // Time in secs.
@@ -396,6 +396,10 @@ public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
+ _FORCE_INLINE_ const Vector<Track *> get_tracks() {
+ return tracks;
+ }
+
bool is_capture_included() const;
int get_track_count() const;
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index de6a069567..08ebacc2b3 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -179,17 +179,17 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
for (int i = 0; i < 2; i++) {
// Sign operations prevent triple decoding on backward loops, maxing prevents pop.
- uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1);
+ uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1);
uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
if (p_qoa->data_ofs != new_data_ofs) {
p_qoa->data_ofs = new_data_ofs;
const uint8_t *src_ptr = (const uint8_t *)base->data;
src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD;
- qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
+ qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
}
- uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels;
+ uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels;
if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
final = p_qoa->dec[dec_idx];
@@ -286,7 +286,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- len = qoa.desc->samples * qoa.desc->channels;
+ len = qoa.desc.samples * qoa.desc.channels;
break;
}
@@ -484,10 +484,6 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback>
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
- if (qoa.desc) {
- memfree(qoa.desc);
- }
-
if (qoa.dec) {
memfree(qoa.dec);
}
@@ -557,7 +553,7 @@ double AudioStreamWAV::get_length() const {
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
+ qoa_desc desc = {};
qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc);
len = desc.samples * desc.channels;
break;
@@ -697,12 +693,11 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
sample->base = Ref<AudioStreamWAV>(this);
if (format == AudioStreamWAV::FORMAT_QOA) {
- sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc));
- uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc);
+ uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &sample->qoa.desc);
ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>());
- sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc);
- int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples);
- int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t);
+ sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc);
+ int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples);
+ int alloc_len = sample->qoa.desc.channels * samples_len * sizeof(int16_t);
sample->qoa.dec = (int16_t *)memalloc(alloc_len);
}
@@ -765,7 +760,7 @@ void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index 806db675b6..47aa10e790 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -59,7 +59,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
} ima_adpcm[2];
struct QOA_State {
- qoa_desc *desc = nullptr;
+ qoa_desc desc = {};
uint32_t data_ofs = 0;
uint32_t frame_len = 0;
int16_t *dec = nullptr;
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index d07dee6674..927e76e4b2 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -37,7 +37,7 @@
#include "scene/main/scene_tree.h"
void Material::set_next_pass(const Ref<Material> &p_pass) {
- for (Ref<Material> pass_child = p_pass; pass_child != nullptr; pass_child = pass_child->get_next_pass()) {
+ for (Ref<Material> pass_child = p_pass; pass_child.is_valid(); pass_child = pass_child->get_next_pass()) {
ERR_FAIL_COND_MSG(pass_child == this, "Can't set as next_pass one of its parents to prevent crashes due to recursive loop.");
}
diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp
index 8b5e438aea..22e2e9138f 100644
--- a/scene/resources/mesh.cpp
+++ b/scene/resources/mesh.cpp
@@ -2251,6 +2251,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo
}
void ArrayMesh::set_shadow_mesh(const Ref<ArrayMesh> &p_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == this, "Cannot set a mesh as its own shadow mesh.");
shadow_mesh = p_mesh;
if (shadow_mesh.is_valid()) {
RS::get_singleton()->mesh_set_shadow_mesh(mesh, shadow_mesh->get_rid());
diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h
index 741cea0791..1b3db5bac2 100644
--- a/scene/resources/navigation_mesh.h
+++ b/scene/resources/navigation_mesh.h
@@ -33,6 +33,7 @@
#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationMesh : public Resource {
GDCLASS(NavigationMesh, Resource);
@@ -77,8 +78,8 @@ public:
};
protected:
- float cell_size = 0.25f; // Must match ProjectSettings default 3D cell_size and NavigationServer NavMap cell_size.
- float cell_height = 0.25f; // Must match ProjectSettings default 3D cell_height and NavigationServer NavMap cell_height.
+ float cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float cell_height = NavigationDefaults3D::navmesh_cell_height;
float border_size = 0.0f;
float agent_height = 1.5f;
float agent_radius = 0.5f;
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index ee986f5820..8cfe4c92b7 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -110,6 +110,7 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->emission_ring_height = "emission_ring_height";
shader_names->emission_ring_radius = "emission_ring_radius";
shader_names->emission_ring_inner_radius = "emission_ring_inner_radius";
+ shader_names->emission_ring_cone_angle = "emission_ring_cone_angle";
shader_names->emission_shape_offset = "emission_shape_offset";
shader_names->emission_shape_scale = "emission_shape_scale";
@@ -269,6 +270,7 @@ void ParticleProcessMaterial::_update_shader() {
code += "uniform float " + shader_names->emission_ring_height + ";\n";
code += "uniform float " + shader_names->emission_ring_radius + ";\n";
code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n";
+ code += "uniform float " + shader_names->emission_ring_cone_angle + ";\n";
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -643,8 +645,14 @@ void ParticleProcessMaterial::_update_shader() {
code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
}
if (emission_shape == EMISSION_SHAPE_RING) {
+ code += " float radius_clamped = max(0.001, emission_ring_radius);\n";
+ code += " float top_radius = max(radius_clamped - tan(radians(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);\n";
+ code += " float y_pos = rand_from_seed(alt_seed);\n";
+ code += " float skew = max(min(radius_clamped, top_radius) / max(radius_clamped, top_radius), 0.5);\n";
+ code += " y_pos = radius_clamped < top_radius ? pow(y_pos, skew) : 1.0 - pow(y_pos, skew);\n";
code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " ring_random_radius = mix(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);\n";
code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n";
code += " vec3 ortho_axis = vec3(0.0);\n";
code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n";
@@ -662,7 +670,7 @@ void ParticleProcessMaterial::_update_shader() {
code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n";
code += " ) * ortho_axis;\n";
code += " ortho_axis = normalize(ortho_axis);\n";
- code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
+ code += " pos = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
}
code += " }\n";
code += " return pos * emission_shape_scale + emission_shape_offset;\n";
@@ -1615,6 +1623,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius);
}
+void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle);
+}
+
void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) {
inherit_emitter_velocity_ratio = p_ratio;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio);
@@ -1664,6 +1677,10 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) {
emission_shape_offset = p_emission_shape_offset;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset);
@@ -2015,6 +2032,9 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &ParticleProcessMaterial::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &ParticleProcessMaterial::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset);
ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset);
@@ -2096,9 +2116,10 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture");
ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_SUBGROUP("Angle", "");
ADD_MIN_MAX_PROPERTY("angle", "-720,720,0.1,or_less,or_greater,degrees", PARAM_ANGLE);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE);
@@ -2276,6 +2297,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_emission_shape_offset(Vector3(0.0, 0.0, 0.0));
set_emission_shape_scale(Vector3(1.0, 1.0, 1.0));
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 25046b51cd..12e3fbb64e 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -259,6 +259,7 @@ private:
StringName emission_ring_height;
StringName emission_ring_radius;
StringName emission_ring_inner_radius;
+ StringName emission_ring_cone_angle;
StringName emission_shape_offset;
StringName emission_shape_scale;
@@ -325,6 +326,7 @@ private:
real_t emission_ring_height = 0.0f;
real_t emission_ring_radius = 0.0f;
real_t emission_ring_inner_radius = 0.0f;
+ real_t emission_ring_cone_angle = 0.0f;
int emission_point_count = 1;
Vector3 emission_shape_offset;
Vector3 emission_shape_scale;
@@ -417,6 +419,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_emission_point_count(int p_count);
EmissionShape get_emission_shape() const;
@@ -429,6 +432,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
int get_emission_point_count() const;
void set_turbulence_enabled(bool p_turbulence_enabled);
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 90102e44e4..e9df20d9db 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -35,17 +35,12 @@
#include "core/io/missing_resource.h"
#include "core/object/script_language.h"
-// Version 2: Changed names for Basis, AABB, Vectors, etc.
-// Version 3: New string ID for ext/subresources, breaks forward compat.
-// Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added.
-#define FORMAT_VERSION 4
-// For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray.
-#define FORMAT_VERSION_COMPAT 3
-
-#define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data());
-
///
+void ResourceLoaderText::_printerr() {
+ ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data());
+}
+
Ref<Resource> ResourceLoaderText::get_resource() {
return resource;
}
@@ -1734,7 +1729,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (load_steps > 1) {
title += "load_steps=" + itos(load_steps) + " ";
}
- title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + "";
+ title += "format=" + itos(use_compat ? ResourceLoaderText::FORMAT_VERSION_COMPAT : ResourceLoaderText::FORMAT_VERSION) + "";
ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true);
diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h
index b5542f77ba..8397bc985f 100644
--- a/scene/resources/resource_format_text.h
+++ b/scene/resources/resource_format_text.h
@@ -38,6 +38,17 @@
#include "scene/resources/packed_scene.h"
class ResourceLoaderText {
+public:
+ enum {
+ // Version 2: Changed names for Basis, AABB, Vectors, etc.
+ // Version 3: New string ID for ext/subresources, breaks forward compat.
+ // Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added.
+ FORMAT_VERSION = 4,
+ // For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray.
+ FORMAT_VERSION_COMPAT = 3,
+ };
+
+private:
bool translation_remapped = false;
String local_path;
String res_path;
@@ -100,6 +111,7 @@ class ResourceLoaderText {
static Error _parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str);
static Error _parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str);
+ void _printerr();
VariantParser::ResourceParser rp;
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index f343229cd8..46d38146a6 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -158,7 +158,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr
#ifdef MODULE_REGEX_ENABLED
const RegEx pattern("/\\*\\*\\s([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/\\s*uniform\\s+\\w+\\s+" + pi.name + "(?=[\\s:;=])");
Ref<RegExMatch> pattern_ref = pattern.search(code);
- if (pattern_ref != nullptr) {
+ if (pattern_ref.is_valid()) {
RegExMatch *match = pattern_ref.ptr();
const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/");
Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0));
diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp
index 6e43ea9b17..dac0ceaa78 100644
--- a/scene/resources/sprite_frames.cpp
+++ b/scene/resources/sprite_frames.cpp
@@ -106,6 +106,12 @@ bool SpriteFrames::has_animation(const StringName &p_anim) const {
return animations.has(p_anim);
}
+void SpriteFrames::duplicate_animation(const StringName &p_from, const StringName &p_to) {
+ ERR_FAIL_COND_MSG(!animations.has(p_from), vformat("SpriteFrames doesn't have animation '%s'.", p_from));
+ ERR_FAIL_COND_MSG(animations.has(p_to), vformat("Animation '%s' already exists.", p_to));
+ animations[p_to] = animations[p_from];
+}
+
void SpriteFrames::remove_animation(const StringName &p_anim) {
animations.erase(p_anim);
}
@@ -246,6 +252,7 @@ void SpriteFrames::get_argument_options(const StringName &p_function, int p_idx,
void SpriteFrames::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation);
ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation);
+ ClassDB::bind_method(D_METHOD("duplicate_animation", "anim_from", "anim_to"), &SpriteFrames::duplicate_animation);
ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation);
ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation);
diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h
index 0e0bb28d71..8d5b4232cf 100644
--- a/scene/resources/sprite_frames.h
+++ b/scene/resources/sprite_frames.h
@@ -60,6 +60,7 @@ protected:
public:
void add_animation(const StringName &p_anim);
bool has_animation(const StringName &p_anim) const;
+ void duplicate_animation(const StringName &p_from, const StringName &p_to);
void remove_animation(const StringName &p_anim);
void rename_animation(const StringName &p_prev, const StringName &p_next);
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 3273bc3c00..6921885ee0 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -792,7 +792,7 @@ void SurfaceTool::deindex() {
}
void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh");
Array arr = p_existing->surface_get_arrays(p_surface);
ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX);
@@ -968,7 +968,7 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) {
}
void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from() must be a valid object of type Mesh");
clear();
primitive = p_existing->surface_get_primitive_type(p_surface);
@@ -983,7 +983,7 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) {
}
void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh");
clear();
primitive = p_existing->surface_get_primitive_type(p_surface);
@@ -1023,7 +1023,7 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur
}
void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) {
- ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::append_from() must be a valid object of type Mesh");
+ ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::append_from() must be a valid object of type Mesh");
if (vertex_array.size() == 0) {
primitive = p_existing->surface_get_primitive_type(p_surface);
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
index e30a8fa99e..6f358d54bb 100644
--- a/servers/audio/effects/audio_effect_record.cpp
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -149,7 +149,7 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
ensure_thread_stopped();
bool is_currently_recording = false;
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
is_currently_recording = current_instance->is_recording;
}
if (is_currently_recording) {
@@ -161,28 +161,28 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() {
}
void AudioEffectRecord::ensure_thread_stopped() {
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
current_instance->finish();
}
}
void AudioEffectRecord::set_recording_active(bool p_record) {
if (p_record) {
- if (current_instance == nullptr) {
+ if (current_instance.is_null()) {
WARN_PRINT("Recording should not be set as active before Godot has initialized.");
return;
}
ensure_thread_stopped();
current_instance->init();
} else {
- if (current_instance != nullptr) {
+ if (current_instance.is_valid()) {
current_instance->is_recording = false;
}
}
}
bool AudioEffectRecord::is_recording_active() const {
- if (current_instance == nullptr) {
+ if (current_instance.is_null()) {
return false;
} else {
return current_instance->is_recording;
@@ -283,7 +283,7 @@ void AudioEffectRecord::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
}
AudioEffectRecord::AudioEffectRecord() {
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 8cb3e560ac..12456fc828 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -41,6 +41,9 @@
#if defined(D3D12_ENABLED)
#include "drivers/d3d12/rendering_context_driver_d3d12.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
DisplayServer *DisplayServer::singleton = nullptr;
@@ -1232,6 +1235,15 @@ bool DisplayServer::can_create_rendering_device() {
rcd = memnew(RenderingContextDriverD3D12);
}
#endif
+#ifdef METAL_ENABLED
+ if (rcd == nullptr) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+ // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer".
+ rcd = memnew(RenderingContextDriverMetal);
+#pragma clang diagnostic pop
+ }
+#endif
if (rcd != nullptr) {
err = rcd->initialize();
diff --git a/servers/navigation/navigation_globals.h b/servers/navigation/navigation_globals.h
new file mode 100644
index 0000000000..aa54f95519
--- /dev/null
+++ b/servers/navigation/navigation_globals.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* navigation_globals.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 NAVIGATION_GLOBALS_H
+#define NAVIGATION_GLOBALS_H
+
+namespace NavigationDefaults3D {
+
+// Rasterization.
+
+// To find the polygons edges the vertices are displaced in a grid where
+// each cell has the following cell_size and cell_height.
+constexpr float navmesh_cell_size{ 0.25f }; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
+constexpr float navmesh_cell_height{ 0.25f }; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 0.25f };
+constexpr float link_connection_radius{ 1.0f };
+
+} //namespace NavigationDefaults3D
+
+namespace NavigationDefaults2D {
+
+// Rasterization.
+
+// Same as in 3D but larger since 1px is treated as 1m.
+constexpr float navmesh_cell_size{ 1.0f }; // Must match ProjectSettings default 2D cell_size.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 1.0f };
+constexpr float link_connection_radius{ 4.0f };
+
+} //namespace NavigationDefaults2D
+
+#endif // NAVIGATION_GLOBALS_H
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 398ff1e1f3..f4ffcf5a3e 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "scene/main/node.h"
+#include "servers/navigation/navigation_globals.h"
NavigationServer3D *NavigationServer3D::singleton = nullptr;
@@ -227,18 +228,18 @@ NavigationServer3D::NavigationServer3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 1.0);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults2D::navmesh_cell_size_hint), NavigationDefaults2D::navmesh_cell_size);
GLOBAL_DEF("navigation/2d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1.0);
- GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4.0);
+ GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", NavigationDefaults2D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", NavigationDefaults2D::link_connection_radius);
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", 0.25);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults3D::navmesh_cell_size_hint), NavigationDefaults3D::navmesh_cell_size);
+ GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", NavigationDefaults3D::navmesh_cell_height);
GLOBAL_DEF("navigation/3d/default_up", Vector3(0, 1, 0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "navigation/3d/merge_rasterizer_cell_scale", PROPERTY_HINT_RANGE, "0.001,1,0.001,or_greater"), 1.0);
GLOBAL_DEF("navigation/3d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0);
+ GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", NavigationDefaults3D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", NavigationDefaults3D::link_connection_radius);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true);
diff --git a/servers/rendering/renderer_rd/effects/taa.cpp b/servers/rendering/renderer_rd/effects/taa.cpp
index c1037ec11a..4ac5552aa6 100644
--- a/servers/rendering/renderer_rd/effects/taa.cpp
+++ b/servers/rendering/renderer_rd/effects/taa.cpp
@@ -62,8 +62,8 @@ void TAA::resolve(RID p_frame, RID p_temp, RID p_depth, RID p_velocity, RID p_pr
memset(&push_constant, 0, sizeof(TAAResolvePushConstant));
push_constant.resolution_width = p_resolution.width;
push_constant.resolution_height = p_resolution.height;
- push_constant.disocclusion_threshold = 0.025f;
- push_constant.disocclusion_scale = 10.0f;
+ push_constant.disocclusion_threshold = 2.5f; // If velocity changes by less than this amount of texels we can retain the accumulation buffer.
+ push_constant.disocclusion_scale = 0.01f; // Scale the weight of this pixel calculated as (change in velocity - threshold) * scale.
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipeline);
diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp
index 5dc67725f1..7d4ce6888f 100644
--- a/servers/rendering/renderer_rd/environment/sky.cpp
+++ b/servers/rendering/renderer_rd/environment/sky.cpp
@@ -545,7 +545,6 @@ void SkyRD::Sky::free() {
}
if (material.is_valid()) {
- RSG::material_storage->material_free(material);
material = RID();
}
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index b97e38da4d..36bd22b723 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -2231,7 +2231,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
}
RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
_render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
}
diff --git a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
index e50397cc5d..00ac6a7919 100644
--- a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl
@@ -307,6 +307,8 @@ float luminance(vec3 color) {
return max(dot(color, lumCoeff), 0.0001f);
}
+// This is "velocity disocclusion" as described by https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/.
+// We use texel space, so our scale and threshold differ.
float get_factor_disocclusion(vec2 uv_reprojected, vec2 velocity) {
vec2 velocity_previous = imageLoad(last_velocity_buffer, ivec2(uv_reprojected * params.resolution)).xy;
vec2 velocity_texels = velocity * params.resolution;
@@ -336,7 +338,7 @@ vec3 temporal_antialiasing(uvec2 pos_group_top_left, uvec2 pos_group, uvec2 pos_
// Compute blend factor
float blend_factor = RPC_16; // We want to be able to accumulate as many jitter samples as we generated, that is, 16.
{
- // If re-projected UV is out of screen, converge to current color immediatel
+ // If re-projected UV is out of screen, converge to current color immediately.
float factor_screen = any(lessThan(uv_reprojected, vec2(0.0))) || any(greaterThan(uv_reprojected, vec2(1.0))) ? 1.0 : 0.0;
// Increase blend factor when there is disocclusion (fixes a lot of the remaining ghosting).
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index 35d29fed6a..aafb9b4764 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -2065,7 +2065,7 @@ void fragment_shader(in SceneData scene_data) {
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth;
-
+#ifndef SHADOWS_DISABLED
if (directional_lights.data[i].shadow_opacity > 0.001) {
float depth_z = -vertex.z;
@@ -2112,7 +2112,8 @@ void fragment_shader(in SceneData scene_data) {
transmittance_z = z - shadow_z;
}
}
-#endif
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
float shadow = 1.0;
#ifndef SHADOWS_DISABLED
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
index 40ca74ae07..748fb59531 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -582,34 +582,39 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth; //no transmittance by default
transmittance_color.a *= light_attenuation;
- {
- vec4 clamp_rect = omni_lights.data[idx].atlas_rect;
+#ifndef SHADOWS_DISABLED
+ if (omni_lights.data[idx].shadow_opacity > 0.001) {
+ // Redo shadowmapping, but shrink the model a bit to avoid artifacts.
+ vec2 texel_size = scene_data_block.data.shadow_atlas_pixel_size;
+ vec4 uv_rect = omni_lights.data[idx].atlas_rect;
+ uv_rect.xy += texel_size;
+ uv_rect.zw -= texel_size * 2.0;
- //redo shadowmapping, but shrink the model a bit to avoid artifacts
- vec4 splane = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * omni_lights.data[idx].transmittance_bias, 1.0));
+ // Omni lights use direction.xy to store to store the offset between the two paraboloid regions
+ vec2 flip_offset = omni_lights.data[idx].direction.xy;
- float shadow_len = length(splane.xyz);
- splane.xyz = normalize(splane.xyz);
+ vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * omni_lights.data[idx].transmittance_bias, 1.0)).xyz;
- if (splane.z >= 0.0) {
- splane.z += 1.0;
- clamp_rect.y += clamp_rect.w;
- } else {
- splane.z = 1.0 - splane.z;
- }
+ float shadow_len = length(local_vert); //need to remember shadow len from here
+ vec3 shadow_sample = normalize(local_vert);
- splane.xy /= splane.z;
+ if (shadow_sample.z >= 0.0) {
+ uv_rect.xy += flip_offset;
+ flip_offset *= -1.0;
+ }
- splane.xy = splane.xy * 0.5 + 0.5;
- splane.z = shadow_len * omni_lights.data[idx].inv_radius;
- splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw;
- // splane.xy = clamp(splane.xy,clamp_rect.xy + scene_data_block.data.shadow_atlas_pixel_size,clamp_rect.xy + clamp_rect.zw - scene_data_block.data.shadow_atlas_pixel_size );
- splane.w = 1.0; //needed? i think it should be 1 already
+ shadow_sample.z = 1.0 + abs(shadow_sample.z);
+ vec2 pos = shadow_sample.xy / shadow_sample.z;
+ float depth = shadow_len * omni_lights.data[idx].inv_radius;
+ depth = 1.0 - depth;
- float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r;
- transmittance_z = (splane.z - shadow_z) / omni_lights.data[idx].inv_radius;
+ pos = pos * 0.5 + 0.5;
+ pos = uv_rect.xy + pos * uv_rect.zw;
+ float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), pos, 0.0).r;
+ transmittance_z = (depth - shadow_z) / omni_lights.data[idx].inv_radius;
}
-#endif
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
if (sc_use_light_projector && omni_lights.data[idx].projector_rect != vec4(0.0)) {
vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz;
@@ -834,12 +839,13 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
#ifdef LIGHT_TRANSMITTANCE_USED
float transmittance_z = transmittance_depth;
transmittance_color.a *= light_attenuation;
- {
- vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * spot_lights.data[idx].transmittance_bias, 1.0));
+#ifndef SHADOWS_DISABLED
+ if (spot_lights.data[idx].shadow_opacity > 0.001) {
+ vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * spot_lights.data[idx].transmittance_bias, 1.0));
splane /= splane.w;
- splane.xy = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy;
- float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r;
+ vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z);
+ float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), shadow_uv.xy, 0.0).r;
shadow_z = shadow_z * 2.0 - 1.0;
float z_far = 1.0 / spot_lights.data[idx].inv_radius;
@@ -850,7 +856,8 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v
float z = dot(spot_dir, -light_rel_vec);
transmittance_z = z - shadow_z;
}
-#endif //LIGHT_TRANSMITTANCE_USED
+#endif // !SHADOWS_DISABLED
+#endif // LIGHT_TRANSMITTANCE_USED
if (sc_use_light_projector && spot_lights.data[idx].projector_rect != vec4(0.0)) {
vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0));
@@ -933,7 +940,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal,
vec4 reflection;
- reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier;
+ reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier;
reflection.rgb *= reflections.data[ref_index].exposure_normalization;
if (reflections.data[ref_index].exterior) {
reflection.rgb = mix(specular_light, reflection.rgb, blend);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index c217c0fa9a..b07063cfda 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -685,7 +685,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged
float bias_scale = light_instance->shadow_transform[j].bias_scale * light_data.soft_shadow_scale;
light_data.shadow_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_BIAS] / 100.0 * bias_scale;
light_data.shadow_normal_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] * light_instance->shadow_transform[j].shadow_texel_size;
- light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] * bias_scale;
+ light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] / 100.0 * bias_scale;
light_data.shadow_z_range[j] = light_instance->shadow_transform[j].farplane;
light_data.shadow_range_begin[j] = light_instance->shadow_transform[j].range_begin;
RendererRD::MaterialStorage::store_camera(shadow_mtx, light_data.shadow_matrices[j]);
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 94ab219dc2..1db58d72f9 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -49,7 +49,7 @@ namespace RendererRD {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 539bdcbbd0..9bd62ba065 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -783,6 +783,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const {
}
void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) {
+ ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh.");
Mesh *mesh = mesh_owner.get_or_null(p_mesh);
ERR_FAIL_NULL(mesh);
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index 6e5e8f63e0..be29716f45 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -1470,8 +1470,24 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->width;
tinfo.height = t->height;
- tinfo.depth = t->depth;
- tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps);
+ tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps > 1);
+
+ switch (t->type) {
+ case TextureType::TYPE_3D:
+ tinfo.depth = t->depth;
+ tinfo.bytes *= t->depth;
+ break;
+
+ case TextureType::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ tinfo.bytes *= t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 1d25dec633..286944641c 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -1783,6 +1783,8 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
if (p_instance->scenario) {
RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, *instance_xform, p_instance->visible);
}
+ } else if (p_instance->base_type == RS::INSTANCE_NONE) {
+ return;
}
if (!p_instance->aabb.has_surface()) {
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index d8f9e2c31a..1405f585b2 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -377,7 +377,9 @@ public:
// used for the render pipelines.
struct AttachmentFormat {
- enum { UNUSED_ATTACHMENT = 0xFFFFFFFF };
+ enum : uint32_t {
+ UNUSED_ATTACHMENT = 0xFFFFFFFF
+ };
DataFormat format;
TextureSamples samples;
uint32_t usage_flags;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 92f0f0dbc0..1848d5602e 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3263,6 +3263,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_z_index", "item", "z_index"), &RenderingServer::canvas_item_set_z_index);
ClassDB::bind_method(D_METHOD("canvas_item_set_z_as_relative_to_parent", "item", "enabled"), &RenderingServer::canvas_item_set_z_as_relative_to_parent);
ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &RenderingServer::canvas_item_set_copy_to_backbuffer);
+ ClassDB::bind_method(D_METHOD("canvas_item_attach_skeleton", "item", "skeleton"), &RenderingServer::canvas_item_attach_skeleton);
ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &RenderingServer::canvas_item_clear);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index d8b6651833..62ca6b3b6d 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -176,7 +176,7 @@ public:
uint32_t height;
uint32_t depth;
Image::Format format;
- int bytes;
+ int64_t bytes;
String path;
};
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 8a53b919d4..d2cf4674ae 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -2071,8 +2071,8 @@ TypedArray<Vector3i> TextServer::parse_structured_text(StructuredTextParser p_pa
if (prev != i) {
ret.push_back(Vector3i(prev, i, TextServer::DIRECTION_AUTO));
}
- prev = i + 1;
- ret.push_back(Vector3i(i, i + 1, TextServer::DIRECTION_LTR));
+ prev = p_text.length();
+ ret.push_back(Vector3i(i, p_text.length(), TextServer::DIRECTION_AUTO));
break;
}
}
diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp
index a4e68afee0..4c4781fdef 100644
--- a/servers/xr_server.cpp
+++ b/servers/xr_server.cpp
@@ -183,7 +183,7 @@ Transform3D XRServer::get_reference_frame() const {
}
void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) {
- if (primary_interface == nullptr) {
+ if (primary_interface.is_null()) {
return;
}
@@ -235,7 +235,7 @@ void XRServer::_set_render_reference_frame(const Transform3D &p_reference_frame)
Transform3D XRServer::get_hmd_transform() {
Transform3D hmd_transform;
- if (primary_interface != nullptr) {
+ if (primary_interface.is_valid()) {
hmd_transform = primary_interface->get_camera_transform();
}
return hmd_transform;
diff --git a/tests/core/io/test_http_client.h b/tests/core/io/test_http_client.h
index 961c653a0a..114ce3b4ed 100644
--- a/tests/core/io/test_http_client.h
+++ b/tests/core/io/test_http_client.h
@@ -41,7 +41,7 @@ namespace TestHTTPClient {
TEST_CASE("[HTTPClient] Instantiation") {
Ref<HTTPClient> client = HTTPClient::create();
- CHECK_MESSAGE(client != nullptr, "A HTTP Client created should not be a null pointer");
+ CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer");
}
TEST_CASE("[HTTPClient] query_string_from_dict") {
diff --git a/tests/core/io/test_json_native.h b/tests/core/io/test_json_native.h
new file mode 100644
index 0000000000..819078ac57
--- /dev/null
+++ b/tests/core/io/test_json_native.h
@@ -0,0 +1,160 @@
+/**************************************************************************/
+/* test_json_native.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_JSON_NATIVE_H
+#define TEST_JSON_NATIVE_H
+
+#include "core/io/json.h"
+
+namespace TestJSONNative {
+
+bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) {
+ if (depth > 100) {
+ return false;
+ }
+ if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) {
+ return true;
+ }
+ if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) {
+ return true;
+ }
+
+ List<PropertyInfo> variant_1_properties;
+ variant_1.get_property_list(&variant_1_properties);
+ List<PropertyInfo> variant_2_properties;
+ variant_2.get_property_list(&variant_2_properties);
+
+ if (variant_1_properties.size() != variant_2_properties.size()) {
+ return false;
+ }
+
+ for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) {
+ String name = E->get().name;
+ Variant variant_1_value = variant_1.get(name);
+ Variant variant_2_value = variant_2.get(name);
+
+ if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") {
+ for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) {
+ Variant::Type type = static_cast<Variant::Type>(variant_i);
+ Variant native_data;
+ Callable::CallError error;
+
+ if (type == Variant::Type::INT || type == Variant::Type::FLOAT) {
+ Variant value = int64_t(INT64_MAX);
+ const Variant *args[] = { &value };
+ Variant::construct(type, native_data, args, 1, error);
+ } else if (type == Variant::Type::OBJECT) {
+ Ref<JSON> json = memnew(JSON);
+ native_data = json;
+ } else if (type == Variant::Type::DICTIONARY) {
+ Dictionary dictionary;
+ dictionary["key"] = "value";
+ native_data = dictionary;
+ } else if (type == Variant::Type::ARRAY) {
+ Array array;
+ array.push_back("element1");
+ array.push_back("element2");
+ native_data = array;
+ } else if (type == Variant::Type::PACKED_BYTE_ARRAY) {
+ PackedByteArray packed_array;
+ packed_array.push_back(1);
+ packed_array.push_back(2);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT32_ARRAY) {
+ PackedInt32Array packed_array;
+ packed_array.push_back(INT32_MIN);
+ packed_array.push_back(INT32_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT64_ARRAY) {
+ PackedInt64Array packed_array;
+ packed_array.push_back(INT64_MIN);
+ packed_array.push_back(INT64_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) {
+ PackedFloat32Array packed_array;
+ packed_array.push_back(FLT_MIN);
+ packed_array.push_back(FLT_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) {
+ PackedFloat64Array packed_array;
+ packed_array.push_back(DBL_MIN);
+ packed_array.push_back(DBL_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_STRING_ARRAY) {
+ PackedStringArray packed_array;
+ packed_array.push_back("string1");
+ packed_array.push_back("string2");
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) {
+ PackedVector2Array packed_array;
+ Vector2 vector(1.0, 2.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) {
+ PackedVector3Array packed_array;
+ Vector3 vector(1.0, 2.0, 3.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_COLOR_ARRAY) {
+ PackedColorArray packed_array;
+ Color color(1.0, 1.0, 1.0);
+ packed_array.push_back(color);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) {
+ PackedVector4Array packed_array;
+ Vector4 vector(1.0, 2.0, 3.0, 4.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else {
+ Variant::construct(type, native_data, nullptr, 0, error);
+ }
+ Variant json_converted_from_native = JSON::from_native(native_data, true, true);
+ Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true);
+ CHECK_MESSAGE(compare_variants(native_data, variant_native_converted),
+ vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s",
+ Variant::get_type_name(type),
+ native_data,
+ variant_native_converted,
+ itos(error.error),
+ Variant::get_type_name(type),
+ json_converted_from_native));
+ }
+}
+} // namespace TestJSONNative
+
+#endif // TEST_JSON_NATIVE_H
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 57bc65328a..f1bb62cb70 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -174,6 +174,31 @@ TEST_CASE("[Object] Metadata") {
CHECK_MESSAGE(
meta_list2.size() == 0,
"The metadata list should contain 0 items after removing all metadata items.");
+
+ Object other;
+ object.set_meta("conflicting_meta", "string");
+ object.set_meta("not_conflicting_meta", 123);
+ other.set_meta("conflicting_meta", Color(0, 1, 0));
+ other.set_meta("other_meta", "other");
+ object.merge_meta_from(&other);
+
+ CHECK_MESSAGE(
+ Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
+ "String meta should be overwritten with Color after merging.");
+
+ CHECK_MESSAGE(
+ int(object.get_meta("not_conflicting_meta")) == 123,
+ "Not conflicting meta on destination should be kept intact.");
+
+ CHECK_MESSAGE(
+ object.get_meta("other_meta", String()) == "other",
+ "Not conflicting meta name on source should merged.");
+
+ List<StringName> meta_list3;
+ object.get_meta_list(&meta_list3);
+ CHECK_MESSAGE(
+ meta_list3.size() == 3,
+ "The metadata list should contain 3 items after merging meta from two objects.");
}
TEST_CASE("[Object] Construction") {
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index b47e5b1eb9..a9f615af84 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -433,6 +433,19 @@ TEST_CASE("[String] Insertion") {
String s = "Who is Frederic?";
s = s.insert(s.find("?"), " Chopin");
CHECK(s == "Who is Frederic Chopin?");
+
+ s = "foobar";
+ CHECK(s.insert(0, "X") == "Xfoobar");
+ CHECK(s.insert(-100, "X") == "foobar");
+ CHECK(s.insert(6, "X") == "foobarX");
+ CHECK(s.insert(100, "X") == "foobarX");
+ CHECK(s.insert(2, "") == "foobar");
+
+ s = "";
+ CHECK(s.insert(0, "abc") == "abc");
+ CHECK(s.insert(100, "abc") == "abc");
+ CHECK(s.insert(-100, "abc") == "");
+ CHECK(s.insert(0, "") == "");
}
TEST_CASE("[String] Erasing") {
@@ -1811,13 +1824,25 @@ TEST_CASE("[String] SHA1/SHA256/MD5") {
}
TEST_CASE("[String] Join") {
- String s = ", ";
+ String comma = ", ";
+ String empty = "";
Vector<String> parts;
+
+ CHECK(comma.join(parts) == "");
+ CHECK(empty.join(parts) == "");
+
parts.push_back("One");
+ CHECK(comma.join(parts) == "One");
+ CHECK(empty.join(parts) == "One");
+
parts.push_back("B");
parts.push_back("C");
- String t = s.join(parts);
- CHECK(t == "One, B, C");
+ CHECK(comma.join(parts) == "One, B, C");
+ CHECK(empty.join(parts) == "OneBC");
+
+ parts.push_back("");
+ CHECK(comma.join(parts) == "One, B, C, ");
+ CHECK(empty.join(parts) == "OneBC");
}
TEST_CASE("[String] Is_*") {
diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h
index e8f3c9e8f5..5166cd3c13 100644
--- a/tests/scene/test_audio_stream_wav.h
+++ b/tests/scene/test_audio_stream_wav.h
@@ -159,6 +159,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
for (const ResourceImporter::ImportOption &E : options_list) {
options_map[E.option.name] = E.default_value;
}
+ // Compressed streams can't be saved, disable compression.
+ options_map["compress/mode"] = 0;
REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK);
diff --git a/tests/scene/test_path_2d.h b/tests/scene/test_path_2d.h
index 7b6cec96db..4703bfa3bb 100644
--- a/tests/scene/test_path_2d.h
+++ b/tests/scene/test_path_2d.h
@@ -40,7 +40,7 @@ namespace TestPath2D {
TEST_CASE("[SceneTree][Path2D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path2D *test_path = memnew(Path2D);
- CHECK(test_path->get_curve() == nullptr);
+ CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}
diff --git a/tests/scene/test_path_3d.h b/tests/scene/test_path_3d.h
index f779f514a4..70c7099d48 100644
--- a/tests/scene/test_path_3d.h
+++ b/tests/scene/test_path_3d.h
@@ -40,7 +40,7 @@ namespace TestPath3D {
TEST_CASE("[Path3D] Initialization") {
SUBCASE("Path should be empty right after initialization") {
Path3D *test_path = memnew(Path3D);
- CHECK(test_path->get_curve() == nullptr);
+ CHECK(test_path->get_curve().is_null());
memdelete(test_path);
}
}
diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h
index f105e1ac04..59f23983e5 100644
--- a/tests/scene/test_primitives.h
+++ b/tests/scene/test_primitives.h
@@ -609,7 +609,7 @@ TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") {
CHECK(tube->get_sections() >= 0);
CHECK(tube->get_section_length() > 0);
CHECK(tube->get_section_rings() >= 0);
- CHECK(tube->get_curve() == nullptr);
+ CHECK(tube->get_curve().is_null());
CHECK(tube->get_builtin_bind_pose_count() >= 0);
}
@@ -669,7 +669,7 @@ TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") {
CHECK(ribbon->get_section_length() > 0);
CHECK(ribbon->get_section_segments() >= 0);
CHECK(ribbon->get_builtin_bind_pose_count() >= 0);
- CHECK(ribbon->get_curve() == nullptr);
+ CHECK(ribbon->get_curve().is_null());
CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS ||
ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT));
}
@@ -731,7 +731,7 @@ TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") {
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER ||
text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL));
- CHECK(text->get_font() == nullptr);
+ CHECK(text->get_font().is_null());
CHECK(text->get_font_size() > 0);
CHECK(text->get_line_spacing() >= 0);
CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF ||
diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h
index cf6b89c330..4411b1aae5 100644
--- a/tests/servers/test_navigation_server_3d.h
+++ b/tests/servers/test_navigation_server_3d.h
@@ -31,6 +31,7 @@
#ifndef TEST_NAVIGATION_SERVER_3D_H
#define TEST_NAVIGATION_SERVER_3D_H
+#include "modules/navigation/nav_utils.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/resources/3d/primitive_meshes.h"
#include "servers/navigation_server_3d.h"
@@ -61,6 +62,32 @@ static inline Array build_array(Variant item, Targs... Fargs) {
return a;
}
+struct GreaterThan {
+ bool operator()(int p_a, int p_b) const { return p_a > p_b; }
+};
+
+struct CompareArrayValues {
+ const int *array;
+
+ CompareArrayValues(const int *p_array) :
+ array(p_array) {}
+
+ bool operator()(uint32_t p_index_a, uint32_t p_index_b) const {
+ return array[p_index_a] < array[p_index_b];
+ }
+};
+
+struct RegisterHeapIndexes {
+ uint32_t *indexes;
+
+ RegisterHeapIndexes(uint32_t *p_indexes) :
+ indexes(p_indexes) {}
+
+ void operator()(uint32_t p_vector_index, uint32_t p_heap_index) {
+ indexes[p_vector_index] = p_heap_index;
+ }
+};
+
TEST_SUITE("[Navigation]") {
TEST_CASE("[NavigationServer3D] Server should be empty when initialized") {
NavigationServer3D *navigation_server = NavigationServer3D::get_singleton();
@@ -788,6 +815,139 @@ TEST_SUITE("[Navigation]") {
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
}
*/
+
+ TEST_CASE("[Heap] size") {
+ gd::Heap<int> heap;
+
+ CHECK(heap.size() == 0);
+
+ heap.push(0);
+ CHECK(heap.size() == 1);
+
+ heap.push(1);
+ CHECK(heap.size() == 2);
+
+ heap.pop();
+ CHECK(heap.size() == 1);
+
+ heap.pop();
+ CHECK(heap.size() == 0);
+ }
+
+ TEST_CASE("[Heap] is_empty") {
+ gd::Heap<int> heap;
+
+ CHECK(heap.is_empty() == true);
+
+ heap.push(0);
+ CHECK(heap.is_empty() == false);
+
+ heap.pop();
+ CHECK(heap.is_empty() == true);
+ }
+
+ TEST_CASE("[Heap] push/pop") {
+ SUBCASE("Default comparator") {
+ gd::Heap<int> heap;
+
+ heap.push(2);
+ heap.push(7);
+ heap.push(5);
+ heap.push(3);
+ heap.push(4);
+
+ CHECK(heap.pop() == 7);
+ CHECK(heap.pop() == 5);
+ CHECK(heap.pop() == 4);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 2);
+ }
+
+ SUBCASE("Custom comparator") {
+ GreaterThan greaterThan;
+ gd::Heap<int, GreaterThan> heap(greaterThan);
+
+ heap.push(2);
+ heap.push(7);
+ heap.push(5);
+ heap.push(3);
+ heap.push(4);
+
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 4);
+ CHECK(heap.pop() == 5);
+ CHECK(heap.pop() == 7);
+ }
+
+ SUBCASE("Intermediate pops") {
+ gd::Heap<int> heap;
+
+ heap.push(0);
+ heap.push(3);
+ heap.pop();
+ heap.push(1);
+ heap.push(2);
+
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 1);
+ CHECK(heap.pop() == 0);
+ }
+ }
+
+ TEST_CASE("[Heap] shift") {
+ int values[] = { 5, 3, 6, 7, 1 };
+ uint32_t heap_indexes[] = { 0, 0, 0, 0, 0 };
+ CompareArrayValues comparator(values);
+ RegisterHeapIndexes indexer(heap_indexes);
+ gd::Heap<uint32_t, CompareArrayValues, RegisterHeapIndexes> heap(comparator, indexer);
+
+ heap.push(0);
+ heap.push(1);
+ heap.push(2);
+ heap.push(3);
+ heap.push(4);
+
+ // Shift down: 6 -> 2
+ values[2] = 2;
+ heap.shift(heap_indexes[2]);
+
+ // Shift up: 5 -> 8
+ values[0] = 8;
+ heap.shift(heap_indexes[0]);
+
+ CHECK(heap.pop() == 0);
+ CHECK(heap.pop() == 3);
+ CHECK(heap.pop() == 1);
+ CHECK(heap.pop() == 2);
+ CHECK(heap.pop() == 4);
+
+ CHECK(heap_indexes[0] == UINT32_MAX);
+ CHECK(heap_indexes[1] == UINT32_MAX);
+ CHECK(heap_indexes[2] == UINT32_MAX);
+ CHECK(heap_indexes[3] == UINT32_MAX);
+ CHECK(heap_indexes[4] == UINT32_MAX);
+ }
+
+ TEST_CASE("[Heap] clear") {
+ uint32_t heap_indexes[] = { 0, 0, 0, 0 };
+ RegisterHeapIndexes indexer(heap_indexes);
+ gd::Heap<uint32_t, Comparator<uint32_t>, RegisterHeapIndexes> heap(indexer);
+
+ heap.push(0);
+ heap.push(2);
+ heap.push(1);
+ heap.push(3);
+
+ heap.clear();
+
+ CHECK(heap.size() == 0);
+
+ CHECK(heap_indexes[0] == UINT32_MAX);
+ CHECK(heap_indexes[1] == UINT32_MAX);
+ CHECK(heap_indexes[2] == UINT32_MAX);
+ CHECK(heap_indexes[3] == UINT32_MAX);
+ }
}
} //namespace TestNavigationServer3D
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 46714a2627..7e1c431a3c 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -48,6 +48,7 @@
#include "tests/core/io/test_image.h"
#include "tests/core/io/test_ip.h"
#include "tests/core/io/test_json.h"
+#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_marshalls.h"
#include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h"
diff --git a/thirdparty/README.md b/thirdparty/README.md
index d87c1c3c33..f8b41a8c52 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -543,7 +543,7 @@ in the MSVC debugger.
## mbedtls
- Upstream: https://github.com/Mbed-TLS/mbedtls
-- Version: 3.6.0 (2ca6c285a0dd3f33982dd57299012dacab1ff206, 2024)
+- Version: 3.6.1 (71c569d44bf3a8bd53d874c81ee8ac644dd6e9e3, 2024)
- License: Apache 2.0
File extracted from upstream release tarball:
@@ -553,8 +553,6 @@ File extracted from upstream release tarball:
- All `.c` and `.h` from `library/` to `thirdparty/mbedtls/library/` except
for the `psa_*.c` source files
- The `LICENSE` file (edited to keep only the Apache 2.0 variant)
-- Applied the patch `no-flexible-arrays.diff` to fix Windows build (see
- upstream GH-9020)
- Applied the patch `msvc-redeclaration-bug.diff` to fix a compilation error
with some MSVC versions
- Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h`
diff --git a/thirdparty/libwebp/patches/godot-clang-cl-fix.patch b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
new file mode 100644
index 0000000000..ee4f598951
--- /dev/null
+++ b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
@@ -0,0 +1,19 @@
+diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
+index c86540f280..4dbe607aec 100644
+--- a/thirdparty/libwebp/src/dsp/cpu.h
++++ b/thirdparty/libwebp/src/dsp/cpu.h
+@@ -47,12 +47,12 @@
+ // x86 defines.
+
+ #if !defined(HAVE_CONFIG_H)
+-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
+ #endif
+
+-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
+ #endif
diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
index c86540f280..4dbe607aec 100644
--- a/thirdparty/libwebp/src/dsp/cpu.h
+++ b/thirdparty/libwebp/src/dsp/cpu.h
@@ -47,12 +47,12 @@
// x86 defines.
#if !defined(HAVE_CONFIG_H)
-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
#endif
-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
#endif
diff --git a/thirdparty/mbedtls/include/mbedtls/bignum.h b/thirdparty/mbedtls/include/mbedtls/bignum.h
index 71d7b97672..8367cd34e6 100644
--- a/thirdparty/mbedtls/include/mbedtls/bignum.h
+++ b/thirdparty/mbedtls/include/mbedtls/bignum.h
@@ -880,7 +880,7 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A,
mbedtls_mpi_sint b);
/**
- * \brief Perform a sliding-window exponentiation: X = A^E mod N
+ * \brief Perform a modular exponentiation: X = A^E mod N
*
* \param X The destination MPI. This must point to an initialized MPI.
* This must not alias E or N.
diff --git a/thirdparty/mbedtls/include/mbedtls/build_info.h b/thirdparty/mbedtls/include/mbedtls/build_info.h
index eab167f383..8242ec6828 100644
--- a/thirdparty/mbedtls/include/mbedtls/build_info.h
+++ b/thirdparty/mbedtls/include/mbedtls/build_info.h
@@ -26,16 +26,16 @@
*/
#define MBEDTLS_VERSION_MAJOR 3
#define MBEDTLS_VERSION_MINOR 6
-#define MBEDTLS_VERSION_PATCH 0
+#define MBEDTLS_VERSION_PATCH 1
/**
* The single version number has the following structure:
* MMNNPP00
* Major version | Minor version | Patch version
*/
-#define MBEDTLS_VERSION_NUMBER 0x03060000
-#define MBEDTLS_VERSION_STRING "3.6.0"
-#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.0"
+#define MBEDTLS_VERSION_NUMBER 0x03060100
+#define MBEDTLS_VERSION_STRING "3.6.1"
+#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.1"
/* Macros for build-time platform detection */
@@ -101,6 +101,13 @@
#define inline __inline
#endif
+#if defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Something went wrong: MBEDTLS_CONFIG_FILES_READ defined before reading the config files!"
+#endif
+#if defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#error "Something went wrong: MBEDTLS_CONFIG_IS_FINALIZED defined before reading the config files!"
+#endif
+
/* X.509, TLS and non-PSA crypto configuration */
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/mbedtls_config.h"
@@ -135,6 +142,12 @@
#endif
#endif /* defined(MBEDTLS_PSA_CRYPTO_CONFIG) */
+/* Indicate that all configuration files have been read.
+ * It is now time to adjust the configuration (follow through on dependencies,
+ * make PSA and legacy crypto consistent, etc.).
+ */
+#define MBEDTLS_CONFIG_FILES_READ
+
/* Auto-enable MBEDTLS_CTR_DRBG_USE_128_BIT_KEY if
* MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH and MBEDTLS_CTR_DRBG_C defined
* to ensure a 128-bit key size in CTR_DRBG.
@@ -169,8 +182,13 @@
#include "mbedtls/config_adjust_ssl.h"
-/* Make sure all configuration symbols are set before including check_config.h,
- * even the ones that are calculated programmatically. */
+/* Indicate that all configuration symbols are set,
+ * even the ones that are calculated programmatically.
+ * It is now safe to query the configuration (to check it, to size buffers,
+ * etc.).
+ */
+#define MBEDTLS_CONFIG_IS_FINALIZED
+
#include "mbedtls/check_config.h"
#endif /* MBEDTLS_BUILD_INFO_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/check_config.h b/thirdparty/mbedtls/include/mbedtls/check_config.h
index b3c038dd2e..67a05f83b8 100644
--- a/thirdparty/mbedtls/include/mbedtls/check_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/check_config.h
@@ -2,6 +2,13 @@
* \file check_config.h
*
* \brief Consistency checks for configuration options
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * This header is included automatically by all public Mbed TLS headers
+ * (via mbedtls/build_info.h). Do not include it directly in a configuration
+ * file such as mbedtls/mbedtls_config.h or #MBEDTLS_USER_CONFIG_FILE!
+ * It would run at the wrong time due to missing derived symbols.
*/
/*
* Copyright The Mbed TLS Contributors
@@ -12,6 +19,13 @@
#define MBEDTLS_CHECK_CONFIG_H
/* *INDENT-OFF* */
+
+#if !defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#warning "Do not include mbedtls/check_config.h manually! " \
+ "This may cause spurious errors. " \
+ "It is included automatically at the right point since Mbed TLS 3.0."
+#endif /* !MBEDTLS_CONFIG_IS_FINALIZED */
+
/*
* We assume CHAR_BIT is 8 in many places. In practice, this is true on our
* target platforms, so not an issue, but let's just be extra sure.
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
index 9b06041228..3ba987ebb2 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_legacy_crypto.h
* \brief Adjust legacy configuration configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Ideally, we'd set those as defaults in mbedtls_config.h, but
* putting an #ifdef _WIN32 in mbedtls_config.h would confuse config.py.
*
@@ -48,7 +58,8 @@
defined(MBEDTLS_PSA_BUILTIN_ALG_ECB_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_PKCS7) || \
- defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG))
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG) || \
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CMAC))
#define MBEDTLS_CIPHER_C
#endif
@@ -293,6 +304,14 @@
#define MBEDTLS_ECP_LIGHT
#endif
+/* Backward compatibility: after #8740 the RSA module offers functions to parse
+ * and write RSA private/public keys without relying on the PK one. Of course
+ * this needs ASN1 support to do so, so we enable it here. */
+#if defined(MBEDTLS_RSA_C)
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#endif
+
/* MBEDTLS_PK_PARSE_EC_COMPRESSED is introduced in Mbed TLS version 3.5, while
* in previous version compressed points were automatically supported as long
* as PK_PARSE_C and ECP_C were enabled. As a consequence, for backward
@@ -409,12 +428,12 @@
/* psa_util file features some ECDSA conversion functions, to convert between
* legacy's ASN.1 DER format and PSA's raw one. */
-#if defined(MBEDTLS_ECDSA_C) || (defined(MBEDTLS_PSA_CRYPTO_C) && \
+#if (defined(MBEDTLS_PSA_CRYPTO_CLIENT) && \
(defined(PSA_WANT_ALG_ECDSA) || defined(PSA_WANT_ALG_DETERMINISTIC_ECDSA)))
#define MBEDTLS_PSA_UTIL_HAVE_ECDSA
#endif
-/* Some internal helpers to determine which keys are availble. */
+/* Some internal helpers to determine which keys are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_AES_C)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_AES))
#define MBEDTLS_SSL_HAVE_AES
@@ -428,7 +447,7 @@
#define MBEDTLS_SSL_HAVE_CAMELLIA
#endif
-/* Some internal helpers to determine which operation modes are availble. */
+/* Some internal helpers to determine which operation modes are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_CIPHER_MODE_CBC)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_CBC_NO_PADDING))
#define MBEDTLS_SSL_HAVE_CBC
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
index 0091e246b2..04bdae61bb 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_legacy_from_psa.h
* \brief Adjust PSA configuration: activate legacy implementations
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is enabled, activate legacy implementations
* of cryptographic mechanisms as needed to fulfill the needs of the PSA
* configuration. Generally speaking, we activate a legacy mechanism if
@@ -16,6 +18,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Define appropriate ACCEL macros for the p256-m driver.
* In the future, those should be generated from the drivers JSON description.
*/
@@ -498,7 +508,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF */
#endif /* PSA_WANT_ALG_HKDF */
@@ -509,7 +518,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT */
#endif /* PSA_WANT_ALG_HKDF_EXTRACT */
@@ -520,7 +528,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND */
#endif /* PSA_WANT_ALG_HKDF_EXPAND */
@@ -630,9 +637,6 @@
#if !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC)
#define MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC 1
#define PSA_HAVE_SOFT_PBKDF2_HMAC 1
-#if !defined(MBEDTLS_PSA_ACCEL_ALG_HMAC)
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
-#endif /* !MBEDTLS_PSA_ACCEL_ALG_HMAC */
#endif /* !MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC */
#endif /* PSA_WANT_ALG_PBKDF2_HMAC */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
index 3456615943..14ca14696f 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_from_legacy.h
* \brief Adjust PSA configuration: construct PSA configuration from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is disabled, we automatically enable
* cryptographic mechanisms through the PSA interface when the corresponding
* legacy mechanism is enabled. In many cases, this just enables the PSA
@@ -18,6 +20,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*
* Ensure PSA_WANT_* defines are setup properly if MBEDTLS_PSA_CRYPTO_CONFIG
* is not defined
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
index 3a55c3f6e1..ef65cce0d9 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_superset_legacy.h
* \brief Adjust PSA configuration: automatic enablement from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* To simplify some edge cases, we automatically enable certain cryptographic
* mechanisms in the PSA API if they are enabled in the legacy API. The general
* idea is that if legacy module M uses mechanism A internally, and A has
@@ -17,6 +19,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* Hashes that are built in are also enabled in PSA.
* This simplifies dependency declarations especially
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
index 39c7b3b117..1f82d9c006 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_ssl.h
* \brief Adjust TLS configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_SSL_H
#define MBEDTLS_CONFIG_ADJUST_SSL_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* The following blocks make it easier to disable all of TLS,
* or of TLS 1.2 or 1.3 or DTLS, without having to manually disable all
* key exchanges, options and extensions related to them. */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
index 346c8ae6d5..cfb2d88916 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_x509.h
* \brief Adjust X.509 configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,4 +24,12 @@
#ifndef MBEDTLS_CONFIG_ADJUST_X509_H
#define MBEDTLS_CONFIG_ADJUST_X509_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#endif /* MBEDTLS_CONFIG_ADJUST_X509_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_psa.h b/thirdparty/mbedtls/include/mbedtls/config_psa.h
index 17da61b3e8..5f3d0f3d5d 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_psa.h
@@ -22,6 +22,8 @@
#include "psa/crypto_adjust_config_synonyms.h"
+#include "psa/crypto_adjust_config_dependencies.h"
+
#include "mbedtls/config_adjust_psa_superset_legacy.h"
#if defined(MBEDTLS_PSA_CRYPTO_CONFIG)
@@ -32,7 +34,11 @@
* before we deduce what built-ins are required. */
#include "psa/crypto_adjust_config_key_pair_types.h"
+#if defined(MBEDTLS_PSA_CRYPTO_C)
+/* If we are implementing PSA crypto ourselves, then we want to enable the
+ * required built-ins. Otherwise, PSA features will be provided by the server. */
#include "mbedtls/config_adjust_legacy_from_psa.h"
+#endif
#else /* MBEDTLS_PSA_CRYPTO_CONFIG */
diff --git a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
index c00756df1b..0b7cce1923 100644
--- a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
+++ b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
@@ -32,12 +32,27 @@
#include "mbedtls/build_info.h"
-/* In case AES_C is defined then it is the primary option for backward
- * compatibility purposes. If that's not available, PSA is used instead */
-#if defined(MBEDTLS_AES_C)
-#include "mbedtls/aes.h"
-#else
+/* The CTR_DRBG implementation can either directly call the low-level AES
+ * module (gated by MBEDTLS_AES_C) or call the PSA API to perform AES
+ * operations. Calling the AES module directly is the default, both for
+ * maximum backward compatibility and because it's a bit more efficient
+ * (less glue code).
+ *
+ * When MBEDTLS_AES_C is disabled, the CTR_DRBG module calls PSA crypto and
+ * thus benefits from the PSA AES accelerator driver.
+ * It is technically possible to enable MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+ * to use PSA even when MBEDTLS_AES_C is enabled, but there is very little
+ * reason to do so other than testing purposes and this is not officially
+ * supported.
+ */
+#if !defined(MBEDTLS_AES_C)
+#define MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+#endif
+
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa/crypto.h"
+#else
+#include "mbedtls/aes.h"
#endif
#include "entropy.h"
@@ -157,7 +172,7 @@ extern "C" {
#define MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN (MBEDTLS_CTR_DRBG_ENTROPY_LEN + 1) / 2
#endif
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
typedef struct mbedtls_ctr_drbg_psa_context {
mbedtls_svc_key_id_t key_id;
psa_cipher_operation_t operation;
@@ -189,10 +204,10 @@ typedef struct mbedtls_ctr_drbg_context {
* This is the maximum number of requests
* that can be made between reseedings. */
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
mbedtls_ctr_drbg_psa_context MBEDTLS_PRIVATE(psa_ctx); /*!< The PSA context. */
+#else
+ mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
#endif
/*
diff --git a/thirdparty/mbedtls/include/mbedtls/ecdh.h b/thirdparty/mbedtls/include/mbedtls/ecdh.h
index a0909d6b44..a6a5069337 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecdh.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecdh.h
@@ -325,7 +325,7 @@ int mbedtls_ecdh_read_params(mbedtls_ecdh_context *ctx,
* \brief This function sets up an ECDH context from an EC key.
*
* It is used by clients and servers in place of the
- * ServerKeyEchange for static ECDH, and imports ECDH
+ * ServerKeyExchange for static ECDH, and imports ECDH
* parameters from the EC key information of a certificate.
*
* \see ecp.h
diff --git a/thirdparty/mbedtls/include/mbedtls/ecp.h b/thirdparty/mbedtls/include/mbedtls/ecp.h
index d8f73ae965..623910bcbd 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecp.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecp.h
@@ -216,7 +216,7 @@ mbedtls_ecp_point;
* range of <code>0..2^(2*pbits)-1</code>, and transforms it in-place to an integer
* which is congruent mod \p P to the given MPI, and is close enough to \p pbits
* in size, so that it may be efficiently brought in the 0..P-1 range by a few
- * additions or subtractions. Therefore, it is only an approximative modular
+ * additions or subtractions. Therefore, it is only an approximate modular
* reduction. It must return 0 on success and non-zero on failure.
*
* \note Alternative implementations of the ECP module must obey the
diff --git a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
index 35921412c6..bd3f71d5bc 100644
--- a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
@@ -1118,7 +1118,7 @@
* MBEDTLS_ECP_DP_SECP256R1_ENABLED
*
* \warning If SHA-256 is provided only by a PSA driver, you must call
- * psa_crypto_init() before the first hanshake (even if
+ * psa_crypto_init() before the first handshake (even if
* MBEDTLS_USE_PSA_CRYPTO is disabled).
*
* This enables the following ciphersuites (if other requisites are
@@ -1415,6 +1415,23 @@
//#define MBEDTLS_PSA_CRYPTO_SPM
/**
+ * \def MBEDTLS_PSA_KEY_STORE_DYNAMIC
+ *
+ * Dynamically resize the PSA key store to accommodate any number of
+ * volatile keys (until the heap memory is exhausted).
+ *
+ * If this option is disabled, the key store has a fixed size
+ * #MBEDTLS_PSA_KEY_SLOT_COUNT for volatile keys and loaded persistent keys
+ * together.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
+ *
+ * Module: library/psa_crypto.c
+ * Requires: MBEDTLS_PSA_CRYPTO_C
+ */
+#define MBEDTLS_PSA_KEY_STORE_DYNAMIC
+
+/**
* Uncomment to enable p256-m. This is an alternative implementation of
* key generation, ECDH and (randomized) ECDSA on the curve SECP256R1.
* Compared to the default implementation:
@@ -1781,8 +1798,9 @@
* Requires: MBEDTLS_PSA_CRYPTO_C
*
* \note TLS 1.3 uses PSA crypto for cryptographic operations that are
- * directly performed by TLS 1.3 code. As a consequence, you must
- * call psa_crypto_init() before the first TLS 1.3 handshake.
+ * directly performed by TLS 1.3 code. As a consequence, when TLS 1.3
+ * is enabled, a TLS handshake may call psa_crypto_init(), even
+ * if it ends up negotiating a different TLS version.
*
* \note Cryptographic operations performed indirectly via another module
* (X.509, PK) or by code shared with TLS 1.2 (record protection,
@@ -2625,7 +2643,7 @@
* The CTR_DRBG generator uses AES-256 by default.
* To use AES-128 instead, enable \c MBEDTLS_CTR_DRBG_USE_128_BIT_KEY above.
*
- * AES support can either be achived through builtin (MBEDTLS_AES_C) or PSA.
+ * AES support can either be achieved through builtin (MBEDTLS_AES_C) or PSA.
* Builtin is the default option when MBEDTLS_AES_C is defined otherwise PSA
* is used.
*
@@ -4016,22 +4034,38 @@
* Use HMAC_DRBG with the specified hash algorithm for HMAC_DRBG for the
* PSA crypto subsystem.
*
- * If this option is unset:
- * - If CTR_DRBG is available, the PSA subsystem uses it rather than HMAC_DRBG.
- * - Otherwise, the PSA subsystem uses HMAC_DRBG with either
- * #MBEDTLS_MD_SHA512 or #MBEDTLS_MD_SHA256 based on availability and
- * on unspecified heuristics.
+ * If this option is unset, the library chooses a hash (currently between
+ * #MBEDTLS_MD_SHA512 and #MBEDTLS_MD_SHA256) based on availability and
+ * unspecified heuristics.
+ *
+ * \note The PSA crypto subsystem uses the first available mechanism amongst
+ * the following:
+ * - #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG if enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus CTR_DRBG with AES
+ * if #MBEDTLS_CTR_DRBG_C is enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus HMAC_DRBG.
+ *
+ * A future version may reevaluate the prioritization of DRBG mechanisms.
*/
//#define MBEDTLS_PSA_HMAC_DRBG_MD_TYPE MBEDTLS_MD_SHA256
/** \def MBEDTLS_PSA_KEY_SLOT_COUNT
- * Restrict the PSA library to supporting a maximum amount of simultaneously
- * loaded keys. A loaded key is a key stored by the PSA Crypto core as a
- * volatile key, or a persistent key which is loaded temporarily by the
- * library as part of a crypto operation in flight.
*
- * If this option is unset, the library will fall back to a default value of
- * 32 keys.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled,
+ * the maximum amount of PSA keys simultaneously in memory. This counts all
+ * volatile keys, plus loaded persistent keys.
+ *
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+ * the maximum number of loaded persistent keys.
+ *
+ * Currently, persistent keys do not need to be loaded all the time while
+ * a multipart operation is in progress, only while the operation is being
+ * set up. This may change in future versions of the library.
+ *
+ * Currently, the library traverses of the whole table on each access to a
+ * persistent key. Therefore large values may cause poor performance.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
*/
//#define MBEDTLS_PSA_KEY_SLOT_COUNT 32
diff --git a/thirdparty/mbedtls/include/mbedtls/pk.h b/thirdparty/mbedtls/include/mbedtls/pk.h
index fde302f872..52f4cc6c9e 100644
--- a/thirdparty/mbedtls/include/mbedtls/pk.h
+++ b/thirdparty/mbedtls/include/mbedtls/pk.h
@@ -359,32 +359,40 @@ int mbedtls_pk_setup(mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info);
#if defined(MBEDTLS_USE_PSA_CRYPTO)
/**
- * \brief Initialize a PK context to wrap a PSA key.
- *
- * \note This function replaces mbedtls_pk_setup() for contexts
- * that wrap a (possibly opaque) PSA key instead of
- * storing and manipulating the key material directly.
- *
- * \param ctx The context to initialize. It must be empty (type NONE).
- * \param key The PSA key to wrap, which must hold an ECC or RSA key
- * pair (see notes below).
- *
- * \note The wrapped key must remain valid as long as the
- * wrapping PK context is in use, that is at least between
- * the point this function is called and the point
- * mbedtls_pk_free() is called on this context. The wrapped
- * key might then be independently used or destroyed.
- *
- * \note This function is currently only available for ECC or RSA
- * key pairs (that is, keys containing private key material).
- * Support for other key types may be added later.
- *
- * \return \c 0 on success.
- * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input
- * (context already used, invalid key identifier).
- * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an
- * ECC key pair.
- * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
+ * \brief Initialize a PK context to wrap a PSA key.
+ *
+ * This function creates a PK context which wraps a PSA key. The PSA wrapped
+ * key must be an EC or RSA key pair (DH is not suported in the PK module).
+ *
+ * Under the hood PSA functions will be used to perform the required
+ * operations and, based on the key type, used algorithms will be:
+ * * EC:
+ * * verify, verify_ext, sign, sign_ext: ECDSA.
+ * * RSA:
+ * * sign, decrypt: use the primary algorithm in the wrapped PSA key;
+ * * sign_ext: RSA PSS if the pk_type is #MBEDTLS_PK_RSASSA_PSS, otherwise
+ * it falls back to the sign() case;
+ * * verify, verify_ext, encrypt: not supported.
+ *
+ * In order for the above operations to succeed, the policy of the wrapped PSA
+ * key must allow the specified algorithm.
+ *
+ * Opaque PK contexts wrapping an EC keys also support \c mbedtls_pk_check_pair(),
+ * whereas RSA ones do not.
+ *
+ * \warning The PSA wrapped key must remain valid as long as the wrapping PK
+ * context is in use, that is at least between the point this function
+ * is called and the point mbedtls_pk_free() is called on this context.
+ *
+ * \param ctx The context to initialize. It must be empty (type NONE).
+ * \param key The PSA key to wrap, which must hold an ECC or RSA key pair.
+ *
+ * \return \c 0 on success.
+ * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input (context already
+ * used, invalid key identifier).
+ * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an ECC or
+ * RSA key pair.
+ * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
*/
int mbedtls_pk_setup_opaque(mbedtls_pk_context *ctx,
const mbedtls_svc_key_id_t key);
diff --git a/thirdparty/mbedtls/include/mbedtls/ssl.h b/thirdparty/mbedtls/include/mbedtls/ssl.h
index 172d4693b2..42fffbf860 100644
--- a/thirdparty/mbedtls/include/mbedtls/ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/ssl.h
@@ -83,10 +83,7 @@
/** Processing of the Certificate handshake message failed. */
#define MBEDTLS_ERR_SSL_BAD_CERTIFICATE -0x7A00
/* Error space gap */
-/**
- * Received NewSessionTicket Post Handshake Message.
- * This error code is experimental and may be changed or removed without notice.
- */
+/** A TLS 1.3 NewSessionTicket message has been received. */
#define MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET -0x7B00
/** Not possible to read early data */
#define MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA -0x7B80
@@ -324,6 +321,9 @@
#define MBEDTLS_SSL_SESSION_TICKETS_DISABLED 0
#define MBEDTLS_SSL_SESSION_TICKETS_ENABLED 1
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED 0
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED 1
+
#define MBEDTLS_SSL_PRESET_DEFAULT 0
#define MBEDTLS_SSL_PRESET_SUITEB 2
@@ -1446,6 +1446,12 @@ struct mbedtls_ssl_config {
#endif
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_CLI_C)
+ /** Encodes two booleans, one stating whether TLS 1.2 session tickets are
+ * enabled or not, the other one whether the handling of TLS 1.3
+ * NewSessionTicket messages is enabled or not. They are respectively set
+ * by mbedtls_ssl_conf_session_tickets() and
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ */
uint8_t MBEDTLS_PRIVATE(session_tickets); /*!< use session tickets? */
#endif
@@ -2364,7 +2370,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
*/
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len);
/**
@@ -3216,16 +3222,16 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
* a full handshake.
*
* \note This function can handle a variety of mechanisms for session
- * resumption: For TLS 1.2, both session ID-based resumption and
- * ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and loading
- * one or more sessions via this call will lead to their
- * corresponding tickets being advertised as resumption PSKs
- * by the client.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * resumption: For TLS 1.2, both session ID-based resumption
+ * and ticket-based resumption will be considered. For TLS 1.3,
+ * sessions equate to tickets, and loading one session by
+ * calling this function will lead to its corresponding ticket
+ * being advertised as resumption PSK by the client. This
+ * depends on session tickets being enabled (see
+ * #MBEDTLS_SSL_SESSION_TICKETS configuration option) though.
+ * If session tickets are disabled, a call to this function
+ * with a TLS 1.3 session, will not have any effect on the next
+ * handshake for the SSL context \p ssl.
*
* \param ssl The SSL context representing the connection which should
* be attempted to be setup using session resumption. This
@@ -3240,9 +3246,10 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
*
* \return \c 0 if successful.
* \return \c MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the session
- * could not be loaded because of an implementation limitation.
- * This error is non-fatal, and has no observable effect on
- * the SSL context or the session that was attempted to be loaded.
+ * could not be loaded because one session has already been
+ * loaded. This error is non-fatal, and has no observable
+ * effect on the SSL context or the session that was attempted
+ * to be loaded.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_get_session()
@@ -3309,8 +3316,16 @@ int mbedtls_ssl_session_load(mbedtls_ssl_session *session,
* to determine the necessary size by calling this function
* with \p buf set to \c NULL and \p buf_len to \c 0.
*
+ * \note For TLS 1.3 sessions, this feature is supported only if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is enabled,
+ * as in TLS 1.3 session resumption is possible only with
+ * tickets.
+ *
* \return \c 0 if successful.
* \return #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if \p buf is too small.
+ * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is disabled
+ * and the session is a TLS 1.3 session.
*/
int mbedtls_ssl_session_save(const mbedtls_ssl_session *session,
unsigned char *buf,
@@ -4456,21 +4471,50 @@ int mbedtls_ssl_conf_max_frag_len(mbedtls_ssl_config *conf, unsigned char mfl_co
void mbedtls_ssl_conf_preference_order(mbedtls_ssl_config *conf, int order);
#endif /* MBEDTLS_SSL_SRV_C */
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
- defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
/**
- * \brief Enable / Disable session tickets (client only).
- * (Default: MBEDTLS_SSL_SESSION_TICKETS_ENABLED.)
+ * \brief Enable / Disable TLS 1.2 session tickets (client only,
+ * TLS 1.2 only). Enabled by default.
*
* \note On server, use \c mbedtls_ssl_conf_session_tickets_cb().
*
* \param conf SSL configuration
- * \param use_tickets Enable or disable (MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
- * MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
+ * \param use_tickets Enable or disable (#MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
*/
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets);
-#endif /* MBEDTLS_SSL_SESSION_TICKETS &&
- MBEDTLS_SSL_CLI_C */
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+/**
+ * \brief Enable / Disable handling of TLS 1.3 NewSessionTicket messages
+ * (client only, TLS 1.3 only).
+ *
+ * The handling of TLS 1.3 NewSessionTicket messages is disabled by
+ * default.
+ *
+ * In TLS 1.3, servers may send a NewSessionTicket message at any time,
+ * and may send multiple NewSessionTicket messages. By default, TLS 1.3
+ * clients ignore NewSessionTicket messages.
+ *
+ * To support session tickets in TLS 1.3 clients, call this function
+ * with #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED. When
+ * this is enabled, when a client receives a NewSessionTicket message,
+ * the next call to a message processing functions (notably
+ * mbedtls_ssl_handshake() and mbedtls_ssl_read()) will return
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET. The client should then
+ * call mbedtls_ssl_get_session() to retrieve the session ticket before
+ * calling the same message processing function again.
+ *
+ * \param conf SSL configuration
+ * \param signal_new_session_tickets Enable or disable
+ * (#MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED)
+ */
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets);
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_SRV_C) && \
@@ -4837,23 +4881,16 @@ const mbedtls_x509_crt *mbedtls_ssl_get_peer_cert(const mbedtls_ssl_context *ssl
* \note This function can handle a variety of mechanisms for session
* resumption: For TLS 1.2, both session ID-based resumption and
* ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and calling
- * this function multiple times will export the available
- * tickets one a time until no further tickets are available,
- * in which case MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE will
- * be returned.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * sessions equate to tickets, and if session tickets are
+ * enabled (see #MBEDTLS_SSL_SESSION_TICKETS configuration
+ * option), this function exports the last received ticket and
+ * the exported session may be used to resume the TLS 1.3
+ * session. If session tickets are disabled, exported sessions
+ * cannot be used to resume a TLS 1.3 session.
*
* \return \c 0 if successful. In this case, \p session can be used for
* session resumption by passing it to mbedtls_ssl_set_session(),
* and serialized for storage via mbedtls_ssl_session_save().
- * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if no further session
- * is available for export.
- * This error is a non-fatal, and has no observable effect on
- * the SSL context or the destination session.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_set_session()
@@ -4885,6 +4922,10 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* \return #MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED if DTLS is in use
* and the client did not demonstrate reachability yet - in
* this case you must stop using the context (see below).
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -4901,6 +4942,7 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS or
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -4921,10 +4963,13 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* currently being processed might or might not contain further
* DTLS records.
*
- * \note If the context is configured to allow TLS 1.3, or if
- * #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
+ * \note If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
* subsystem must have been initialized by calling
* psa_crypto_init() before calling this function.
+ * Otherwise, the handshake may call psa_crypto_init()
+ * if a negotiation involving TLS 1.3 takes place (this may
+ * be the case even if TLS 1.3 is offered but eventually
+ * not selected).
*/
int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl);
@@ -4972,6 +5017,7 @@ static inline int mbedtls_ssl_is_handshake_over(mbedtls_ssl_context *ssl)
* #MBEDTLS_ERR_SSL_WANT_READ, #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, you must stop using
* the SSL context for reading or writing, and either free it
* or call \c mbedtls_ssl_session_reset() on it before
@@ -5040,6 +5086,17 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* \return #MBEDTLS_ERR_SSL_CLIENT_RECONNECT if we're at the server
* side of a DTLS connection and the client is initiating a
* new connection using the same source port. See below.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received.
+ * This error code is only returned on the client side. It is
+ * only returned if handling of TLS 1.3 NewSessionTicket
+ * messages has been enabled through
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ * This error code indicates that a TLS 1.3 NewSessionTicket
+ * message has been received and parsed successfully by the
+ * client. The ticket data can be retrieved from the SSL
+ * context by calling mbedtls_ssl_get_session(). It remains
+ * available until the next call to mbedtls_ssl_read().
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5057,6 +5114,7 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CLIENT_RECONNECT or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -5122,6 +5180,10 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* operation is in progress (see mbedtls_ecp_set_max_ops()) -
* in this case you must call this function again to complete
* the handshake when you're done attending other tasks.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5138,6 +5200,7 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
index 390534edf5..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -121,8 +121,8 @@ static psa_key_attributes_t psa_key_attributes_init(void);
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -131,6 +131,9 @@ static psa_key_attributes_t psa_key_attributes_init(void);
*
* \param[out] attributes The attribute structure to write to.
* \param key The persistent identifier for the key.
+ * This can be any value in the range from
+ * #PSA_KEY_ID_USER_MIN to #PSA_KEY_ID_USER_MAX
+ * inclusive.
*/
static void psa_set_key_id(psa_key_attributes_t *attributes,
mbedtls_svc_key_id_t key);
@@ -166,8 +169,8 @@ static void mbedtls_set_key_owner_id(psa_key_attributes_t *attributes,
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -875,7 +878,7 @@ psa_status_t psa_hash_compute(psa_algorithm_t alg,
* such that #PSA_ALG_IS_HASH(\p alg) is true).
* \param[in] input Buffer containing the message to hash.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] hash Buffer containing the expected hash value.
+ * \param[in] hash Buffer containing the expected hash value.
* \param hash_length Size of the \p hash buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -1230,7 +1233,7 @@ psa_status_t psa_mac_compute(mbedtls_svc_key_id_t key,
* such that #PSA_ALG_IS_MAC(\p alg) is true).
* \param[in] input Buffer containing the input message.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] mac Buffer containing the expected MAC value.
+ * \param[in] mac Buffer containing the expected MAC value.
* \param mac_length Size of the \p mac buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -2922,7 +2925,7 @@ psa_status_t psa_sign_message(mbedtls_svc_key_id_t key,
* \p key.
* \param[in] input The message whose signature is to be verified.
* \param[in] input_length Size of the \p input buffer in bytes.
- * \param[out] signature Buffer containing the signature to verify.
+ * \param[in] signature Buffer containing the signature to verify.
* \param[in] signature_length Size of the \p signature buffer in bytes.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3248,7 +3251,7 @@ static psa_key_derivation_operation_t psa_key_derivation_operation_init(void);
* of or after providing inputs. For some algorithms, this step is mandatory
* because the output depends on the maximum capacity.
* -# To derive a key, call psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
* To derive a byte string for a different purpose, call
* psa_key_derivation_output_bytes().
* Successive calls to these functions use successive output bytes
@@ -3471,7 +3474,7 @@ psa_status_t psa_key_derivation_input_integer(
* \note Once all inputs steps are completed, the operations will allow:
* - psa_key_derivation_output_bytes() if each input was either a direct input
* or a key with #PSA_KEY_USAGE_DERIVE set;
- * - psa_key_derivation_output_key() or psa_key_derivation_output_key_ext()
+ * - psa_key_derivation_output_key() or psa_key_derivation_output_key_custom()
* if the input for step
* #PSA_KEY_DERIVATION_INPUT_SECRET or #PSA_KEY_DERIVATION_INPUT_PASSWORD
* was from a key slot with #PSA_KEY_USAGE_DERIVE and each other input was
@@ -3721,9 +3724,9 @@ psa_status_t psa_key_derivation_output_bytes(
* on the derived key based on the attributes and strength of the secret key.
*
* \note This function is equivalent to calling
- * psa_key_derivation_output_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * psa_key_derivation_output_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* If the key type to be created is
@@ -3795,6 +3798,85 @@ psa_status_t psa_key_derivation_output_key(
* the policy must be the same as in the current
* operation.
* \param[in,out] operation The key derivation operation object to read from.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_key_derivation_output_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_INSUFFICIENT_DATA
+ * There was not enough data to create the desired key.
+ * Note that in this case, no output is written to the output buffer.
+ * The operation's capacity is set to 0, thus subsequent calls to
+ * this function will not succeed, even with a smaller output buffer.
+ * \retval #PSA_ERROR_NOT_SUPPORTED
+ * The key type or key size is not supported, either by the
+ * implementation in general or in this particular location.
+ * \retval #PSA_ERROR_INVALID_ARGUMENT
+ * The provided key attributes are not valid for the operation.
+ * \retval #PSA_ERROR_NOT_PERMITTED
+ * The #PSA_KEY_DERIVATION_INPUT_SECRET or
+ * #PSA_KEY_DERIVATION_INPUT_PASSWORD input was not provided through a
+ * key; or one of the inputs was a key whose policy didn't allow
+ * #PSA_KEY_USAGE_DERIVE.
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The operation state is not valid (it must be active and completed
+ * all required input steps), or the library has not been previously
+ * initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_key_derivation_output_key_custom(
+ const psa_key_attributes_t *attributes,
+ psa_key_derivation_operation_t *operation,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/** Derive a key from an ongoing key derivation operation with custom
+ * production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
+ * If the key type to be created is
+ * #PSA_KEY_TYPE_PASSWORD_HASH then the algorithm in
+ * the policy must be the same as in the current
+ * operation.
+ * \param[in,out] operation The key derivation operation object to read from.
* \param[in] params Customization parameters for the key derivation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -3848,14 +3930,13 @@ psa_status_t psa_key_derivation_output_key(
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_key_derivation_output_key_ext(
const psa_key_attributes_t *attributes,
psa_key_derivation_operation_t *operation,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/** Compare output data from a key derivation operation to an expected value.
*
@@ -3881,8 +3962,8 @@ psa_status_t psa_key_derivation_output_key_ext(
* psa_key_derivation_abort().
*
* \param[in,out] operation The key derivation operation object to read from.
- * \param[in] expected_output Buffer containing the expected derivation output.
- * \param output_length Length of the expected output; this is also the
+ * \param[in] expected Buffer containing the expected derivation output.
+ * \param expected_length Length of the expected output; this is also the
* number of bytes that will be read.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3912,8 +3993,8 @@ psa_status_t psa_key_derivation_output_key_ext(
*/
psa_status_t psa_key_derivation_verify_bytes(
psa_key_derivation_operation_t *operation,
- const uint8_t *expected_output,
- size_t output_length);
+ const uint8_t *expected,
+ size_t expected_length);
/** Compare output data from a key derivation operation to an expected value
* stored in a key object.
@@ -3943,7 +4024,7 @@ psa_status_t psa_key_derivation_verify_bytes(
* operation. The value of this key was likely
* computed by a previous call to
* psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INVALID_SIGNATURE
@@ -4111,9 +4192,9 @@ psa_status_t psa_generate_random(uint8_t *output,
* between 2^{n-1} and 2^n where n is the bit size specified in the
* attributes.
*
- * \note This function is equivalent to calling psa_generate_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * \note This function is equivalent to calling psa_generate_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* \param[out] key On success, an identifier for the newly created
@@ -4153,7 +4234,7 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* See the description of psa_generate_key() for the operation of this
* function with the default production parameters. In addition, this function
* supports the following production customizations, described in more detail
- * in the documentation of ::psa_key_production_parameters_t:
+ * in the documentation of ::psa_custom_key_parameters_t:
*
* - RSA keys: generation with a custom public exponent.
*
@@ -4161,6 +4242,64 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* versions of Mbed TLS.
*
* \param[in] attributes The attributes for the new key.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_generate_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_NOT_SUPPORTED \emptydescription
+ * \retval #PSA_ERROR_INVALID_ARGUMENT \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The library has not been previously initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/**
+ * \brief Generate a key or key pair using custom production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
* \param[in] params Customization parameters for the key generation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -4196,12 +4335,11 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/**@}*/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
index 63fb29e85b..3a2af15180 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_auto_enabled.h
* \brief Adjust PSA configuration: enable always-on features
*
+ * This is an internal header. Do not include it directly.
+ *
* Always enable certain features which require a negligible amount of code
* to implement, to avoid some edge cases in the configuration combinatorics.
*/
@@ -13,6 +15,14 @@
#ifndef PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
#define PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#define PSA_WANT_KEY_TYPE_DERIVE 1
#define PSA_WANT_KEY_TYPE_PASSWORD 1
#define PSA_WANT_KEY_TYPE_PASSWORD_HASH 1
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
new file mode 100644
index 0000000000..92e9c4de28
--- /dev/null
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
@@ -0,0 +1,51 @@
+/**
+ * \file psa/crypto_adjust_config_dependencies.h
+ * \brief Adjust PSA configuration by resolving some dependencies.
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * See docs/proposed/psa-conditional-inclusion-c.md.
+ * If the Mbed TLS implementation of a cryptographic mechanism A depends on a
+ * cryptographic mechanism B then if the cryptographic mechanism A is enabled
+ * and not accelerated enable B. Note that if A is enabled and accelerated, it
+ * is not necessary to enable B for A support.
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+
+#ifndef PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+#define PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
+#if (defined(PSA_WANT_ALG_TLS12_PRF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PRF)) || \
+ (defined(PSA_WANT_ALG_TLS12_PSK_TO_MS) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PSK_TO_MS)) || \
+ (defined(PSA_WANT_ALG_HKDF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXTRACT) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXPAND) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND)) || \
+ (defined(PSA_WANT_ALG_PBKDF2_HMAC) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC))
+#define PSA_WANT_ALG_HMAC 1
+#define PSA_WANT_KEY_TYPE_HMAC 1
+#endif
+
+#if (defined(PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_AES_CMAC_PRF_128))
+#define PSA_WANT_KEY_TYPE_AES 1
+#define PSA_WANT_ALG_CMAC 1
+#endif
+
+#endif /* PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H */
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
index 63afc0e402..cec39e01ce 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_key_pair_types.h
* \brief Adjust PSA configuration for key pair types.
*
+ * This is an internal header. Do not include it directly.
+ *
* See docs/proposed/psa-conditional-inclusion-c.md.
* - Support non-basic operations in a keypair type implicitly enables basic
* support for that keypair type.
@@ -19,6 +21,14 @@
#ifndef PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
#define PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*****************************************************************
* ANYTHING -> BASIC
****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
index 332b622c9b..54b116f434 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_synonyms.h
* \brief Adjust PSA configuration: enable quasi-synonyms
*
+ * This is an internal header. Do not include it directly.
+ *
* When two features require almost the same code, we automatically enable
* both when either one is requested, to reduce the combinatorics of
* possible configurations.
@@ -14,6 +16,14 @@
#ifndef PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
#define PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* De facto synonyms */
/****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
index bd985e9b78..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
@@ -154,6 +154,14 @@ static inline void psa_clear_key_slot_number(
* specified in \p attributes.
*
* \param[in] attributes The attributes of the existing key.
+ * - The lifetime must be a persistent lifetime
+ * in a secure element. Volatile lifetimes are
+ * not currently supported.
+ * - The key identifier must be in the valid
+ * range for persistent keys.
+ * - The key type and size must be specified and
+ * must be consistent with the key material
+ * in the secure element.
*
* \retval #PSA_SUCCESS
* The key was successfully registered.
@@ -479,7 +487,7 @@ psa_status_t mbedtls_psa_external_get_random(
* #PSA_KEY_ID_VENDOR_MIN and #PSA_KEY_ID_VENDOR_MAX and must not intersect
* with any other set of implementation-chosen key identifiers.
*
- * This value is part of the library's ABI since changing it would invalidate
+ * This value is part of the library's API since changing it would invalidate
* the values of built-in key identifiers in applications.
*/
#define MBEDTLS_PSA_KEY_ID_BUILTIN_MIN ((psa_key_id_t) 0x7fff0000)
diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
index e2c227b2eb..362e921a36 100644
--- a/thirdparty/mbedtls/include/psa/crypto_struct.h
+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
@@ -223,13 +223,36 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
return v;
}
+struct psa_custom_key_parameters_s {
+ /* Future versions may add other fields in this structure. */
+ uint32_t flags;
+};
+
+/** The default production parameters for key generation or key derivation.
+ *
+ * Calling psa_generate_key_custom() or psa_key_derivation_output_key_custom()
+ * with `custom=PSA_CUSTOM_KEY_PARAMETERS_INIT` and `custom_data_length=0` is
+ * equivalent to calling psa_generate_key() or psa_key_derivation_output_key()
+ * respectively.
+ */
+#define PSA_CUSTOM_KEY_PARAMETERS_INIT { 0 }
+
#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/* This is a deprecated variant of `struct psa_custom_key_parameters_s`.
+ * It has exactly the same layout, plus an extra field which is a flexible
+ * array member. Thus a `const struct psa_key_production_parameters_s *`
+ * can be passed to any function that reads a
+ * `const struct psa_custom_key_parameters_s *`.
+ */
struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
uint32_t flags;
uint8_t data[];
};
-#endif
/** The default production parameters for key generation or key derivation.
*
@@ -240,6 +263,7 @@ struct psa_key_production_parameters_s {
* respectively.
*/
#define PSA_KEY_PRODUCTION_PARAMETERS_INIT { 0 }
+#endif /* !__cplusplus */
struct psa_key_policy_s {
psa_key_usage_t MBEDTLS_PRIVATE(usage);
diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
index a36b6ee65d..f831486f4e 100644
--- a/thirdparty/mbedtls/include/psa/crypto_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
@@ -457,6 +457,30 @@ typedef uint16_t psa_key_derivation_step_t;
/** \brief Custom parameters for key generation or key derivation.
*
+ * This is a structure type with at least the following field:
+ *
+ * - \c flags: an unsigned integer type. 0 for the default production parameters.
+ *
+ * Functions that take such a structure as input also take an associated
+ * input buffer \c custom_data of length \c custom_data_length.
+ *
+ * The interpretation of this structure and the associated \c custom_data
+ * parameter depend on the type of the created key.
+ *
+ * - #PSA_KEY_TYPE_RSA_KEY_PAIR:
+ * - \c flags: must be 0.
+ * - \c custom_data: the public exponent, in little-endian order.
+ * This must be an odd integer and must not be 1.
+ * Implementations must support 65537, should support 3 and may
+ * support other values.
+ * When not using a driver, Mbed TLS supports values up to \c INT_MAX.
+ * If this is empty, the default value 65537 is used.
+ * - Other key types: reserved for future use. \c flags must be 0.
+ */
+typedef struct psa_custom_key_parameters_s psa_custom_key_parameters_t;
+
+/** \brief Custom parameters for key generation or key derivation.
+ *
* This is a structure type with at least the following fields:
*
* - \c flags: an unsigned integer type. 0 for the default production parameters.
@@ -477,9 +501,7 @@ typedef uint16_t psa_key_derivation_step_t;
* - Other key types: reserved for future use. \c flags must be 0.
*
*/
-#ifndef __cplusplus
typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-#endif
/**@}*/
diff --git a/thirdparty/mbedtls/library/bignum.c b/thirdparty/mbedtls/library/bignum.c
index c45fd5bf24..424490951d 100644
--- a/thirdparty/mbedtls/library/bignum.c
+++ b/thirdparty/mbedtls/library/bignum.c
@@ -27,6 +27,7 @@
#include "mbedtls/bignum.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "bn_mul.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/error.h"
@@ -1610,9 +1611,13 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_s
return 0;
}
-int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
- const mbedtls_mpi *E, const mbedtls_mpi *N,
- mbedtls_mpi *prec_RR)
+/*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static int mbedtls_mpi_exp_mod_optionally_safe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, int E_public,
+ const mbedtls_mpi *N, mbedtls_mpi *prec_RR)
{
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
@@ -1695,7 +1700,11 @@ int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
{
mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N->p);
mbedtls_mpi_core_to_mont_rep(X->p, X->p, N->p, N->n, mm, RR.p, T);
- mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_core_exp_mod_unsafe(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ } else {
+ mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ }
mbedtls_mpi_core_from_mont_rep(X->p, X->p, N->p, N->n, mm, T);
}
@@ -1720,6 +1729,20 @@ cleanup:
return ret;
}
+int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_SECRET, N, prec_RR);
+}
+
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_PUBLIC, N, prec_RR);
+}
+
/*
* Greatest common divisor: G = gcd(A, B) (HAC 14.54)
*/
diff --git a/thirdparty/mbedtls/library/bignum_core.c b/thirdparty/mbedtls/library/bignum_core.c
index 1a3e0b9b6f..4231554b84 100644
--- a/thirdparty/mbedtls/library/bignum_core.c
+++ b/thirdparty/mbedtls/library/bignum_core.c
@@ -746,8 +746,94 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
}
}
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+// Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+int mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+#endif
+
+/*
+ * This function calculates the indices of the exponent where the exponentiation algorithm should
+ * start processing.
+ *
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static inline void exp_mod_calc_first_bit_optionally_safe(const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ size_t *E_limb_index,
+ size_t *E_bit_index)
+{
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ /*
+ * Skip leading zero bits.
+ */
+ size_t E_bits = mbedtls_mpi_core_bitlen(E, E_limbs);
+ if (E_bits == 0) {
+ /*
+ * If E is 0 mbedtls_mpi_core_bitlen() returns 0. Even if that is the case, we will want
+ * to represent it as a single 0 bit and as such the bitlength will be 1.
+ */
+ E_bits = 1;
+ }
+
+ *E_limb_index = E_bits / biL;
+ *E_bit_index = E_bits % biL;
+
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /*
+ * Here we need to be constant time with respect to E and can't do anything better than
+ * start at the first allocated bit.
+ */
+ *E_limb_index = E_limbs;
+ *E_bit_index = 0;
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
+/*
+ * Warning! If the parameter window_public has MBEDTLS_MPI_IS_PUBLIC as its value, this function is
+ * not constant time with respect to the window parameter and consequently the exponent of the
+ * exponentiation (parameter E of mbedtls_mpi_core_exp_mod_optionally_safe).
+ */
+static inline void exp_mod_table_lookup_optionally_safe(mbedtls_mpi_uint *Wselect,
+ mbedtls_mpi_uint *Wtable,
+ size_t AN_limbs, size_t welem,
+ mbedtls_mpi_uint window,
+ int window_public)
+{
+ if (window_public == MBEDTLS_MPI_IS_PUBLIC) {
+ memcpy(Wselect, Wtable + window * AN_limbs, AN_limbs * ciL);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /* Select Wtable[window] without leaking window through
+ * memory access patterns. */
+ mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
+ AN_limbs, welem, window);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
/* Exponentiation: X := A^E mod N.
*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ *
* A must already be in Montgomery form.
*
* As in other bignum functions, assume that AN_limbs and E_limbs are nonzero.
@@ -758,16 +844,25 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
* (The difference is that the body in our loop processes a single bit instead
* of a full window.)
*/
-void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
- const mbedtls_mpi_uint *A,
- const mbedtls_mpi_uint *N,
- size_t AN_limbs,
- const mbedtls_mpi_uint *E,
- size_t E_limbs,
- const mbedtls_mpi_uint *RR,
- mbedtls_mpi_uint *T)
+static void mbedtls_mpi_core_exp_mod_optionally_safe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N,
+ size_t AN_limbs,
+ const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
{
- const size_t wsize = exp_mod_get_window_size(E_limbs * biL);
+ /* We'll process the bits of E from most significant
+ * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
+ * (limb_index=0, E_bit_index=0). */
+ size_t E_limb_index;
+ size_t E_bit_index;
+ exp_mod_calc_first_bit_optionally_safe(E, E_limbs, E_public,
+ &E_limb_index, &E_bit_index);
+
+ const size_t wsize = exp_mod_get_window_size(E_limb_index * biL);
const size_t welem = ((size_t) 1) << wsize;
/* This is how we will use the temporary storage T, which must have space
@@ -786,7 +881,7 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N);
- /* Set Wtable[i] = A^(2^i) (in Montgomery representation) */
+ /* Set Wtable[i] = A^i (in Montgomery representation) */
exp_mod_precompute_window(A, N, AN_limbs,
mm, RR,
welem, Wtable, temp);
@@ -798,11 +893,6 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
/* X = 1 (in Montgomery presentation) initially */
memcpy(X, Wtable, AN_limbs * ciL);
- /* We'll process the bits of E from most significant
- * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
- * (limb_index=0, E_bit_index=0). */
- size_t E_limb_index = E_limbs;
- size_t E_bit_index = 0;
/* At any given time, window contains window_bits bits from E.
* window_bits can go up to wsize. */
size_t window_bits = 0;
@@ -828,10 +918,9 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
* when we've finished processing the exponent. */
if (window_bits == wsize ||
(E_bit_index == 0 && E_limb_index == 0)) {
- /* Select Wtable[window] without leaking window through
- * memory access patterns. */
- mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
- AN_limbs, welem, window);
+
+ exp_mod_table_lookup_optionally_safe(Wselect, Wtable, AN_limbs, welem,
+ window, E_public);
/* Multiply X by the selected element. */
mbedtls_mpi_core_montmul(X, X, Wselect, AN_limbs, N, AN_limbs, mm,
temp);
@@ -841,6 +930,42 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
} while (!(E_bit_index == 0 && E_limb_index == 0));
}
+void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_SECRET,
+ RR,
+ T);
+}
+
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_PUBLIC,
+ RR,
+ T);
+}
+
mbedtls_mpi_uint mbedtls_mpi_core_sub_int(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint *A,
mbedtls_mpi_uint c, /* doubles as carry */
diff --git a/thirdparty/mbedtls/library/bignum_core.h b/thirdparty/mbedtls/library/bignum_core.h
index 92c8d47db5..cf6485a148 100644
--- a/thirdparty/mbedtls/library/bignum_core.h
+++ b/thirdparty/mbedtls/library/bignum_core.h
@@ -90,6 +90,27 @@
#define GET_BYTE(X, i) \
(((X)[(i) / ciL] >> (((i) % ciL) * 8)) & 0xff)
+/* Constants to identify whether a value is public or secret. If a parameter is marked as secret by
+ * this constant, the function must be constant time with respect to the parameter.
+ *
+ * This is only needed for functions with the _optionally_safe postfix. All other functions have
+ * fixed behavior that can't be changed at runtime and are constant time with respect to their
+ * parameters as prescribed by their documentation or by conventions in their module's documentation.
+ *
+ * Parameters should be named X_public where X is the name of the
+ * corresponding input parameter.
+ *
+ * Implementation should always check using
+ * if (X_public == MBEDTLS_MPI_IS_PUBLIC) {
+ * // unsafe path
+ * } else {
+ * // safe path
+ * }
+ * not the other way round, in order to prevent misuse. (This is, if a value
+ * other than the two below is passed, default to the safe path.) */
+#define MBEDTLS_MPI_IS_PUBLIC 0x2a2a2a2a
+#define MBEDTLS_MPI_IS_SECRET 0
+
/** Count leading zero bits in a given integer.
*
* \warning The result is undefined if \p a == 0
@@ -605,6 +626,42 @@ int mbedtls_mpi_core_random(mbedtls_mpi_uint *X,
size_t mbedtls_mpi_core_exp_mod_working_limbs(size_t AN_limbs, size_t E_limbs);
/**
+ * \brief Perform a modular exponentiation with public or secret exponent:
+ * X = A^E mod N, where \p A is already in Montgomery form.
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \p X may be aliased to \p A, but not to \p RR or \p E, even if \p E_limbs ==
+ * \p AN_limbs.
+ *
+ * \param[out] X The destination MPI, as a little endian array of length
+ * \p AN_limbs.
+ * \param[in] A The base MPI, as a little endian array of length \p AN_limbs.
+ * Must be in Montgomery form.
+ * \param[in] N The modulus, as a little endian array of length \p AN_limbs.
+ * \param AN_limbs The number of limbs in \p X, \p A, \p N, \p RR.
+ * \param[in] E The exponent, as a little endian array of length \p E_limbs.
+ * \param E_limbs The number of limbs in \p E.
+ * \param[in] RR The precomputed residue of 2^{2*biL} modulo N, as a little
+ * endian array of length \p AN_limbs.
+ * \param[in,out] T Temporary storage of at least the number of limbs returned
+ * by `mbedtls_mpi_core_exp_mod_working_limbs()`.
+ * Its initial content is unused and its final content is
+ * indeterminate.
+ * It must not alias or otherwise overlap any of the other
+ * parameters.
+ * It is up to the caller to zeroize \p T when it is no
+ * longer needed, and before freeing it if it was dynamically
+ * allocated.
+ */
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T);
+
+/**
* \brief Perform a modular exponentiation with secret exponent:
* X = A^E mod N, where \p A is already in Montgomery form.
*
@@ -760,4 +817,17 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X,
mbedtls_mpi_uint mm,
mbedtls_mpi_uint *T);
+/*
+ * Can't define thread local variables with our abstraction layer: do nothing if threading is on.
+ */
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+extern int mbedtls_mpi_optionally_safe_codepath;
+
+static inline void mbedtls_mpi_optionally_safe_codepath_reset(void)
+{
+ // Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+}
+#endif
+
#endif /* MBEDTLS_BIGNUM_CORE_H */
diff --git a/thirdparty/mbedtls/library/bignum_internal.h b/thirdparty/mbedtls/library/bignum_internal.h
new file mode 100644
index 0000000000..aceaf55ea2
--- /dev/null
+++ b/thirdparty/mbedtls/library/bignum_internal.h
@@ -0,0 +1,50 @@
+/**
+ * \file bignum_internal.h
+ *
+ * \brief Internal-only bignum public-key cryptosystem API.
+ *
+ * This file declares bignum-related functions that are to be used
+ * only from within the Mbed TLS library itself.
+ *
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+#ifndef MBEDTLS_BIGNUM_INTERNAL_H
+#define MBEDTLS_BIGNUM_INTERNAL_H
+
+/**
+ * \brief Perform a modular exponentiation: X = A^E mod N
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \param X The destination MPI. This must point to an initialized MPI.
+ * This must not alias E or N.
+ * \param A The base of the exponentiation.
+ * This must point to an initialized MPI.
+ * \param E The exponent MPI. This must point to an initialized MPI.
+ * \param N The base for the modular reduction. This must point to an
+ * initialized MPI.
+ * \param prec_RR A helper MPI depending solely on \p N which can be used to
+ * speed-up multiple modular exponentiations for the same value
+ * of \p N. This may be \c NULL. If it is not \c NULL, it must
+ * point to an initialized MPI. If it hasn't been used after
+ * the call to mbedtls_mpi_init(), this function will compute
+ * the helper value and store it in \p prec_RR for reuse on
+ * subsequent calls to this function. Otherwise, the function
+ * will assume that \p prec_RR holds the helper value set by a
+ * previous call to mbedtls_mpi_exp_mod(), and reuse it.
+ *
+ * \return \c 0 if successful.
+ * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed.
+ * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if \c N is negative or
+ * even, or if \c E is negative.
+ * \return Another negative error code on different kinds of failures.
+ *
+ */
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR);
+
+#endif /* bignum_internal.h */
diff --git a/thirdparty/mbedtls/library/block_cipher.c b/thirdparty/mbedtls/library/block_cipher.c
index 04cd7fb444..51cdcdf46b 100644
--- a/thirdparty/mbedtls/library/block_cipher.c
+++ b/thirdparty/mbedtls/library/block_cipher.c
@@ -51,6 +51,10 @@ static int mbedtls_cipher_error_from_psa(psa_status_t status)
void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA)
if (ctx->engine == MBEDTLS_BLOCK_CIPHER_ENGINE_PSA) {
psa_destroy_key(ctx->psa_key_id);
diff --git a/thirdparty/mbedtls/library/cipher.c b/thirdparty/mbedtls/library/cipher.c
index 0683677eda..7f4c121492 100644
--- a/thirdparty/mbedtls/library/cipher.c
+++ b/thirdparty/mbedtls/library/cipher.c
@@ -849,6 +849,9 @@ static int get_pkcs_padding(unsigned char *input, size_t input_len,
}
padding_len = input[input_len - 1];
+ if (padding_len == 0 || padding_len > input_len) {
+ return MBEDTLS_ERR_CIPHER_INVALID_PADDING;
+ }
*data_len = input_len - padding_len;
mbedtls_ct_condition_t bad = mbedtls_ct_uint_gt(padding_len, input_len);
diff --git a/thirdparty/mbedtls/library/common.h b/thirdparty/mbedtls/library/common.h
index 3936ffdfe1..7bb2674293 100644
--- a/thirdparty/mbedtls/library/common.h
+++ b/thirdparty/mbedtls/library/common.h
@@ -352,17 +352,19 @@ static inline void mbedtls_xor_no_simd(unsigned char *r,
#endif
/* Always provide a static assert macro, so it can be used unconditionally.
- * It will expand to nothing on some systems.
- * Can be used outside functions (but don't add a trailing ';' in that case:
- * the semicolon is included here to avoid triggering -Wextra-semi when
- * MBEDTLS_STATIC_ASSERT() expands to nothing).
- * Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
+ * It does nothing on systems where we don't know how to define a static assert.
+ */
+/* Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
* defines static_assert even with -std=c99, but then complains about it.
*/
#if defined(static_assert) && !defined(__FreeBSD__)
-#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg);
+#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg)
#else
-#define MBEDTLS_STATIC_ASSERT(expr, msg)
+/* Make sure `MBEDTLS_STATIC_ASSERT(expr, msg);` is valid both inside and
+ * outside a function. We choose a struct declaration, which can be repeated
+ * any number of times and does not need a matching definition. */
+#define MBEDTLS_STATIC_ASSERT(expr, msg) \
+ struct ISO_C_does_not_allow_extra_semicolon_outside_of_a_function
#endif
#if defined(__has_builtin)
diff --git a/thirdparty/mbedtls/library/ctr_drbg.c b/thirdparty/mbedtls/library/ctr_drbg.c
index 66d9d28c58..b82044eb7d 100644
--- a/thirdparty/mbedtls/library/ctr_drbg.c
+++ b/thirdparty/mbedtls/library/ctr_drbg.c
@@ -26,13 +26,13 @@
#endif
/* Using error translation functions from PSA to MbedTLS */
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa_util_internal.h"
#endif
#include "mbedtls/platform.h"
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
static psa_status_t ctr_drbg_setup_psa_context(mbedtls_ctr_drbg_psa_context *psa_ctx,
unsigned char *key, size_t key_len)
{
@@ -73,11 +73,11 @@ static void ctr_drbg_destroy_psa_contex(mbedtls_ctr_drbg_psa_context *psa_ctx)
void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context *ctx)
{
memset(ctx, 0, sizeof(mbedtls_ctr_drbg_context));
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_init(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctx->psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
ctx->psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_init(&ctx->aes_ctx);
#endif
/* Indicate that the entropy nonce length is not set explicitly.
* See mbedtls_ctr_drbg_set_nonce_len(). */
@@ -102,10 +102,10 @@ void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context *ctx)
mbedtls_mutex_free(&ctx->mutex);
}
#endif
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
+#else
+ mbedtls_aes_free(&ctx->aes_ctx);
#endif
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_ctr_drbg_context));
ctx->reseed_interval = MBEDTLS_CTR_DRBG_RESEED_INTERVAL;
@@ -168,15 +168,15 @@ static int block_cipher_df(unsigned char *output,
unsigned char chain[MBEDTLS_CTR_DRBG_BLOCKSIZE];
unsigned char *p, *iv;
int ret = 0;
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context aes_ctx;
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
mbedtls_ctr_drbg_psa_context psa_ctx;
psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_context aes_ctx;
#endif
int i, j;
@@ -209,19 +209,19 @@ static int block_cipher_df(unsigned char *output,
key[i] = i;
}
-#if defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
+ status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
+ if (status != PSA_SUCCESS) {
+ ret = psa_generic_status_to_mbedtls(status);
+ goto exit;
+ }
+#else
mbedtls_aes_init(&aes_ctx);
if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, key,
MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
goto exit;
}
-#else
- status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
- if (status != PSA_SUCCESS) {
- ret = psa_generic_status_to_mbedtls(status);
- goto exit;
- }
#endif
/*
@@ -238,18 +238,18 @@ static int block_cipher_df(unsigned char *output,
use_len -= (use_len >= MBEDTLS_CTR_DRBG_BLOCKSIZE) ?
MBEDTLS_CTR_DRBG_BLOCKSIZE : use_len;
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- chain, chain)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, chain, MBEDTLS_CTR_DRBG_BLOCKSIZE,
chain, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ chain, chain)) != 0) {
+ goto exit;
+ }
#endif
}
@@ -264,12 +264,7 @@ static int block_cipher_df(unsigned char *output,
/*
* Do final encryption with reduced data
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
status = ctr_drbg_setup_psa_context(&psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -277,32 +272,37 @@ static int block_cipher_df(unsigned char *output,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
iv = tmp + MBEDTLS_CTR_DRBG_KEYSIZE;
p = output;
for (j = 0; j < MBEDTLS_CTR_DRBG_SEEDLEN; j += MBEDTLS_CTR_DRBG_BLOCKSIZE) {
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- iv, iv)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE,
iv, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ iv, iv)) != 0) {
+ goto exit;
+ }
#endif
memcpy(p, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE);
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
}
exit:
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
+#else
+ mbedtls_aes_free(&aes_ctx);
#endif
/*
* tidy up the stack
@@ -336,7 +336,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
unsigned char *p = tmp;
int j;
int ret = 0;
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
#endif
@@ -352,18 +352,18 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, p)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&ctx->psa_ctx.operation, ctx->counter, sizeof(ctx->counter),
p, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, p)) != 0) {
+ goto exit;
+ }
#endif
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
@@ -374,12 +374,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Update key and counter
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -387,6 +382,11 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
memcpy(ctx->counter, tmp + MBEDTLS_CTR_DRBG_KEYSIZE,
MBEDTLS_CTR_DRBG_BLOCKSIZE);
@@ -564,12 +564,7 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
good_nonce_len(ctx->entropy_len));
/* Initialize with an empty key. */
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- return ret;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, key, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -577,6 +572,11 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
return status;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ return ret;
+ }
#endif
/* Do the initial seeding. */
@@ -655,12 +655,7 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, locals.tmp)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
@@ -670,6 +665,11 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, locals.tmp)) != 0) {
+ goto exit;
+ }
#endif
use_len = (output_len > MBEDTLS_CTR_DRBG_BLOCKSIZE)
diff --git a/thirdparty/mbedtls/library/entropy.c b/thirdparty/mbedtls/library/entropy.c
index e3bc8516e2..7dcf067a52 100644
--- a/thirdparty/mbedtls/library/entropy.c
+++ b/thirdparty/mbedtls/library/entropy.c
@@ -61,6 +61,10 @@ void mbedtls_entropy_init(mbedtls_entropy_context *ctx)
void mbedtls_entropy_free(mbedtls_entropy_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
/* If the context was already free, don't call free() again.
* This is important for mutexes which don't allow double-free. */
if (ctx->accumulator_started == -1) {
diff --git a/thirdparty/mbedtls/library/entropy_poll.c b/thirdparty/mbedtls/library/entropy_poll.c
index 794ee03a83..611768cd85 100644
--- a/thirdparty/mbedtls/library/entropy_poll.c
+++ b/thirdparty/mbedtls/library/entropy_poll.c
@@ -5,10 +5,12 @@
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
-#if defined(__linux__) || defined(__midipix__) && !defined(_GNU_SOURCE)
+#if defined(__linux__) || defined(__midipix__)
/* Ensure that syscall() is available even when compiling with -std=c99 */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
+#endif
#include "common.h"
diff --git a/thirdparty/mbedtls/library/error.c b/thirdparty/mbedtls/library/error.c
index 84b637aeb2..6ad7162ab5 100644
--- a/thirdparty/mbedtls/library/error.c
+++ b/thirdparty/mbedtls/library/error.c
@@ -418,7 +418,7 @@ const char *mbedtls_high_level_strerr(int error_code)
case -(MBEDTLS_ERR_SSL_BAD_CERTIFICATE):
return( "SSL - Processing of the Certificate handshake message failed" );
case -(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET):
- return( "SSL - * Received NewSessionTicket Post Handshake Message. This error code is experimental and may be changed or removed without notice" );
+ return( "SSL - A TLS 1.3 NewSessionTicket message has been received" );
case -(MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA):
return( "SSL - Not possible to read early data" );
case -(MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA):
diff --git a/thirdparty/mbedtls/library/lmots.c b/thirdparty/mbedtls/library/lmots.c
index c7091b49e1..c51cb41ece 100644
--- a/thirdparty/mbedtls/library/lmots.c
+++ b/thirdparty/mbedtls/library/lmots.c
@@ -387,6 +387,10 @@ void mbedtls_lmots_public_init(mbedtls_lmots_public_t *ctx)
void mbedtls_lmots_public_free(mbedtls_lmots_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -556,6 +560,10 @@ void mbedtls_lmots_private_init(mbedtls_lmots_private_t *ctx)
void mbedtls_lmots_private_free(mbedtls_lmots_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx,
sizeof(*ctx));
}
diff --git a/thirdparty/mbedtls/library/lms.c b/thirdparty/mbedtls/library/lms.c
index 8d3cae0524..7f7bec068b 100644
--- a/thirdparty/mbedtls/library/lms.c
+++ b/thirdparty/mbedtls/library/lms.c
@@ -229,6 +229,10 @@ void mbedtls_lms_public_init(mbedtls_lms_public_t *ctx)
void mbedtls_lms_public_free(mbedtls_lms_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -528,6 +532,10 @@ void mbedtls_lms_private_init(mbedtls_lms_private_t *ctx)
void mbedtls_lms_private_free(mbedtls_lms_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
unsigned int idx;
if (ctx->have_private_key) {
diff --git a/thirdparty/mbedtls/library/md.c b/thirdparty/mbedtls/library/md.c
index 12a3ea2374..c95846aa04 100644
--- a/thirdparty/mbedtls/library/md.c
+++ b/thirdparty/mbedtls/library/md.c
@@ -41,7 +41,7 @@
#include "mbedtls/sha512.h"
#include "mbedtls/sha3.h"
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
#include <psa/crypto.h>
#include "md_psa.h"
#include "psa_util_internal.h"
@@ -761,13 +761,13 @@ mbedtls_md_type_t mbedtls_md_get_type(const mbedtls_md_info_t *md_info)
return md_info->type;
}
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
int mbedtls_md_error_from_psa(psa_status_t status)
{
return PSA_TO_MBEDTLS_ERR_LIST(status, psa_to_md_errors,
psa_generic_status_to_mbedtls);
}
-#endif /* MBEDTLS_PSA_CRYPTO_C */
+#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */
/************************************************************************
diff --git a/thirdparty/mbedtls/library/net_sockets.c b/thirdparty/mbedtls/library/net_sockets.c
index edec5876ad..ef89a88ef0 100644
--- a/thirdparty/mbedtls/library/net_sockets.c
+++ b/thirdparty/mbedtls/library/net_sockets.c
@@ -683,7 +683,7 @@ void mbedtls_net_close(mbedtls_net_context *ctx)
*/
void mbedtls_net_free(mbedtls_net_context *ctx)
{
- if (ctx->fd == -1) {
+ if (ctx == NULL || ctx->fd == -1) {
return;
}
diff --git a/thirdparty/mbedtls/library/nist_kw.c b/thirdparty/mbedtls/library/nist_kw.c
index f15425b8bd..8faafe43f1 100644
--- a/thirdparty/mbedtls/library/nist_kw.c
+++ b/thirdparty/mbedtls/library/nist_kw.c
@@ -102,6 +102,10 @@ int mbedtls_nist_kw_setkey(mbedtls_nist_kw_context *ctx,
*/
void mbedtls_nist_kw_free(mbedtls_nist_kw_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_cipher_free(&ctx->cipher_ctx);
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_nist_kw_context));
}
diff --git a/thirdparty/mbedtls/library/pem.c b/thirdparty/mbedtls/library/pem.c
index 0fee5df43a..0207601456 100644
--- a/thirdparty/mbedtls/library/pem.c
+++ b/thirdparty/mbedtls/library/pem.c
@@ -481,6 +481,10 @@ int mbedtls_pem_read_buffer(mbedtls_pem_context *ctx, const char *header, const
void mbedtls_pem_free(mbedtls_pem_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
if (ctx->buf != NULL) {
mbedtls_zeroize_and_free(ctx->buf, ctx->buflen);
}
diff --git a/thirdparty/mbedtls/library/pk.c b/thirdparty/mbedtls/library/pk.c
index 097777f2c0..3fe51ea34f 100644
--- a/thirdparty/mbedtls/library/pk.c
+++ b/thirdparty/mbedtls/library/pk.c
@@ -868,7 +868,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
psa_status_t status;
psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
psa_key_type_t key_type;
- psa_algorithm_t alg_type;
size_t key_bits;
/* Use a buffer size large enough to contain either a key pair or public key. */
unsigned char exp_key[PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE];
@@ -899,7 +898,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type);
}
key_bits = psa_get_key_bits(&key_attr);
- alg_type = psa_get_key_algorithm(&key_attr);
#if defined(MBEDTLS_RSA_C)
if ((key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) ||
@@ -919,6 +917,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
goto exit;
}
+ psa_algorithm_t alg_type = psa_get_key_algorithm(&key_attr);
mbedtls_md_type_t md_type = MBEDTLS_MD_NONE;
if (PSA_ALG_GET_HASH(alg_type) != PSA_ALG_ANY_HASH) {
md_type = mbedtls_md_type_from_psa_alg(alg_type);
@@ -968,6 +967,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
} else
#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
{
+ (void) key_bits;
return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
}
@@ -1327,43 +1327,19 @@ int mbedtls_pk_sign_ext(mbedtls_pk_type_t pk_type,
}
if (mbedtls_pk_get_type(ctx) == MBEDTLS_PK_OPAQUE) {
- psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
- psa_algorithm_t psa_alg, sign_alg;
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_algorithm_t psa_enrollment_alg;
-#endif /* MBEDTLS_PSA_CRYPTO_C */
psa_status_t status;
- status = psa_get_key_attributes(ctx->priv_id, &key_attr);
- if (status != PSA_SUCCESS) {
- return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
- }
- psa_alg = psa_get_key_algorithm(&key_attr);
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_enrollment_alg = psa_get_key_enrollment_algorithm(&key_attr);
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- psa_reset_key_attributes(&key_attr);
-
- /* Since we're PK type is MBEDTLS_PK_RSASSA_PSS at least one between
- * alg and enrollment alg should be of type RSA_PSS. */
- if (PSA_ALG_IS_RSA_PSS(psa_alg)) {
- sign_alg = psa_alg;
- }
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- else if (PSA_ALG_IS_RSA_PSS(psa_enrollment_alg)) {
- sign_alg = psa_enrollment_alg;
- }
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- else {
- /* The opaque key has no RSA PSS algorithm associated. */
- return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
- }
- /* Adjust the hashing algorithm. */
- sign_alg = (sign_alg & ~PSA_ALG_HASH_MASK) | PSA_ALG_GET_HASH(psa_md_alg);
-
- status = psa_sign_hash(ctx->priv_id, sign_alg,
+ /* PSA_ALG_RSA_PSS() behaves the same as PSA_ALG_RSA_PSS_ANY_SALT() when
+ * performing a signature, but they are encoded differently. Instead of
+ * extracting the proper one from the wrapped key policy, just try both. */
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS(psa_md_alg),
hash, hash_len,
sig, sig_size, sig_len);
+ if (status == PSA_ERROR_NOT_PERMITTED) {
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS_ANY_SALT(psa_md_alg),
+ hash, hash_len,
+ sig, sig_size, sig_len);
+ }
return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
}
diff --git a/thirdparty/mbedtls/library/platform_util.c b/thirdparty/mbedtls/library/platform_util.c
index 0741bf575e..19ef07aead 100644
--- a/thirdparty/mbedtls/library/platform_util.c
+++ b/thirdparty/mbedtls/library/platform_util.c
@@ -149,7 +149,7 @@ void mbedtls_zeroize_and_free(void *buf, size_t len)
#include <time.h>
#if !defined(_WIN32) && (defined(unix) || \
defined(__unix) || defined(__unix__) || (defined(__APPLE__) && \
- defined(__MACH__)) || defined__midipix__)
+ defined(__MACH__)) || defined(__midipix__))
#include <unistd.h>
#endif /* !_WIN32 && (unix || __unix || __unix__ ||
* (__APPLE__ && __MACH__) || __midipix__) */
diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
index c059162efe..21e7559f01 100644
--- a/thirdparty/mbedtls/library/psa_crypto_core.h
+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
@@ -59,6 +59,8 @@ typedef enum {
* and metadata for one key.
*/
typedef struct {
+ /* This field is accessed in a lot of places. Putting it first
+ * reduces the code size. */
psa_key_attributes_t attr;
/*
@@ -78,35 +80,77 @@ typedef struct {
* slots that are in a suitable state for the function.
* For example, psa_get_and_lock_key_slot_in_memory, which finds a slot
* containing a given key ID, will only check slots whose state variable is
- * PSA_SLOT_FULL. */
+ * PSA_SLOT_FULL.
+ */
psa_key_slot_state_t state;
- /*
- * Number of functions registered as reading the material in the key slot.
- *
- * Library functions must not write directly to registered_readers
- *
- * A function must call psa_register_read(slot) before reading the current
- * contents of the slot for an operation.
- * They then must call psa_unregister_read(slot) once they have finished
- * reading the current contents of the slot. If the key slot mutex is not
- * held (when mutexes are enabled), this call must be done via a call to
- * psa_unregister_read_under_mutex(slot).
- * A function must call psa_key_slot_has_readers(slot) to check if
- * the slot is in use for reading.
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ /* The index of the slice containing this slot.
+ * This field must be filled if the slot contains a key
+ * (including keys being created or destroyed), and can be either
+ * filled or 0 when the slot is free.
*
- * This counter is used to prevent resetting the key slot while the library
- * may access it. For example, such control is needed in the following
- * scenarios:
- * . In case of key slot starvation, all key slots contain the description
- * of a key, and the library asks for the description of a persistent
- * key not present in the key slots, the key slots currently accessed by
- * the library cannot be reclaimed to free a key slot to load the
- * persistent key.
- * . In case of a multi-threaded application where one thread asks to close
- * or purge or destroy a key while it is in use by the library through
- * another thread. */
- size_t registered_readers;
+ * In most cases, the slice index can be deduced from the key identifer.
+ * We keep it in a separate field for robustness (it reduces the chance
+ * that a coding mistake in the key store will result in accessing the
+ * wrong slice), and also so that it's available even on code paths
+ * during creation or destruction where the key identifier might not be
+ * filled in.
+ * */
+ uint8_t slice_index;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+ union {
+ struct {
+ /* The index of the next slot in the free list for this
+ * slice, relative * to the next array element.
+ *
+ * That is, 0 means the next slot, 1 means the next slot
+ * but one, etc. -1 would mean the slot itself. -2 means
+ * the previous slot, etc.
+ *
+ * If this is beyond the array length, the free list ends with the
+ * current element.
+ *
+ * The reason for this strange encoding is that 0 means the next
+ * element. This way, when we allocate a slice and initialize it
+ * to all-zero, the slice is ready for use, with a free list that
+ * consists of all the slots in order.
+ */
+ int32_t next_free_relative_to_next;
+ } free;
+
+ struct {
+ /*
+ * Number of functions registered as reading the material in the key slot.
+ *
+ * Library functions must not write directly to registered_readers
+ *
+ * A function must call psa_register_read(slot) before reading
+ * the current contents of the slot for an operation.
+ * They then must call psa_unregister_read(slot) once they have
+ * finished reading the current contents of the slot. If the key
+ * slot mutex is not held (when mutexes are enabled), this call
+ * must be done via a call to
+ * psa_unregister_read_under_mutex(slot).
+ * A function must call psa_key_slot_has_readers(slot) to check if
+ * the slot is in use for reading.
+ *
+ * This counter is used to prevent resetting the key slot while
+ * the library may access it. For example, such control is needed
+ * in the following scenarios:
+ * . In case of key slot starvation, all key slots contain the
+ * description of a key, and the library asks for the
+ * description of a persistent key not present in the
+ * key slots, the key slots currently accessed by the
+ * library cannot be reclaimed to free a key slot to load
+ * the persistent key.
+ * . In case of a multi-threaded application where one thread
+ * asks to close or purge or destroy a key while it is in use
+ * by the library through another thread. */
+ size_t registered_readers;
+ } occupied;
+ } var;
/* Dynamically allocated key data buffer.
* Format as specified in psa_export_key(). */
@@ -169,7 +213,7 @@ typedef struct {
*/
static inline int psa_key_slot_has_readers(const psa_key_slot_t *slot)
{
- return slot->registered_readers > 0;
+ return slot->var.occupied.registered_readers > 0;
}
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
@@ -343,19 +387,18 @@ psa_status_t psa_export_public_key_internal(
const uint8_t *key_buffer, size_t key_buffer_size,
uint8_t *data, size_t data_size, size_t *data_length);
-/** Whether a key production parameters structure is the default.
+/** Whether a key custom production parameters structure is the default.
*
- * Calls to a key generation driver with non-default production parameters
+ * Calls to a key generation driver with non-default custom production parameters
* require a driver supporting custom production parameters.
*
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
+ * \param[in] custom The key custom production parameters to check.
+ * \param custom_data_length Size of the associated variable-length data
+ * in bytes.
*/
-#ifndef __cplusplus
-int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-#endif
+int psa_custom_key_parameters_are_default(
+ const psa_custom_key_parameters_t *custom,
+ size_t custom_data_length);
/**
* \brief Generate a key.
@@ -364,9 +407,9 @@ int psa_key_production_parameters_are_default(
* entry point.
*
* \param[in] attributes The attributes for the key to generate.
- * \param[in] params The production parameters from
- * psa_generate_key_ext().
- * \param params_data_length The size of `params->data` in bytes.
+ * \param[in] custom Custom parameters for the key generation.
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length Length of `custom_data` in bytes.
* \param[out] key_buffer Buffer where the key data is to be written.
* \param[in] key_buffer_size Size of \p key_buffer in bytes.
* \param[out] key_buffer_length On success, the number of bytes written in
@@ -380,14 +423,13 @@ int psa_key_production_parameters_are_default(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
uint8_t *key_buffer,
size_t key_buffer_size,
size_t *key_buffer_length);
-#endif
/** Sign a message with a private key. For hash-and-sign algorithms,
* this includes the hashing step.
diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
index 6919971aca..b901557208 100644
--- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
@@ -728,10 +728,10 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
}
}
-#ifndef __cplusplus
static inline psa_status_t psa_driver_wrapper_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length )
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
@@ -740,7 +740,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE)
int is_default_production =
- psa_key_production_parameters_are_default(params, params_data_length);
+ psa_custom_key_parameters_are_default(custom, custom_data_length);
if( location != PSA_KEY_LOCATION_LOCAL_STORAGE && !is_default_production )
{
/* We don't support passing custom production parameters
@@ -811,7 +811,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
/* Software fallback */
status = psa_generate_key_internal(
- attributes, params, params_data_length,
+ attributes, custom, custom_data, custom_data_length,
key_buffer, key_buffer_size, key_buffer_length );
break;
@@ -833,7 +833,6 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
return( status );
}
-#endif
static inline psa_status_t psa_driver_wrapper_import_key(
const psa_key_attributes_t *attributes,
diff --git a/thirdparty/mbedtls/library/psa_crypto_random_impl.h b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
index 533fb2e940..5b5163111b 100644
--- a/thirdparty/mbedtls/library/psa_crypto_random_impl.h
+++ b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
@@ -21,13 +21,10 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#include "mbedtls/entropy.h"
/* Choose a DRBG based on configuration and availability */
-#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE)
-
-#include "mbedtls/hmac_drbg.h"
-
-#elif defined(MBEDTLS_CTR_DRBG_C)
+#if defined(MBEDTLS_CTR_DRBG_C)
#include "mbedtls/ctr_drbg.h"
+#undef MBEDTLS_PSA_HMAC_DRBG_MD_TYPE
#elif defined(MBEDTLS_HMAC_DRBG_C)
@@ -49,17 +46,11 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#error "No hash algorithm available for HMAC_DBRG."
#endif
-#else /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
+#else /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
#error "No DRBG module available for the psa_crypto module."
-#endif /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
-
-#if defined(MBEDTLS_CTR_DRBG_C)
-#include "mbedtls/ctr_drbg.h"
-#elif defined(MBEDTLS_HMAC_DRBG_C)
-#include "mbedtls/hmac_drbg.h"
-#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */
+#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
/* The maximum number of bytes that mbedtls_psa_get_random() is expected to return. */
#if defined(MBEDTLS_CTR_DRBG_C)
diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
index 6d695ddf50..1a780006a9 100644
--- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
@@ -105,17 +105,11 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
/**
* \brief Generate an RSA key.
*
- * \note The signature of the function is that of a PSA driver generate_key
- * entry point.
- *
* \param[in] attributes The attributes for the RSA key to generate.
- * \param[in] params Production parameters for the key
- * generation. This function only uses
- * `params->data`,
- * which contains the public exponent.
+ * \param[in] custom_data The public exponent to use.
* This can be a null pointer if
* \c params_data_length is 0.
- * \param params_data_length Length of `params->data` in bytes.
+ * \param custom_data_length Length of \p custom_data in bytes.
* This can be 0, in which case the
* public exponent will be 65537.
* \param[out] key_buffer Buffer where the key data is to be written.
@@ -130,12 +124,10 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t mbedtls_psa_rsa_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-#endif
/** Sign an already-calculated hash with an RSA private key.
*
diff --git a/thirdparty/mbedtls/library/psa_crypto_slot_management.h b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
index bcfc9d8adc..af1208e3ae 100644
--- a/thirdparty/mbedtls/library/psa_crypto_slot_management.h
+++ b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
@@ -15,20 +15,26 @@
/** Range of volatile key identifiers.
*
- * The last #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
+ * The first #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
* range of key identifiers are reserved for volatile key identifiers.
- * A volatile key identifier is equal to #PSA_KEY_ID_VOLATILE_MIN plus the
- * index of the key slot containing the volatile key definition.
+ *
+ * If \c id is a a volatile key identifier, #PSA_KEY_ID_VOLATILE_MIN - \c id
+ * indicates the key slot containing the volatile key definition. See
+ * psa_crypto_slot_management.c for details.
*/
/** The minimum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MIN (PSA_KEY_ID_VENDOR_MAX - \
- MBEDTLS_PSA_KEY_SLOT_COUNT + 1)
+#define PSA_KEY_ID_VOLATILE_MIN PSA_KEY_ID_VENDOR_MIN
/** The maximum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MAX PSA_KEY_ID_VENDOR_MAX
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+#define PSA_KEY_ID_VOLATILE_MAX (MBEDTLS_PSA_KEY_ID_BUILTIN_MIN - 1)
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+#define PSA_KEY_ID_VOLATILE_MAX \
+ (PSA_KEY_ID_VOLATILE_MIN + MBEDTLS_PSA_KEY_SLOT_COUNT - 1)
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
/** Test whether a key identifier is a volatile key identifier.
*
@@ -58,6 +64,9 @@ static inline int psa_key_id_is_volatile(psa_key_id_t key_id)
* It is the responsibility of the caller to call psa_unregister_read(slot)
* when they have finished reading the contents of the slot.
*
+ * On failure, `*p_slot` is set to NULL. This ensures that it is always valid
+ * to call psa_unregister_read on the returned slot.
+ *
* \param key Key identifier to query.
* \param[out] p_slot On success, `*p_slot` contains a pointer to the
* key slot containing the description of the key
@@ -91,6 +100,24 @@ psa_status_t psa_get_and_lock_key_slot(mbedtls_svc_key_id_t key,
*/
psa_status_t psa_initialize_key_slots(void);
+#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/* Allow test code to customize the key slice length. We use this in tests
+ * that exhaust the key store to reach a full key store in reasonable time
+ * and memory.
+ *
+ * The length of each slice must be between 1 and
+ * (1 << KEY_ID_SLOT_INDEX_WIDTH) inclusive.
+ *
+ * The length for a given slice index must not change while
+ * the key store is initialized.
+ */
+extern size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)(
+ size_t slice_idx);
+
+/* The number of volatile key slices. */
+size_t psa_key_slot_volatile_slice_count(void);
+#endif
+
/** Delete all data from key slots in memory.
* This function is not thread safe, it wipes every key slot regardless of
* state and reader count. It should only be called when no slot is in use.
@@ -110,13 +137,22 @@ void psa_wipe_all_key_slots(void);
* If multi-threading is enabled, the caller must hold the
* global key slot mutex.
*
- * \param[out] volatile_key_id On success, volatile key identifier
- * associated to the returned slot.
+ * \param[out] volatile_key_id - If null, reserve a cache slot for
+ * a persistent or built-in key.
+ * - If non-null, allocate a slot for
+ * a volatile key. On success,
+ * \p *volatile_key_id is the
+ * identifier corresponding to the
+ * returned slot. It is the caller's
+ * responsibility to set this key identifier
+ * in the attributes.
* \param[out] p_slot On success, a pointer to the slot.
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* There were no free key slots.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, there was not
+ * enough memory to allocate more slots.
* \retval #PSA_ERROR_BAD_STATE \emptydescription
* \retval #PSA_ERROR_CORRUPTION_DETECTED
* This function attempted to operate on a key slot which was in an
@@ -125,6 +161,29 @@ void psa_wipe_all_key_slots(void);
psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
psa_key_slot_t **p_slot);
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/** Return a key slot to the free list.
+ *
+ * Call this function when a slot obtained from psa_reserve_free_key_slot()
+ * is no longer in use.
+ *
+ * If multi-threading is enabled, the caller must hold the
+ * global key slot mutex.
+ *
+ * \param slice_idx The slice containing the slot.
+ * This is `slot->slice_index` when the slot
+ * is obtained from psa_reserve_free_key_slot().
+ * \param slot The key slot.
+ *
+ * \retval #PSA_SUCCESS \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED
+ * This function attempted to operate on a key slot which was in an
+ * unexpected state.
+ */
+psa_status_t psa_free_key_slot(size_t slice_idx,
+ psa_key_slot_t *slot);
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
/** Change the state of a key slot.
*
* This function changes the state of the key slot from expected_state to
@@ -171,10 +230,10 @@ static inline psa_status_t psa_key_slot_state_transition(
static inline psa_status_t psa_register_read(psa_key_slot_t *slot)
{
if ((slot->state != PSA_SLOT_FULL) ||
- (slot->registered_readers >= SIZE_MAX)) {
+ (slot->var.occupied.registered_readers >= SIZE_MAX)) {
return PSA_ERROR_CORRUPTION_DETECTED;
}
- slot->registered_readers++;
+ slot->var.occupied.registered_readers++;
return PSA_SUCCESS;
}
diff --git a/thirdparty/mbedtls/library/rsa.c b/thirdparty/mbedtls/library/rsa.c
index 7eb4a259ea..557faaf363 100644
--- a/thirdparty/mbedtls/library/rsa.c
+++ b/thirdparty/mbedtls/library/rsa.c
@@ -29,6 +29,7 @@
#include "mbedtls/rsa.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "rsa_alt_helpers.h"
#include "rsa_internal.h"
#include "mbedtls/oid.h"
@@ -1259,7 +1260,7 @@ int mbedtls_rsa_public(mbedtls_rsa_context *ctx,
}
olen = ctx->len;
- MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
+ MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod_unsafe(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&T, output, olen));
cleanup:
diff --git a/thirdparty/mbedtls/library/sha256.c b/thirdparty/mbedtls/library/sha256.c
index 87889817a4..159acccaeb 100644
--- a/thirdparty/mbedtls/library/sha256.c
+++ b/thirdparty/mbedtls/library/sha256.c
@@ -44,7 +44,9 @@
#endif /* defined(__clang__) && (__clang_major__ >= 4) */
/* Ensure that SIG_SETMASK is defined when -std=c99 is used. */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
+#endif
#include "common.h"
@@ -150,7 +152,9 @@ static int mbedtls_a64_crypto_sha256_determine_support(void)
return 1;
}
#elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <Windows.h>
#include <processthreadsapi.h>
diff --git a/thirdparty/mbedtls/library/ssl_cookie.c b/thirdparty/mbedtls/library/ssl_cookie.c
index 2772cac4be..acc9e8c080 100644
--- a/thirdparty/mbedtls/library/ssl_cookie.c
+++ b/thirdparty/mbedtls/library/ssl_cookie.c
@@ -84,6 +84,10 @@ void mbedtls_ssl_cookie_set_timeout(mbedtls_ssl_cookie_ctx *ctx, unsigned long d
void mbedtls_ssl_cookie_free(mbedtls_ssl_cookie_ctx *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->psa_hmac_key);
#else
diff --git a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
index f8b4448c86..734c417b8b 100644
--- a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
+++ b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
@@ -60,7 +60,7 @@ const char *mbedtls_ssl_named_group_to_str( uint16_t in )
return "ffdhe8192";
};
- return "UNKOWN";
+ return "UNKNOWN";
}
const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
{
diff --git a/thirdparty/mbedtls/library/ssl_misc.h b/thirdparty/mbedtls/library/ssl_misc.h
index a8807f67c6..98668798a8 100644
--- a/thirdparty/mbedtls/library/ssl_misc.h
+++ b/thirdparty/mbedtls/library/ssl_misc.h
@@ -1507,7 +1507,7 @@ int mbedtls_ssl_psk_derive_premaster(mbedtls_ssl_context *ssl,
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */
#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_PSK_ENABLED)
-#if defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C) || defined(MBEDTLS_SSL_SRV_C)
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_conf_has_static_psk(mbedtls_ssl_config const *conf);
#endif
@@ -1674,18 +1674,53 @@ static inline mbedtls_x509_crt *mbedtls_ssl_own_cert(mbedtls_ssl_context *ssl)
}
/*
- * Check usage of a certificate wrt extensions:
- * keyUsage, extendedKeyUsage (later), and nSCertType (later).
+ * Verify a certificate.
+ *
+ * [in/out] ssl: misc. things read
+ * ssl->session_negotiate->verify_result updated
+ * [in] authmode: one of MBEDTLS_SSL_VERIFY_{NONE,OPTIONAL,REQUIRED}
+ * [in] chain: the certificate chain to verify (ie the peer's chain)
+ * [in] ciphersuite_info: For TLS 1.2, this session's ciphersuite;
+ * for TLS 1.3, may be left NULL.
+ * [in] rs_ctx: restart context if restartable ECC is in use;
+ * leave NULL for no restartable behaviour.
+ *
+ * Return:
+ * - 0 if the handshake should continue. Depending on the
+ * authmode it means:
+ * - REQUIRED: the certificate was found to be valid, trusted & acceptable.
+ * ssl->session_negotiate->verify_result is 0.
+ * - OPTIONAL: the certificate may or may not be acceptable, but
+ * ssl->session_negotiate->verify_result was updated with the result.
+ * - NONE: the certificate wasn't even checked.
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED or MBEDTLS_ERR_SSL_BAD_CERTIFICATE if
+ * the certificate was found to be invalid/untrusted/unacceptable and the
+ * handshake should be aborted (can only happen with REQUIRED).
+ * - another error code if another error happened (out-of-memory, etc.)
+ */
+MBEDTLS_CHECK_RETURN_CRITICAL
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx);
+
+/*
+ * Check usage of a certificate wrt usage extensions:
+ * keyUsage and extendedKeyUsage.
+ * (Note: nSCertType is deprecated and not standard, we don't check it.)
*
- * Warning: cert_endpoint is the endpoint of the cert (ie, of our peer when we
- * check a cert we received from them)!
+ * Note: if tls_version is 1.3, ciphersuite is ignored and can be NULL.
+ *
+ * Note: recv_endpoint is the receiver's endpoint.
*
* Return 0 if everything is OK, -1 if not.
*/
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
uint32_t *flags);
#endif /* MBEDTLS_X509_CRT_PARSE_C */
@@ -1891,6 +1926,26 @@ static inline int mbedtls_ssl_conf_is_hybrid_tls12_tls13(const mbedtls_ssl_confi
#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_SSL_PROTO_TLS1_3 */
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+
+/** \brief Initialize the PSA crypto subsystem if necessary.
+ *
+ * Call this function before doing any cryptography in a TLS 1.3 handshake.
+ *
+ * This is necessary in Mbed TLS 3.x for backward compatibility.
+ * Up to Mbed TLS 3.5, in the default configuration, you could perform
+ * a TLS connection with default parameters without having called
+ * psa_crypto_init(), since the TLS layer only supported TLS 1.2 and
+ * did not use PSA crypto. (TLS 1.2 only uses PSA crypto if
+ * MBEDTLS_USE_PSA_CRYPTO is enabled, which is not the case in the default
+ * configuration.) Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled
+ * by default, and the TLS 1.3 layer uses PSA crypto. This means that
+ * applications that are not otherwise using PSA crypto and that worked
+ * with Mbed TLS 3.5 started failing in TLS 3.6.0 if they connected to
+ * a peer that supports TLS 1.3. See
+ * https://github.com/Mbed-TLS/mbedtls/issues/9072
+ */
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl);
+
extern const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN];
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -2914,8 +2969,37 @@ static inline void mbedtls_ssl_tls13_session_clear_ticket_flags(
{
session->ticket_flags &= ~(flags & MBEDTLS_SSL_TLS1_3_TICKET_FLAGS_MASK);
}
+
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 && MBEDTLS_SSL_SESSION_TICKETS */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT 0
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT 1
+
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT)
+
+static inline int mbedtls_ssl_conf_get_session_tickets(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK ?
+ MBEDTLS_SSL_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED;
+}
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+static inline int mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK ?
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+
#if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_PROTO_TLS1_3)
int mbedtls_ssl_tls13_finalize_client_hello(mbedtls_ssl_context *ssl);
#endif
diff --git a/thirdparty/mbedtls/library/ssl_msg.c b/thirdparty/mbedtls/library/ssl_msg.c
index b07cd96f1b..ef722d7bdc 100644
--- a/thirdparty/mbedtls/library/ssl_msg.c
+++ b/thirdparty/mbedtls/library/ssl_msg.c
@@ -5570,9 +5570,9 @@ static int ssl_check_ctr_renegotiate(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
+static int ssl_tls13_is_new_session_ticket(mbedtls_ssl_context *ssl)
{
if ((ssl->in_hslen == mbedtls_ssl_hs_hdr_len(ssl)) ||
@@ -5580,15 +5580,9 @@ static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
return 0;
}
- ssl->keep_current_message = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
- mbedtls_ssl_handshake_set_state(ssl,
- MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
-
- return MBEDTLS_ERR_SSL_WANT_READ;
+ return 1;
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
@@ -5596,14 +5590,29 @@ static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
MBEDTLS_SSL_DEBUG_MSG(3, ("received post-handshake message"));
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- int ret = ssl_tls13_check_new_session_ticket(ssl);
- if (ret != 0) {
- return ret;
+ if (ssl_tls13_is_new_session_ticket(ssl)) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+ MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
+ if (mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(ssl->conf) ==
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED) {
+ ssl->keep_current_message = 1;
+
+ mbedtls_ssl_handshake_set_state(ssl,
+ MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
+ return MBEDTLS_ERR_SSL_WANT_READ;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, handling disabled."));
+ return 0;
+ }
+#else
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, not supported."));
+ return 0;
+#endif
}
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
/* Fail in all other cases. */
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
diff --git a/thirdparty/mbedtls/library/ssl_ticket.c b/thirdparty/mbedtls/library/ssl_ticket.c
index 6a31b0bee6..bfb656cf62 100644
--- a/thirdparty/mbedtls/library/ssl_ticket.c
+++ b/thirdparty/mbedtls/library/ssl_ticket.c
@@ -534,6 +534,10 @@ cleanup:
*/
void mbedtls_ssl_ticket_free(mbedtls_ssl_ticket_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->keys[0].key);
psa_destroy_key(ctx->keys[1].key);
diff --git a/thirdparty/mbedtls/library/ssl_tls.c b/thirdparty/mbedtls/library/ssl_tls.c
index c5e06491c1..c773365bf6 100644
--- a/thirdparty/mbedtls/library/ssl_tls.c
+++ b/thirdparty/mbedtls/library/ssl_tls.c
@@ -132,7 +132,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len)
{
*enabled = MBEDTLS_SSL_CID_DISABLED;
@@ -1354,29 +1354,6 @@ static int ssl_conf_check(const mbedtls_ssl_context *ssl)
return ret;
}
-#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
- /* RFC 8446 section 4.4.3
- *
- * If the verification fails, the receiver MUST terminate the handshake with
- * a "decrypt_error" alert.
- *
- * If the client is configured as TLS 1.3 only with optional verify, return
- * bad config.
- *
- */
- if (mbedtls_ssl_conf_tls13_is_ephemeral_enabled(
- (mbedtls_ssl_context *) ssl) &&
- ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT &&
- ssl->conf->max_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->min_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->authmode == MBEDTLS_SSL_VERIFY_OPTIONAL) {
- MBEDTLS_SSL_DEBUG_MSG(
- 1, ("Optional verify auth mode "
- "is not available for TLS 1.3 client"));
- return MBEDTLS_ERR_SSL_BAD_CONFIG;
- }
-#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
-
if (ssl->conf->f_rng == NULL) {
MBEDTLS_SSL_DEBUG_MSG(1, ("no RNG provided"));
return MBEDTLS_ERR_SSL_NO_RNG;
@@ -1760,6 +1737,7 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
if (session->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
mbedtls_ssl_ciphersuite_from_id(session->ciphersuite);
@@ -1770,6 +1748,14 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
session->ciphersuite));
return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
}
+#else
+ /*
+ * If session tickets are not enabled, it is not possible to resume a
+ * TLS 1.3 session, thus do not make any change to the SSL context in
+ * the first place.
+ */
+ return 0;
+#endif
}
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
@@ -2234,6 +2220,7 @@ static void ssl_remove_psk(mbedtls_ssl_context *ssl)
mbedtls_zeroize_and_free(ssl->handshake->psk,
ssl->handshake->psk_len);
ssl->handshake->psk_len = 0;
+ ssl->handshake->psk = NULL;
}
#endif /* MBEDTLS_USE_PSA_CRYPTO */
}
@@ -2999,11 +2986,24 @@ void mbedtls_ssl_conf_renegotiation_period(mbedtls_ssl_config *conf,
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
#if defined(MBEDTLS_SSL_CLI_C)
+
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets)
{
- conf->session_tickets = use_tickets;
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK;
+ conf->session_tickets |= (use_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT;
}
-#endif
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets)
+{
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK;
+ conf->session_tickets |= (signal_new_session_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SRV_C)
@@ -4049,7 +4049,7 @@ static int ssl_tls13_session_save(const mbedtls_ssl_session *session,
}
static int ssl_tls13_session_load(const mbedtls_ssl_session *session,
- unsigned char *buf,
+ const unsigned char *buf,
size_t buf_len)
{
((void) session);
@@ -5868,7 +5868,33 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
if (endpoint == MBEDTLS_SSL_IS_CLIENT) {
conf->authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
- conf->session_tickets = MBEDTLS_SSL_SESSION_TICKETS_ENABLED;
+ mbedtls_ssl_conf_session_tickets(conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED);
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+ /* Contrary to TLS 1.2 tickets, TLS 1.3 NewSessionTicket message
+ * handling is disabled by default in Mbed TLS 3.6.x for backward
+ * compatibility with client applications developed using Mbed TLS 3.5
+ * or earlier with the default configuration.
+ *
+ * Up to Mbed TLS 3.5, in the default configuration TLS 1.3 was
+ * disabled, and a Mbed TLS client with the default configuration would
+ * establish a TLS 1.2 connection with a TLS 1.2 and TLS 1.3 capable
+ * server.
+ *
+ * Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled by default, and thus
+ * an Mbed TLS client with the default configuration establishes a
+ * TLS 1.3 connection with a TLS 1.2 and TLS 1.3 capable server. If
+ * following the handshake the TLS 1.3 server sends NewSessionTicket
+ * messages and the Mbed TLS client processes them, this results in
+ * Mbed TLS high level APIs (mbedtls_ssl_read(),
+ * mbedtls_ssl_handshake(), ...) to eventually return an
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET non fatal error code
+ * (see the documentation of mbedtls_ssl_read() for more information on
+ * that error code). Applications unaware of that TLS 1.3 specific non
+ * fatal error code are then failing.
+ */
+ mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ conf, MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED);
+#endif
#endif
}
#endif
@@ -6030,6 +6056,10 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
*/
void mbedtls_ssl_config_free(mbedtls_ssl_config *conf)
{
+ if (conf == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_DHM_C)
mbedtls_mpi_free(&conf->dhm_P);
mbedtls_mpi_free(&conf->dhm_G);
@@ -6344,71 +6374,6 @@ const char *mbedtls_ssl_get_curve_name_from_tls_id(uint16_t tls_id)
}
#endif
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
- const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
- uint32_t *flags)
-{
- int ret = 0;
- unsigned int usage = 0;
- const char *ext_oid;
- size_t ext_len;
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- /* Server part of the key exchange */
- switch (ciphersuite->key_exchange) {
- case MBEDTLS_KEY_EXCHANGE_RSA:
- case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
- usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
- usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
- break;
-
- /* Don't use default: we want warnings when adding new values */
- case MBEDTLS_KEY_EXCHANGE_NONE:
- case MBEDTLS_KEY_EXCHANGE_PSK:
- case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
- usage = 0;
- }
- } else {
- /* Client auth: we only implement rsa_sign and mbedtls_ecdsa_sign for now */
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- }
-
- if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
- ret = -1;
- }
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
- ret = -1;
- }
-
- return ret;
-}
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
#if defined(MBEDTLS_USE_PSA_CRYPTO)
int mbedtls_ssl_get_handshake_transcript(mbedtls_ssl_context *ssl,
const mbedtls_md_type_t md,
@@ -7927,196 +7892,6 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl,
return SSL_CERTIFICATE_EXPECTED;
}
-MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_parse_certificate_verify(mbedtls_ssl_context *ssl,
- int authmode,
- mbedtls_x509_crt *chain,
- void *rs_ctx)
-{
- int ret = 0;
- const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
- ssl->handshake->ciphersuite_info;
- int have_ca_chain = 0;
-
- int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
- void *p_vrfy;
-
- if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
- return 0;
- }
-
- if (ssl->f_vrfy != NULL) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
- f_vrfy = ssl->f_vrfy;
- p_vrfy = ssl->p_vrfy;
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
- f_vrfy = ssl->conf->f_vrfy;
- p_vrfy = ssl->conf->p_vrfy;
- }
-
- /*
- * Main check: verify certificate
- */
-#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
- if (ssl->conf->f_ca_cb != NULL) {
- ((void) rs_ctx);
- have_ca_chain = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
- ret = mbedtls_x509_crt_verify_with_ca_cb(
- chain,
- ssl->conf->f_ca_cb,
- ssl->conf->p_ca_cb,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy);
- } else
-#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
- {
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
-
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- if (ca_chain != NULL) {
- have_ca_chain = 1;
- }
-
- ret = mbedtls_x509_crt_verify_restartable(
- chain,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy, rs_ctx);
- }
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
-#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
- if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
- return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
- }
-#endif
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
-
-#if defined(MBEDTLS_PK_HAVE_ECC_KEYS)
- {
- const mbedtls_pk_context *pk = &chain->pk;
-
- /* If certificate uses an EC key, make sure the curve is OK.
- * This is a public key, so it can't be opaque, so can_do() is a good
- * enough check to ensure pk_ec() is safe to use here. */
- if (mbedtls_pk_can_do(pk, MBEDTLS_PK_ECKEY)) {
- /* and in the unlikely case the above assumption no longer holds
- * we are making sure that pk_ec() here does not return a NULL
- */
- mbedtls_ecp_group_id grp_id = mbedtls_pk_get_ec_group_id(pk);
- if (grp_id == MBEDTLS_ECP_DP_NONE) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("invalid group ID"));
- return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
- }
- if (mbedtls_ssl_check_curve(ssl, grp_id) != 0) {
- ssl->session_negotiate->verify_result |=
- MBEDTLS_X509_BADCERT_BAD_KEY;
-
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
- }
- }
-#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
-
- if (mbedtls_ssl_check_cert_usage(chain,
- ciphersuite_info,
- !ssl->conf->endpoint,
- &ssl->session_negotiate->verify_result) != 0) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * ssl_parse_certificate even if verification was optional. */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (have_ca_chain == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- uint8_t alert;
-
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NS_CERT_TYPE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
- } else {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
- }
- mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
- alert);
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (ssl->session_negotiate->verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) ssl->session_negotiate->verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- return ret;
-}
-
#if !defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_remember_peer_crt_digest(mbedtls_ssl_context *ssl,
@@ -8173,6 +7948,7 @@ int mbedtls_ssl_parse_certificate(mbedtls_ssl_context *ssl)
{
int ret = 0;
int crt_expected;
+ /* Authmode: precedence order is SNI if used else configuration */
#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
? ssl->handshake->sni_authmode
@@ -8252,8 +8028,9 @@ crt_verify:
}
#endif
- ret = ssl_parse_certificate_verify(ssl, authmode,
- chain, rs_ctx);
+ ret = mbedtls_ssl_verify_certificate(ssl, authmode, chain,
+ ssl->handshake->ciphersuite_info,
+ rs_ctx);
if (ret != 0) {
goto exit;
}
@@ -9919,4 +9696,274 @@ int mbedtls_ssl_session_set_ticket_alpn(mbedtls_ssl_session *session,
return 0;
}
#endif /* MBEDTLS_SSL_SRV_C && MBEDTLS_SSL_EARLY_DATA && MBEDTLS_SSL_ALPN */
+
+/*
+ * The following functions are used by 1.2 and 1.3, client and server.
+ */
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
+ uint32_t *flags)
+{
+ int ret = 0;
+ unsigned int usage = 0;
+ const char *ext_oid;
+ size_t ext_len;
+
+ /*
+ * keyUsage
+ */
+
+ /* Note: don't guard this with MBEDTLS_SSL_CLI_C because the server wants
+ * to check what a compliant client will think while choosing which cert
+ * to send to the client. */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
+ if (tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ /* TLS 1.2 server part of the key exchange */
+ switch (ciphersuite->key_exchange) {
+ case MBEDTLS_KEY_EXCHANGE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
+ usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
+ usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
+ break;
+
+ /* Don't use default: we want warnings when adding new values */
+ case MBEDTLS_KEY_EXCHANGE_NONE:
+ case MBEDTLS_KEY_EXCHANGE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
+ usage = 0;
+ }
+ } else
+#endif
+ {
+ /* This is either TLS 1.3 authentication, which always uses signatures,
+ * or 1.2 client auth: rsa_sign and mbedtls_ecdsa_sign are the only
+ * options we implement, both using signatures. */
+ (void) tls_version;
+ (void) ciphersuite;
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ }
+
+ if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
+ ret = -1;
+ }
+
+ /*
+ * extKeyUsage
+ */
+
+ if (recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ ext_oid = MBEDTLS_OID_SERVER_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
+ } else {
+ ext_oid = MBEDTLS_OID_CLIENT_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
+ }
+
+ if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx)
+{
+ if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
+ return 0;
+ }
+
+ /*
+ * Primary check: use the appropriate X.509 verification function
+ */
+ int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
+ void *p_vrfy;
+ if (ssl->f_vrfy != NULL) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
+ f_vrfy = ssl->f_vrfy;
+ p_vrfy = ssl->p_vrfy;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
+ f_vrfy = ssl->conf->f_vrfy;
+ p_vrfy = ssl->conf->p_vrfy;
+ }
+
+ int ret = 0;
+ int have_ca_chain_or_callback = 0;
+#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
+ if (ssl->conf->f_ca_cb != NULL) {
+ ((void) rs_ctx);
+ have_ca_chain_or_callback = 1;
+
+ MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
+ ret = mbedtls_x509_crt_verify_with_ca_cb(
+ chain,
+ ssl->conf->f_ca_cb,
+ ssl->conf->p_ca_cb,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy);
+ } else
+#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
+ {
+ mbedtls_x509_crt *ca_chain;
+ mbedtls_x509_crl *ca_crl;
+#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ if (ssl->handshake->sni_ca_chain != NULL) {
+ ca_chain = ssl->handshake->sni_ca_chain;
+ ca_crl = ssl->handshake->sni_ca_crl;
+ } else
+#endif
+ {
+ ca_chain = ssl->conf->ca_chain;
+ ca_crl = ssl->conf->ca_crl;
+ }
+
+ if (ca_chain != NULL) {
+ have_ca_chain_or_callback = 1;
+ }
+
+ ret = mbedtls_x509_crt_verify_restartable(
+ chain,
+ ca_chain, ca_crl,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy, rs_ctx);
+ }
+
+ if (ret != 0) {
+ MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
+ }
+
+#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
+ if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
+ return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
+ }
+#endif
+
+ /*
+ * Secondary checks: always done, but change 'ret' only if it was 0
+ */
+
+ /* With TLS 1.2 and ECC certs, check that the curve used by the
+ * certificate is on our list of acceptable curves.
+ *
+ * With TLS 1.3 this is not needed because the curve is part of the
+ * signature algorithm (eg ecdsa_secp256r1_sha256) which is checked when
+ * we validate the signature made with the key associated to this cert.
+ */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \
+ defined(MBEDTLS_PK_HAVE_ECC_KEYS)
+ if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ mbedtls_pk_can_do(&chain->pk, MBEDTLS_PK_ECKEY)) {
+ if (mbedtls_ssl_check_curve(ssl, mbedtls_pk_get_ec_group_id(&chain->pk)) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
+ ssl->session_negotiate->verify_result |= MBEDTLS_X509_BADCERT_BAD_KEY;
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+ }
+#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_PK_HAVE_ECC_KEYS */
+
+ /* Check X.509 usage extensions (keyUsage, extKeyUsage) */
+ if (mbedtls_ssl_check_cert_usage(chain,
+ ciphersuite_info,
+ ssl->conf->endpoint,
+ ssl->tls_version,
+ &ssl->session_negotiate->verify_result) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+
+ /* With authmode optional, we want to keep going if the certificate was
+ * unacceptable, but still fail on other errors (out of memory etc),
+ * including fatal errors from the f_vrfy callback.
+ *
+ * The only acceptable errors are:
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: cert rejected by primary check;
+ * - MBEDTLS_ERR_SSL_BAD_CERTIFICATE: cert rejected by secondary checks.
+ * Anything else is a fatal error. */
+ if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
+ (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
+ ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
+ ret = 0;
+ }
+
+ /* Return a specific error as this is a user error: inconsistent
+ * configuration - can't verify without trust anchors. */
+ if (have_ca_chain_or_callback == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
+ ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
+ }
+
+ if (ret != 0) {
+ uint8_t alert;
+
+ /* The certificate may have been rejected for several reasons.
+ Pick one and send the corresponding alert. Which alert to send
+ may be a subject of debate in some cases. */
+ if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
+ alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
+ alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
+ } else {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
+ }
+ mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
+ alert);
+ }
+
+#if defined(MBEDTLS_DEBUG_C)
+ if (ssl->session_negotiate->verify_result != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
+ (unsigned int) ssl->session_negotiate->verify_result));
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
+ }
+#endif /* MBEDTLS_DEBUG_C */
+
+ return ret;
+}
+#endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
+
#endif /* MBEDTLS_SSL_TLS_C */
diff --git a/thirdparty/mbedtls/library/ssl_tls12_client.c b/thirdparty/mbedtls/library/ssl_tls12_client.c
index eac6a3aadd..9b2da5a39d 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_client.c
@@ -364,7 +364,8 @@ static int ssl_write_session_ticket_ext(mbedtls_ssl_context *ssl,
*olen = 0;
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
+ if (mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
return 0;
}
@@ -787,7 +788,8 @@ static int ssl_parse_session_ticket_ext(mbedtls_ssl_context *ssl,
const unsigned char *buf,
size_t len)
{
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED ||
+ if ((mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) ||
len != 0) {
MBEDTLS_SSL_DEBUG_MSG(1,
("non-matching session ticket extension"));
diff --git a/thirdparty/mbedtls/library/ssl_tls12_server.c b/thirdparty/mbedtls/library/ssl_tls12_server.c
index b49a8ae6a6..03722ac33c 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_server.c
@@ -756,7 +756,9 @@ static int ssl_pick_cert(mbedtls_ssl_context *ssl,
* and decrypting with the same RSA key.
*/
if (mbedtls_ssl_check_cert_usage(cur->cert, ciphersuite_info,
- MBEDTLS_SSL_IS_SERVER, &flags) != 0) {
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_VERSION_TLS1_2,
+ &flags) != 0) {
MBEDTLS_SSL_DEBUG_MSG(3, ("certificate mismatch: "
"(extended) key usage extension"));
continue;
@@ -2631,13 +2633,8 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
ssl->handshake->xxdh_psa_type = psa_get_key_type(&key_attributes);
ssl->handshake->xxdh_psa_bits = psa_get_key_bits(&key_attributes);
- if (pk_type == MBEDTLS_PK_OPAQUE) {
- /* Opaque key is created by the user (externally from Mbed TLS)
- * so we assume it already has the right algorithm and flags
- * set. Just copy its ID as reference. */
- ssl->handshake->xxdh_psa_privkey = pk->priv_id;
- ssl->handshake->xxdh_psa_privkey_is_external = 1;
- } else {
+#if defined(MBEDTLS_PK_USE_PSA_EC_DATA)
+ if (pk_type != MBEDTLS_PK_OPAQUE) {
/* PK_ECKEY[_DH] and PK_ECDSA instead as parsed from the PK
* module and only have ECDSA capabilities. Since we need
* them for ECDH later, we export and then re-import them with
@@ -2665,10 +2662,20 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
/* Set this key as owned by the TLS library: it will be its duty
* to clear it exit. */
ssl->handshake->xxdh_psa_privkey_is_external = 0;
+
+ ret = 0;
+ break;
}
+#endif /* MBEDTLS_PK_USE_PSA_EC_DATA */
+ /* Opaque key is created by the user (externally from Mbed TLS)
+ * so we assume it already has the right algorithm and flags
+ * set. Just copy its ID as reference. */
+ ssl->handshake->xxdh_psa_privkey = pk->priv_id;
+ ssl->handshake->xxdh_psa_privkey_is_external = 1;
ret = 0;
break;
+
#if !defined(MBEDTLS_PK_USE_PSA_EC_DATA)
case MBEDTLS_PK_ECKEY:
case MBEDTLS_PK_ECKEY_DH:
@@ -3916,7 +3923,7 @@ static int ssl_parse_client_key_exchange(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_status_t destruction_status = PSA_ERROR_CORRUPTION_DETECTED;
- uint8_t ecpoint_len;
+ size_t ecpoint_len;
mbedtls_ssl_handshake_params *handshake = ssl->handshake;
diff --git a/thirdparty/mbedtls/library/ssl_tls13_client.c b/thirdparty/mbedtls/library/ssl_tls13_client.c
index 7fcc394319..b63b5e63c5 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_client.c
@@ -666,6 +666,7 @@ static int ssl_tls13_write_psk_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
{
const mbedtls_ssl_ciphersuite_t *ciphersuite_info = NULL;
@@ -678,7 +679,6 @@ static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
return PSA_ALG_NONE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static int ssl_tls13_has_configured_ticket(mbedtls_ssl_context *ssl)
{
mbedtls_ssl_session *session = ssl->session_negotiate;
@@ -1141,6 +1141,11 @@ int mbedtls_ssl_tls13_write_client_hello_exts(mbedtls_ssl_context *ssl,
*out_len = 0;
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/* Write supported_versions extension
*
* Supported Versions Extension is mandatory with TLS 1.3.
diff --git a/thirdparty/mbedtls/library/ssl_tls13_generic.c b/thirdparty/mbedtls/library/ssl_tls13_generic.c
index d448a054a9..b6d09788ba 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_generic.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_generic.c
@@ -27,7 +27,6 @@
#include "psa/crypto.h"
#include "psa_util_internal.h"
-#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED)
/* Define a local translating function to save code size by not using too many
* arguments in each translating place. */
static int local_err_translation(psa_status_t status)
@@ -37,7 +36,16 @@ static int local_err_translation(psa_status_t status)
psa_generic_status_to_mbedtls);
}
#define PSA_TO_MBEDTLS_ERR(status) local_err_translation(status)
-#endif
+
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl)
+{
+ psa_status_t status = psa_crypto_init();
+ if (status != PSA_SUCCESS) {
+ (void) ssl; // unused when debugging is disabled
+ MBEDTLS_SSL_DEBUG_RET(1, "psa_crypto_init", status);
+ }
+ return PSA_TO_MBEDTLS_ERR(status);
+}
const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN] =
@@ -193,10 +201,12 @@ static void ssl_tls13_create_verify_structure(const unsigned char *transcript_ha
idx = 64;
if (from == MBEDTLS_SSL_IS_CLIENT) {
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(client_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.client_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv);
} else { /* from == MBEDTLS_SSL_IS_SERVER */
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(server_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.server_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv);
}
@@ -470,6 +480,7 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
mbedtls_free(ssl->session_negotiate->peer_cert);
}
+ /* This is used by ssl_tls13_validate_certificate() */
if (certificate_list_len == 0) {
ssl->session_negotiate->peer_cert = NULL;
ret = 0;
@@ -625,25 +636,13 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
{
- int ret = 0;
- int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
- const char *ext_oid;
- size_t ext_len;
- uint32_t verify_result = 0;
-
- /* If SNI was used, overwrite authentication mode
- * from the configuration. */
-#if defined(MBEDTLS_SSL_SRV_C)
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET) {
- authmode = ssl->handshake->sni_authmode;
- } else
-#endif
- authmode = ssl->conf->authmode;
- }
+ /* Authmode: precedence order is SNI if used else configuration */
+#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
+ ? ssl->handshake->sni_authmode
+ : ssl->conf->authmode;
+#else
+ const int authmode = ssl->conf->authmode;
#endif
/*
@@ -675,6 +674,11 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_SRV_C */
#if defined(MBEDTLS_SSL_CLI_C)
+ /* Regardless of authmode, the server is not allowed to send an empty
+ * certificate chain. (Last paragraph before 4.4.2.1 in RFC 8446: "The
+ * server's certificate_list MUST always be non-empty.") With authmode
+ * optional/none, we continue the handshake if we can't validate the
+ * server's cert, but we still break it if no certificate was sent. */
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_NO_CERT,
MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE);
@@ -683,114 +687,9 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_CLI_C */
}
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- /*
- * Main check: verify certificate
- */
- ret = mbedtls_x509_crt_verify_with_profile(
- ssl->session_negotiate->peer_cert,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &verify_result,
- ssl->conf->f_vrfy, ssl->conf->p_vrfy);
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if ((mbedtls_x509_crt_check_key_usage(
- ssl->session_negotiate->peer_cert,
- MBEDTLS_X509_KU_DIGITAL_SIGNATURE) != 0) ||
- (mbedtls_x509_crt_check_extended_key_usage(
- ssl->session_negotiate->peer_cert,
- ext_oid, ext_len) != 0)) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * mbedtls_ssl_tls13_parse_certificate even if verification was optional.
- */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (ca_chain == NULL && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_BAD_CERT, ret);
- } else if (verify_result & (MBEDTLS_X509_BADCERT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_EXT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_NS_CERT_TYPE |
- MBEDTLS_X509_BADCERT_BAD_PK |
- MBEDTLS_X509_BADCERT_BAD_KEY)) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA, ret);
- } else {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN, ret);
- }
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- ssl->session_negotiate->verify_result = verify_result;
- return ret;
+ return mbedtls_ssl_verify_certificate(ssl, authmode,
+ ssl->session_negotiate->peer_cert,
+ NULL, NULL);
}
#else /* MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -1482,9 +1381,11 @@ int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl,
ssl->total_early_data_size)) {
MBEDTLS_SSL_DEBUG_MSG(
- 2, ("EarlyData: Too much early data received, %u + %" MBEDTLS_PRINTF_SIZET " > %u",
- ssl->total_early_data_size, early_data_len,
- ssl->session_negotiate->max_early_data_size));
+ 2, ("EarlyData: Too much early data received, "
+ "%lu + %" MBEDTLS_PRINTF_SIZET " > %lu",
+ (unsigned long) ssl->total_early_data_size,
+ early_data_len,
+ (unsigned long) ssl->session_negotiate->max_early_data_size));
MBEDTLS_SSL_PEND_FATAL_ALERT(
MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE,
diff --git a/thirdparty/mbedtls/library/ssl_tls13_server.c b/thirdparty/mbedtls/library/ssl_tls13_server.c
index 2760d76a5d..693edc7b0b 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_server.c
@@ -92,8 +92,9 @@ static void ssl_tls13_select_ciphersuite(
return;
}
- MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%x",
- (unsigned) psk_ciphersuite_id, psk_hash_alg));
+ MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx",
+ (unsigned) psk_ciphersuite_id,
+ (unsigned long) psk_hash_alg));
}
#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED)
@@ -172,12 +173,12 @@ static int ssl_tls13_parse_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
#define SSL_TLS1_3_PSK_IDENTITY_MATCH_BUT_PSK_NOT_USABLE 1
#define SSL_TLS1_3_PSK_IDENTITY_MATCH 0
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_available(mbedtls_ssl_context *ssl);
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_ephemeral_available(mbedtls_ssl_context *ssl);
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_offered_psks_check_identity_match_ticket(
mbedtls_ssl_context *ssl,
@@ -575,10 +576,8 @@ static int ssl_tls13_parse_pre_shared_key_ext(
psa_algorithm_t psk_hash_alg;
int allowed_key_exchange_modes;
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
mbedtls_ssl_session session;
mbedtls_ssl_session_init(&session);
-#endif
MBEDTLS_SSL_CHK_BUF_READ_PTR(p_identity_len, identities_end, 2 + 1 + 4);
identity_len = MBEDTLS_GET_UINT16_BE(p_identity_len, 0);
@@ -1356,19 +1355,23 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
* compression methods and the length of the extensions.
*
* cipher_suites cipher_suites_len bytes
- * legacy_compression_methods 2 bytes
- * extensions_len 2 bytes
+ * legacy_compression_methods length 1 byte
*/
- MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 2 + 2);
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 1);
p += cipher_suites_len;
cipher_suites_end = p;
+ /* Check if we have enough data for legacy_compression_methods
+ * and the length of the extensions (2 bytes).
+ */
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p + 1, end, p[0] + 2);
+
/*
* Search for the supported versions extension and parse it to determine
* if the client supports TLS 1.3.
*/
ret = mbedtls_ssl_tls13_is_supported_versions_ext_present_in_exts(
- ssl, p + 2, end,
+ ssl, p + 1 + p[0], end,
&supported_versions_data, &supported_versions_data_end);
if (ret < 0) {
MBEDTLS_SSL_DEBUG_RET(1,
@@ -1409,6 +1412,12 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
ssl->session_negotiate->tls_version = MBEDTLS_SSL_VERSION_TLS1_3;
ssl->session_negotiate->endpoint = ssl->conf->endpoint;
+ /* Before doing any crypto, make sure we can. */
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/*
* We are negotiating the version 1.3 of the protocol. Do what we have
* postponed: copy of the client random bytes, copy of the legacy session
@@ -3109,6 +3118,7 @@ static int ssl_tls13_handshake_wrapup(mbedtls_ssl_context *ssl)
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
/*
* Handler for MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET
*/
@@ -3138,7 +3148,6 @@ static int ssl_tls13_write_new_session_ticket_coordinate(mbedtls_ssl_context *ss
return SSL_NEW_SESSION_TICKET_WRITE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_prepare_new_session_ticket(mbedtls_ssl_context *ssl,
unsigned char *ticket_nonce,
diff --git a/thirdparty/mbedtls/library/version_features.c b/thirdparty/mbedtls/library/version_features.c
index 406161d4c7..f542d9808f 100644
--- a/thirdparty/mbedtls/library/version_features.c
+++ b/thirdparty/mbedtls/library/version_features.c
@@ -423,6 +423,9 @@ static const char * const features[] = {
#if defined(MBEDTLS_PSA_CRYPTO_SPM)
"PSA_CRYPTO_SPM", //no-check-names
#endif /* MBEDTLS_PSA_CRYPTO_SPM */
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ "PSA_KEY_STORE_DYNAMIC", //no-check-names
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
#if defined(MBEDTLS_PSA_P256M_DRIVER_ENABLED)
"PSA_P256M_DRIVER_ENABLED", //no-check-names
#endif /* MBEDTLS_PSA_P256M_DRIVER_ENABLED */
diff --git a/thirdparty/mbedtls/library/x509_crt.c b/thirdparty/mbedtls/library/x509_crt.c
index 2fd56fbd79..53cdcf0266 100644
--- a/thirdparty/mbedtls/library/x509_crt.c
+++ b/thirdparty/mbedtls/library/x509_crt.c
@@ -48,7 +48,9 @@
#if defined(MBEDTLS_HAVE_TIME)
#if defined(_WIN32) && !defined(EFIX64) && !defined(EFI32)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <windows.h>
#else
#include <time.h>
diff --git a/thirdparty/mbedtls/library/x509write_crt.c b/thirdparty/mbedtls/library/x509write_crt.c
index 72f5a10a17..56f23c9fab 100644
--- a/thirdparty/mbedtls/library/x509write_crt.c
+++ b/thirdparty/mbedtls/library/x509write_crt.c
@@ -46,6 +46,10 @@ void mbedtls_x509write_crt_init(mbedtls_x509write_cert *ctx)
void mbedtls_x509write_crt_free(mbedtls_x509write_cert *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->issuer);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/library/x509write_csr.c b/thirdparty/mbedtls/library/x509write_csr.c
index d3ddbcc03d..0d6f6bb1d3 100644
--- a/thirdparty/mbedtls/library/x509write_csr.c
+++ b/thirdparty/mbedtls/library/x509write_csr.c
@@ -43,6 +43,10 @@ void mbedtls_x509write_csr_init(mbedtls_x509write_csr *ctx)
void mbedtls_x509write_csr_free(mbedtls_x509write_csr *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
index c5f1970223..3a15928fe1 100644
--- a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
+++ b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
@@ -1,5 +1,5 @@
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 92f9c824e9..1cc2e7e729 100644
+index 2bbcea3ee0..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -107,7 +107,9 @@ psa_status_t psa_crypto_init(void);
@@ -12,7 +12,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Declare a key as persistent and set its key identifier.
*
-@@ -333,7 +335,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
+@@ -336,7 +338,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
*
* \return The key type stored in the attribute structure.
*/
@@ -22,7 +22,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Retrieve the key size from key attributes.
*
-@@ -936,7 +940,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
+@@ -939,7 +943,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
/** Return an initial value for a hash operation object.
*/
@@ -32,7 +32,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart hash operation.
*
-@@ -1295,7 +1301,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
+@@ -1298,7 +1304,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
/** Return an initial value for a MAC operation object.
*/
@@ -42,7 +42,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart MAC calculation operation.
*
-@@ -1708,7 +1716,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
+@@ -1711,7 +1719,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
/** Return an initial value for a cipher operation object.
*/
@@ -52,7 +52,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart symmetric encryption operation.
*
-@@ -2226,7 +2236,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
+@@ -2229,7 +2239,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
/** Return an initial value for an AEAD operation object.
*/
@@ -62,7 +62,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart authenticated encryption operation.
*
-@@ -3213,7 +3225,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
+@@ -3216,7 +3228,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
/** Return an initial value for a key derivation operation object.
*/
@@ -73,10 +73,10 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a key derivation operation.
*
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
-index 6ed1f6c43a..2686b9d74d 100644
+index 0cf42c6055..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
-@@ -915,7 +915,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
+@@ -923,7 +923,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
/** Return an initial value for a PAKE cipher suite object.
*/
@@ -86,7 +86,7 @@ index 6ed1f6c43a..2686b9d74d 100644
/** Retrieve the PAKE algorithm from a PAKE cipher suite.
*
-@@ -1048,7 +1050,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
+@@ -1056,7 +1058,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
/** Return an initial value for a PAKE operation object.
*/
diff --git a/thirdparty/mbedtls/patches/no-flexible-arrays.diff b/thirdparty/mbedtls/patches/no-flexible-arrays.diff
deleted file mode 100644
index 87fd06f1e3..0000000000
--- a/thirdparty/mbedtls/patches/no-flexible-arrays.diff
+++ /dev/null
@@ -1,132 +0,0 @@
-diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 7083bd911b..92f9c824e9 100644
---- a/thirdparty/mbedtls/include/psa/crypto.h
-+++ b/thirdparty/mbedtls/include/psa/crypto.h
-@@ -3834,12 +3834,14 @@ psa_status_t psa_key_derivation_output_key(
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_key_derivation_output_key_ext(
- const psa_key_attributes_t *attributes,
- psa_key_derivation_operation_t *operation,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /** Compare output data from a key derivation operation to an expected value.
- *
-@@ -4180,10 +4182,12 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
-index 3913551aa8..e2c227b2eb 100644
---- a/thirdparty/mbedtls/include/psa/crypto_struct.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
-@@ -223,11 +223,13 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
- return v;
- }
-
-+#ifndef __cplusplus
- struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
- uint32_t flags;
- uint8_t data[];
- };
-+#endif
-
- /** The default production parameters for key generation or key derivation.
- *
-diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
-index c21bad86cc..a36b6ee65d 100644
---- a/thirdparty/mbedtls/include/psa/crypto_types.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
-@@ -477,7 +477,9 @@ typedef uint16_t psa_key_derivation_step_t;
- * - Other key types: reserved for future use. \c flags must be 0.
- *
- */
-+#ifndef __cplusplus
- typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
-index 9462d2e8be..c059162efe 100644
---- a/thirdparty/mbedtls/library/psa_crypto_core.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
-@@ -351,9 +351,11 @@ psa_status_t psa_export_public_key_internal(
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
- */
-+#ifndef __cplusplus
- int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-+#endif
-
- /**
- * \brief Generate a key.
-@@ -378,12 +380,14 @@ int psa_key_production_parameters_are_default(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- uint8_t *key_buffer,
- size_t key_buffer_size,
- size_t *key_buffer_length);
-+#endif
-
- /** Sign a message with a private key. For hash-and-sign algorithms,
- * this includes the hashing step.
-diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-index ea6aee32eb..6919971aca 100644
---- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-@@ -728,6 +728,7 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
- }
- }
-
-+#ifndef __cplusplus
- static inline psa_status_t psa_driver_wrapper_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
-@@ -832,6 +833,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
-
- return( status );
- }
-+#endif
-
- static inline psa_status_t psa_driver_wrapper_import_key(
- const psa_key_attributes_t *attributes,
-diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-index ffeef26be1..6d695ddf50 100644
---- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-@@ -130,10 +130,12 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t mbedtls_psa_rsa_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
- uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-+#endif
-
- /** Sign an already-calculated hash with an RSA private key.
- *
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 67ebc9381e..02fec07448 100644
--- a/thirdparty/thorvg/inc/config.h
+++ b/thirdparty/thorvg/inc/config.h
@@ -15,5 +15,5 @@
// For internal debugging:
//#define THORVG_LOG_ENABLED
-#define THORVG_VERSION_STRING "0.14.7"
+#define THORVG_VERSION_STRING "0.14.8"
#endif
diff --git a/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
new file mode 100644
index 0000000000..69f4a5cf85
--- /dev/null
+++ b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
@@ -0,0 +1,96 @@
+From ac7d208ed8e4651c93ce1b2384070fccac9b6cb6 Mon Sep 17 00:00:00 2001
+From: Mira Grudzinska <mira@lottiefiles.com>
+Date: Sun, 1 Sep 2024 22:36:18 +0200
+Subject: [PATCH] sw_engine: handle small cubics
+
+During the stroke's outline calculation, the function
+handling small cubics set all angles to zero. Such cases
+should be ignored, as further processing caused errors -
+when the cubic was small but not zero, setting the angles
+to zero resulted in incorrect outlines.
+
+@Issue: https://github.com/godotengine/godot/issues/96262
+---
+ src/renderer/sw_engine/tvgSwCommon.h | 3 ++-
+ src/renderer/sw_engine/tvgSwMath.cpp | 19 ++++++++++++-------
+ src/renderer/sw_engine/tvgSwStroke.cpp | 16 +++++++++++-----
+ 3 files changed, 25 insertions(+), 13 deletions(-)
+
+diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h
+index 893e9beca..158fe8ecd 100644
+--- a/src/renderer/sw_engine/tvgSwCommon.h
++++ b/src/renderer/sw_engine/tvgSwCommon.h
+@@ -491,7 +491,8 @@ SwFixed mathSin(SwFixed angle);
+ void mathSplitCubic(SwPoint* base);
+ SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
+ SwFixed mathLength(const SwPoint& pt);
+-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
++bool mathSmallCubic(const SwPoint* base);
++bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
+ SwFixed mathMean(SwFixed angle1, SwFixed angle2);
+ SwPoint mathTransform(const Point* to, const Matrix& transform);
+ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
+diff --git a/src/renderer/sw_engine/tvgSwMath.cpp b/src/renderer/sw_engine/tvgSwMath.cpp
+index 1093edd62..b311be05f 100644
+--- a/src/renderer/sw_engine/tvgSwMath.cpp
++++ b/src/renderer/sw_engine/tvgSwMath.cpp
+@@ -44,7 +44,17 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2)
+ }
+
+
+-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
++bool mathSmallCubic(const SwPoint* base)
++{
++ auto d1 = base[2] - base[3];
++ auto d2 = base[1] - base[2];
++ auto d3 = base[0] - base[1];
++
++ return d1.small() && d2.small() && d3.small();
++}
++
++
++bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
+ {
+ auto d1 = base[2] - base[3];
+ auto d2 = base[1] - base[2];
+@@ -52,12 +62,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw
+
+ if (d1.small()) {
+ if (d2.small()) {
+- if (d3.small()) {
+- angleIn = angleMid = angleOut = 0;
+- return true;
+- } else {
+- angleIn = angleMid = angleOut = mathAtan(d3);
+- }
++ angleIn = angleMid = angleOut = mathAtan(d3);
+ } else {
+ if (d3.small()) {
+ angleIn = angleMid = angleOut = mathAtan(d2);
+diff --git a/src/renderer/sw_engine/tvgSwStroke.cpp b/src/renderer/sw_engine/tvgSwStroke.cpp
+index 575d12951..4679b72cc 100644
+--- a/src/renderer/sw_engine/tvgSwStroke.cpp
++++ b/src/renderer/sw_engine/tvgSwStroke.cpp
+@@ -441,11 +441,17 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
+ //initialize with current direction
+ angleIn = angleOut = angleMid = stroke.angleIn;
+
+- if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
+- if (stroke.firstPt) stroke.angleIn = angleIn;
+- mathSplitCubic(arc);
+- arc += 3;
+- continue;
++ if (arc < limit) {
++ if (mathSmallCubic(arc)) {
++ arc -= 3;
++ continue;
++ }
++ if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) {
++ if (stroke.firstPt) stroke.angleIn = angleIn;
++ mathSplitCubic(arc);
++ arc += 3;
++ continue;
++ }
+ }
+
+ if (firstArc) {
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
index c56b32249f..0254cce9b8 100644
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
@@ -133,3 +133,11 @@ Point operator*(const Point& pt, const Matrix& m)
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
return {tx, ty};
}
+
+uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t)
+{
+ auto result = static_cast<int>(start + (end - start) * t);
+ if (result > 255) result = 255;
+ else if (result < 0) result = 0;
+ return static_cast<uint8_t>(result);
+}
diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h
index 556ed410ff..df39e3b9af 100644
--- a/thirdparty/thorvg/src/common/tvgMath.h
+++ b/thirdparty/thorvg/src/common/tvgMath.h
@@ -259,5 +259,6 @@ static inline T mathLerp(const T &start, const T &end, float t)
return static_cast<T>(start + (end - start) * t);
}
+uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t);
#endif //_TVG_MATH_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index 197d3cc13a..5aab4f1b0d 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -588,6 +588,11 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
float _red = 0, _green = 0, _blue = 0;
uint32_t i = 0;
+ while (hue < 0) hue += 360.0f;
+ hue = fmod(hue, 360.0f);
+ saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f;
+ brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f;
+
if (mathZero(saturation)) _red = _green = _blue = brightness;
else {
if (mathEqual(hue, 360.0)) hue = 0.0f;
@@ -714,7 +719,6 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
const char* hue = nullptr;
if (_parseNumber(&content, &hue, &th) && hue) {
const char* saturation = nullptr;
- th = float(uint32_t(th) % 360);
hue = _skipSpace(hue, nullptr);
hue = (char*)_skipComma(hue);
hue = _skipSpace(hue, nullptr);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
index 3229eb39ba..09b75d370b 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
@@ -367,7 +367,7 @@ static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNU
}
//TODO: BlendMethod could remove the alpha parameter.
-static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, uint8_t a)
{
//if (s > d) => s - d
//else => d - s
@@ -404,8 +404,7 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t
return JOIN(255, c1, c2, c3);
}
-
-static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDirectMultiply(uint32_t s, uint32_t d, uint8_t a)
{
// s * d
auto c1 = MULTIPLY(C1(s), C1(d));
@@ -414,6 +413,10 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_
return JOIN(255, c1, c2, c3);
}
+static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, uint8_t a)
+{
+ return opBlendDirectMultiply(s, d, a) + ALPHA_BLEND(d, IA(s));
+}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
@@ -492,7 +495,8 @@ SwFixed mathSin(SwFixed angle);
void mathSplitCubic(SwPoint* base);
SwFixed mathDiff(SwFixed angle1, SwFixed angle2);
SwFixed mathLength(const SwPoint& pt);
-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
+bool mathSmallCubic(const SwPoint* base);
+bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut);
SwFixed mathMean(SwFixed angle1, SwFixed angle2);
SwPoint mathTransform(const Point* to, const Matrix& transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
index fb809c4f7e..60dbbc4fbc 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
@@ -44,7 +44,17 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2)
}
-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
+bool mathSmallCubic(const SwPoint* base)
+{
+ auto d1 = base[2] - base[3];
+ auto d2 = base[1] - base[2];
+ auto d3 = base[0] - base[1];
+
+ return d1.small() && d2.small() && d3.small();
+}
+
+
+bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut)
{
auto d1 = base[2] - base[3];
auto d2 = base[1] - base[2];
@@ -52,12 +62,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw
if (d1.small()) {
if (d2.small()) {
- if (d3.small()) {
- angleIn = angleMid = angleOut = 0;
- return true;
- } else {
- angleIn = angleMid = angleOut = mathAtan(d3);
- }
+ angleIn = angleMid = angleOut = mathAtan(d3);
} else {
if (d3.small()) {
angleIn = angleMid = angleOut = mathAtan(d2);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
index a6f9d8745a..90d91e17e0 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
@@ -1383,7 +1383,7 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image,
*dst = INTERPOLATE(tmp, *dst, A(*src));
}
} else {
- for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
auto tmp = ALPHA_BLEND(*src, opacity);
auto tmp2 = surface->blender(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, A(tmp));
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
index 47a7166115..6470089c7c 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
@@ -100,7 +100,6 @@ struct SwShapeTask : SwTask
return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
}
-
bool clip(SwRleData* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
@@ -447,7 +446,7 @@ bool SwRenderer::renderShape(RenderData data)
}
-bool SwRenderer::blend(BlendMethod method)
+bool SwRenderer::blend(BlendMethod method, bool direct)
{
if (surface->blendMethod == method) return true;
surface->blendMethod = method;
@@ -460,7 +459,7 @@ bool SwRenderer::blend(BlendMethod method)
surface->blender = opBlendScreen;
break;
case BlendMethod::Multiply:
- surface->blender = opBlendMultiply;
+ surface->blender = direct ? opBlendDirectMultiply : opBlendMultiply;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
index fcd8ad4620..7aa6d89aaa 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
@@ -46,7 +46,7 @@ public:
RenderRegion region(RenderData data) override;
RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override;
- bool blend(BlendMethod method) override;
+ bool blend(BlendMethod method, bool direct) override;
ColorSpace colorSpace() override;
const Surface* mainSurface() override;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
index 75ac96be04..e0e74ce53c 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
@@ -441,11 +441,17 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
//initialize with current direction
angleIn = angleOut = angleMid = stroke.angleIn;
- if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) {
- if (stroke.firstPt) stroke.angleIn = angleIn;
- mathSplitCubic(arc);
- arc += 3;
- continue;
+ if (arc < limit) {
+ if (mathSmallCubic(arc)) {
+ arc -= 3;
+ continue;
+ }
+ if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) {
+ if (stroke.firstPt) stroke.angleIn = angleIn;
+ mathSplitCubic(arc);
+ arc += 3;
+ continue;
+ }
}
if (firstArc) {
diff --git a/thirdparty/thorvg/src/renderer/tvgLoadModule.h b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
index c750683771..1b81d81a4f 100644
--- a/thirdparty/thorvg/src/renderer/tvgLoadModule.h
+++ b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
@@ -101,7 +101,8 @@ struct FontLoader : LoadModule
FontLoader(FileType type) : LoadModule(type) {}
- virtual bool request(Shape* shape, char* text, bool italic = false) = 0;
+ virtual bool request(Shape* shape, char* text) = 0;
+ virtual bool transform(Paint* paint, float fontSize, bool italic) = 0;
};
#endif //_TVG_LOAD_MODULE_H_
diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
index 11cee0c54a..37813b19ef 100644
--- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
@@ -225,8 +225,6 @@ bool Paint::Impl::render(RenderMethod* renderer)
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
- renderer->blend(blendMethod);
-
bool ret;
PAINT_METHOD(ret, render(renderer));
diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
index 68c7a41032..e6653b1c99 100644
--- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
@@ -73,6 +73,8 @@ bool Picture::Impl::needComposition(uint8_t opacity)
bool Picture::Impl::render(RenderMethod* renderer)
{
bool ret = false;
+ renderer->blend(picture->blend(), true);
+
if (surface) return renderer->renderImage(rd);
else if (paint) {
Compositor* cmp = nullptr;
diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h
index b0ee42db8d..f1042884ec 100644
--- a/thirdparty/thorvg/src/renderer/tvgRender.h
+++ b/thirdparty/thorvg/src/renderer/tvgRender.h
@@ -296,7 +296,7 @@ public:
virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0;
- virtual bool blend(BlendMethod method) = 0;
+ virtual bool blend(BlendMethod method, bool direct = false) = 0;
virtual ColorSpace colorSpace() = 0;
virtual const Surface* mainSurface() = 0;
diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h
index cb6d179326..190ecd31b9 100644
--- a/thirdparty/thorvg/src/renderer/tvgScene.h
+++ b/thirdparty/thorvg/src/renderer/tvgScene.h
@@ -120,6 +120,8 @@ struct Scene::Impl
Compositor* cmp = nullptr;
auto ret = true;
+ renderer->blend(scene->blend());
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h
index b76fde7ced..94704aee67 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.h
+++ b/thirdparty/thorvg/src/renderer/tvgShape.h
@@ -54,10 +54,13 @@ struct Shape::Impl
Compositor* cmp = nullptr;
bool ret;
+ renderer->blend(shape->blend(), !needComp);
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
+
ret = renderer->renderShape(rd);
if (cmp) renderer->endComposite(cmp);
return ret;
diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
index f6faec2d53..746b85bea6 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.h
+++ b/thirdparty/thorvg/src/renderer/tvgText.h
@@ -26,12 +26,7 @@
#include <cstring>
#include "tvgShape.h"
#include "tvgFill.h"
-
-#ifdef THORVG_TTF_LOADER_SUPPORT
- #include "tvgTtfLoader.h"
-#else
- #include "tvgLoader.h"
-#endif
+#include "tvgLoader.h"
struct Text::Impl
{
@@ -69,6 +64,11 @@ struct Text::Impl
auto loader = LoaderMgr::loader(name);
if (!loader) return Result::InsufficientCondition;
+ if (style && strstr(style, "italic")) italic = true;
+ else italic = false;
+
+ fontSize = size;
+
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
@@ -78,8 +78,6 @@ struct Text::Impl
}
this->loader = static_cast<FontLoader*>(loader);
- fontSize = size;
- if (style && strstr(style, "italic")) italic = true;
changed = true;
return Result::Success;
}
@@ -91,6 +89,7 @@ struct Text::Impl
bool render(RenderMethod* renderer)
{
+ renderer->blend(paint->blend(), true);
return PP(shape)->render(renderer);
}
@@ -98,13 +97,13 @@ struct Text::Impl
{
if (!loader) return false;
+ loader->request(shape, utf8);
//reload
if (changed) {
- loader->request(shape, utf8, italic);
loader->read();
changed = false;
}
- return loader->resize(shape, fontSize, fontSize);
+ return loader->transform(shape, fontSize, italic);
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index 7ff07fe4c2..51dc156661 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,16 +1,22 @@
#!/bin/bash -e
-VERSION=0.14.7
+VERSION=0.14.8
+# Uncomment and set a git hash to use specific commit instead of tag.
+#GIT_COMMIT=
-cd thirdparty/thorvg/ || true
+pushd "$(dirname "$0")"
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
mkdir tmp/ && pushd tmp/
# Release
-curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
-# Current Github main branch tip
-#curl -L -O https://github.com/thorvg/thorvg/archive/refs/heads/main.tar.gz
+if [ ! -z "$GIT_COMMIT" ]; then
+ echo "Updating ThorVG to commit:" $GIT_COMMIT
+ curl -L -O https://github.com/thorvg/thorvg/archive/$GIT_COMMIT.tar.gz
+else
+ echo "Updating ThorVG to tagged release:" $VERSION
+ curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
+fi
tar --strip-components=1 -xvf *.tar.gz
rm *.tar.gz
@@ -70,4 +76,4 @@ cp -rv src/loaders/jpg ../src/loaders/
popd
rm -rf tmp
-
+popd