summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/io/resource_loader.cpp37
-rw-r--r--core/io/resource_loader.h3
-rw-r--r--core/math/transform_interpolator.cpp34
-rw-r--r--core/object/worker_thread_pool.cpp91
-rw-r--r--core/object/worker_thread_pool.h24
-rw-r--r--core/templates/command_queue_mt.h4
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--core/variant/variant_op.h32
-rw-r--r--doc/classes/AnimationMixer.xml11
-rw-r--r--doc/classes/AudioEffectSpectrumAnalyzer.xml1
-rw-r--r--doc/classes/AudioEffectSpectrumAnalyzerInstance.xml10
-rw-r--r--doc/classes/Basis.xml2
-rw-r--r--doc/classes/Button.xml2
-rw-r--r--doc/classes/CanvasItem.xml4
-rw-r--r--doc/classes/EditorExportPlugin.xml10
-rw-r--r--doc/classes/EditorPlugin.xml2
-rw-r--r--doc/classes/EditorSettings.xml8
-rw-r--r--doc/classes/GraphEdit.xml1
-rw-r--r--doc/classes/Mesh.xml1
-rw-r--r--doc/classes/NodePath.xml5
-rw-r--r--doc/classes/ProjectSettings.xml8
-rw-r--r--doc/classes/ResourceLoader.xml3
-rw-r--r--doc/classes/TextEdit.xml2
-rw-r--r--doc/classes/Transform3D.xml2
-rw-r--r--drivers/d3d12/rendering_device_driver_d3d12.cpp7
-rw-r--r--drivers/gles3/storage/material_storage.cpp12
-rw-r--r--drivers/vulkan/SCsub4
-rw-r--r--drivers/vulkan/rendering_context_driver_vulkan.cpp9
-rw-r--r--editor/animation_bezier_editor.cpp76
-rw-r--r--editor/animation_bezier_editor.h3
-rw-r--r--editor/animation_track_editor.cpp42
-rw-r--r--editor/code_editor.cpp57
-rw-r--r--editor/code_editor.h8
-rw-r--r--editor/connections_dialog.cpp2
-rw-r--r--editor/editor_autoload_settings.cpp57
-rw-r--r--editor/editor_autoload_settings.h1
-rw-r--r--editor/editor_file_system.cpp495
-rw-r--r--editor/editor_file_system.h46
-rw-r--r--editor/editor_node.cpp46
-rw-r--r--editor/editor_node.h4
-rw-r--r--editor/editor_quick_open.cpp3
-rw-r--r--editor/editor_settings_dialog.cpp15
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/groups_editor.cpp1
-rw-r--r--editor/import/3d/resource_importer_scene.cpp2
-rw-r--r--editor/import/3d/resource_importer_scene.h24
-rw-r--r--editor/plugins/abstract_polygon_2d_editor.cpp2
-rw-r--r--editor/plugins/animation_library_editor.cpp22
-rw-r--r--editor/plugins/animation_library_editor.h1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp46
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h4
-rw-r--r--editor/plugins/editor_plugin.cpp10
-rw-r--r--editor/plugins/gradient_texture_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp30
-rw-r--r--editor/plugins/node_3d_editor_plugin.h1
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp30
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp89
-rw-r--r--editor/plugins/tiles/tile_data_editors.h12
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp9
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp12
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp5
-rw-r--r--editor/project_settings_editor.cpp19
-rw-r--r--editor/project_settings_editor.h1
-rw-r--r--editor/scene_tree_dock.cpp9
-rw-r--r--editor/themes/editor_theme_manager.cpp4
-rw-r--r--main/main.cpp133
-rw-r--r--main/main.h3
-rw-r--r--modules/gdscript/gdscript.cpp226
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp38
-rw-r--r--modules/gdscript/gdscript_cache.cpp41
-rw-r--r--modules/gdscript/gdscript_cache.h3
-rw-r--r--modules/gdscript/gdscript_compiler.cpp8
-rw-r--r--modules/gdscript/gdscript_parser.cpp9
-rw-r--r--modules/gdscript/gdscript_parser.h3
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd23
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out19
-rw-r--r--modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd1
-rw-r--r--modules/gltf/gltf_document.cpp25
-rw-r--r--modules/navigation/nav_map.cpp20
-rw-r--r--modules/openxr/openxr_api.cpp140
-rw-r--r--modules/openxr/openxr_api.h12
-rw-r--r--platform/android/display_server_android.cpp10
-rw-r--r--platform/android/java_godot_lib_jni.cpp53
-rw-r--r--platform/ios/display_server_ios.mm9
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp75
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.h2
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp14
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.h2
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp11
-rw-r--r--platform/web/SCsub2
-rw-r--r--platform/web/js/libs/library_godot_audio.js10
-rw-r--r--platform/web/js/libs/library_godot_javascript_singleton.js16
-rw-r--r--platform/web/web_main.cpp3
-rw-r--r--platform/windows/detect.py27
-rw-r--r--platform/windows/display_server_windows.cpp9
-rw-r--r--scene/2d/animated_sprite_2d.cpp2
-rw-r--r--scene/2d/sprite_2d.cpp4
-rw-r--r--scene/2d/tile_map_layer.cpp6
-rw-r--r--scene/2d/tile_map_layer.h4
-rw-r--r--scene/3d/bone_attachment_3d.cpp11
-rw-r--r--scene/animation/animation_mixer.cpp6
-rw-r--r--scene/animation/animation_player.h4
-rw-r--r--scene/gui/button.cpp2
-rw-r--r--scene/gui/graph_edit.cpp4
-rw-r--r--scene/gui/graph_frame.cpp4
-rw-r--r--scene/gui/graph_frame.h1
-rw-r--r--scene/gui/rich_text_label.cpp2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/texture_rect.cpp10
-rw-r--r--scene/main/node.cpp3
-rw-r--r--scene/resources/animation.cpp14
-rw-r--r--scene/resources/animation.h1
-rw-r--r--scene/resources/atlas_texture.cpp23
-rw-r--r--scene/resources/material.cpp15
-rw-r--r--scene/resources/packed_scene.cpp50
-rw-r--r--scene/resources/packed_scene.h1
-rw-r--r--servers/display_server_headless.h23
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp6
-rw-r--r--servers/rendering/rendering_device.cpp2
-rw-r--r--servers/rendering/shader_language.cpp16
-rw-r--r--tests/core/math/test_vector2.h4
-rw-r--r--tests/core/math/test_vector3.h4
-rw-r--r--tests/core/object/test_class_db.h2
-rw-r--r--tests/core/os/test_os.h2
-rw-r--r--tests/display_server_mock.h22
-rw-r--r--tests/scene/test_instance_placeholder.h2
-rw-r--r--tests/scene/test_node.h2
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/thorvg/AUTHORS8
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/inc/thorvg.h241
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp8
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h10
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp6
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h164
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h142
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h7
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp21
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp4
-rw-r--r--thirdparty/thorvg/src/renderer/tvgCanvas.h22
-rw-r--r--thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp11
-rw-r--r--thirdparty/thorvg/src/renderer/tvgLoader.cpp24
-rw-r--r--thirdparty/thorvg/src/renderer/tvgLoader.h1
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.cpp15
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h10
-rw-r--r--thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp14
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.cpp15
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp16
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh2
155 files changed, 2146 insertions, 1339 deletions
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index ed5e482296..58ad61b621 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -40,6 +40,7 @@
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "core/variant/variant_parser.h"
+#include "servers/rendering_server.h"
#ifdef DEBUG_LOAD_THREADED
#define print_lt(m_text) print_line(m_text)
@@ -585,6 +586,16 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const
*r_progress = _dependency_get_progress(local_path);
}
+ // Support userland polling in a loop on the main thread.
+ if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) {
+ uint64_t frame = Engine::get_singleton()->get_process_frames();
+ if (frame == load_task.last_progress_check_main_thread_frame) {
+ _ensure_load_progress();
+ } else {
+ load_task.last_progress_check_main_thread_frame = frame;
+ }
+ }
+
return status;
}
@@ -613,6 +624,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e
}
return Ref<Resource>();
}
+
+ // Support userland requesting on the main thread before the load is reported to be complete.
+ if (Thread::is_main_thread() && !load_token->local_path.is_empty()) {
+ const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path];
+ while (load_task.status == THREAD_LOAD_IN_PROGRESS) {
+ if (!_ensure_load_progress()) {
+ // This local poll loop is not needed.
+ break;
+ }
+ thread_load_lock.~MutexLock();
+ OS::get_singleton()->delay_usec(1000);
+ new (&thread_load_lock) MutexLock(thread_load_mutex);
+ }
+ }
+
res = _load_complete_inner(*load_token, r_error, thread_load_lock);
if (load_token->unreference()) {
memdelete(load_token);
@@ -731,6 +757,17 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
}
}
+bool ResourceLoader::_ensure_load_progress() {
+ // Some servers may need a new engine iteration to allow the load to progress.
+ // Since the only known one is the rendering server (in single thread mode), let's keep it simple and just sync it.
+ // This may be refactored in the future to support other servers and have less coupling.
+ if (OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD) {
+ return false; // Not needed.
+ }
+ RenderingServer::get_singleton()->sync();
+ return true;
+}
+
Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) {
ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0.
const String &local_path = _validate_local_path(p_path);
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index c48f39b5cc..46df79ea22 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -174,6 +174,7 @@ private:
String type_hint;
float progress = 0.0f;
float max_reported_progress = 0.0f;
+ uint64_t last_progress_check_main_thread_frame = UINT64_MAX;
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
Error error = OK;
@@ -197,6 +198,8 @@ private:
static float _dependency_get_progress(const String &p_path);
+ static bool _ensure_load_progress();
+
public:
static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE);
static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr);
diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp
index 7cfe880b5a..6a564b0ca7 100644
--- a/core/math/transform_interpolator.cpp
+++ b/core/math/transform_interpolator.cpp
@@ -33,44 +33,14 @@
#include "core/math/transform_2d.h"
void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
- // Extract parameters.
- Vector2 p1 = p_prev.get_origin();
- Vector2 p2 = p_curr.get_origin();
-
// Special case for physics interpolation, if flipping, don't interpolate basis.
// If the determinant polarity changes, the handedness of the coordinate system changes.
if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) {
r_result.columns[0] = p_curr.columns[0];
r_result.columns[1] = p_curr.columns[1];
- r_result.set_origin(p1.lerp(p2, p_fraction));
+ r_result.set_origin(p_prev.get_origin().lerp(p_curr.get_origin(), p_fraction));
return;
}
- real_t r1 = p_prev.get_rotation();
- real_t r2 = p_curr.get_rotation();
-
- Size2 s1 = p_prev.get_scale();
- Size2 s2 = p_curr.get_scale();
-
- // Slerp rotation.
- Vector2 v1(Math::cos(r1), Math::sin(r1));
- Vector2 v2(Math::cos(r2), Math::sin(r2));
-
- real_t dot = v1.dot(v2);
-
- dot = CLAMP(dot, -1, 1);
-
- Vector2 v;
-
- if (dot > 0.9995f) {
- v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues.
- } else {
- real_t angle = p_fraction * Math::acos(dot);
- Vector2 v3 = (v2 - v1 * dot).normalized();
- v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
- }
-
- // Construct matrix.
- r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction));
- r_result.scale_basis(s1.lerp(s2, p_fraction));
+ r_result = p_prev.interpolate_with(p_curr, p_fraction);
}
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index a7c0a0353e..caf4ed3835 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -33,7 +33,6 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/os/thread_safe.h"
-#include "core/templates/command_queue_mt.h"
WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1;
@@ -46,7 +45,9 @@ void WorkerThreadPool::Task::free_template_userdata() {
WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
-thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr;
+#ifdef THREADS_ENABLED
+thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {};
+#endif
void WorkerThreadPool::_process_task(Task *p_task) {
#ifdef THREADS_ENABLED
@@ -419,6 +420,34 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
return OK;
}
+void WorkerThreadPool::_lock_unlockable_mutexes() {
+#ifdef THREADS_ENABLED
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlockable_mutexes[i]) {
+ if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
+ ((Mutex *)unlockable_mutexes[i])->lock();
+ } else {
+ ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->lock();
+ }
+ }
+ }
+#endif
+}
+
+void WorkerThreadPool::_unlock_unlockable_mutexes() {
+#ifdef THREADS_ENABLED
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlockable_mutexes[i]) {
+ if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
+ ((Mutex *)unlockable_mutexes[i])->unlock();
+ } else {
+ ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->unlock();
+ }
+ }
+ }
+#endif
+}
+
void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) {
// Keep processing tasks until the condition to stop waiting is met.
@@ -426,6 +455,7 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
while (true) {
Task *task_to_process = nullptr;
+ bool relock_unlockables = false;
{
MutexLock lock(task_mutex);
bool was_signaled = p_caller_pool_thread->signaled;
@@ -463,13 +493,9 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
if (!task_to_process) {
p_caller_pool_thread->awaited_task = p_task;
- if (flushing_cmd_queue) {
- flushing_cmd_queue->unlock();
- }
+ _unlock_unlockable_mutexes();
+ relock_unlockables = true;
p_caller_pool_thread->cond_var.wait(lock);
- if (flushing_cmd_queue) {
- flushing_cmd_queue->lock();
- }
DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER);
p_caller_pool_thread->awaited_task = nullptr;
@@ -477,6 +503,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
}
}
+ if (relock_unlockables) {
+ _lock_unlockable_mutexes();
+ }
+
if (task_to_process) {
_process_task(task_to_process);
}
@@ -603,13 +633,9 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
{
Group *group = *groupp;
- if (flushing_cmd_queue) {
- flushing_cmd_queue->unlock();
- }
+ _unlock_unlockable_mutexes();
group->done_semaphore.wait();
- if (flushing_cmd_queue) {
- flushing_cmd_queue->lock();
- }
+ _lock_unlockable_mutexes();
uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment.
uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later.
@@ -633,16 +659,41 @@ int WorkerThreadPool::get_thread_index() {
return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1;
}
-void WorkerThreadPool::thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue) {
- ERR_FAIL_COND(flushing_cmd_queue != nullptr);
- flushing_cmd_queue = p_queue;
+#ifdef THREADS_ENABLED
+uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) {
+ return _thread_enter_unlock_allowance_zone(p_mutex, false);
+}
+
+uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) {
+ return _thread_enter_unlock_allowance_zone(p_mutex, true);
}
-void WorkerThreadPool::thread_exit_command_queue_mt_flush() {
- ERR_FAIL_NULL(flushing_cmd_queue);
- flushing_cmd_queue = nullptr;
+uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) {
+ for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
+ if (unlikely(unlockable_mutexes[i] == (uintptr_t)p_mutex)) {
+ // Already registered in the current thread.
+ return UINT32_MAX;
+ }
+ if (!unlockable_mutexes[i]) {
+ unlockable_mutexes[i] = (uintptr_t)p_mutex;
+ if (p_is_binary) {
+ unlockable_mutexes[i] |= 1;
+ }
+ return i;
+ }
+ }
+ ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug.");
}
+void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {
+ if (p_zone_id == UINT32_MAX) {
+ return;
+ }
+ DEV_ASSERT(unlockable_mutexes[p_zone_id]);
+ unlockable_mutexes[p_zone_id] = 0;
+}
+#endif
+
void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) {
ERR_FAIL_COND(threads.size() > 0);
if (p_thread_count < 0) {
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index a9cf260a0f..8774143abf 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -41,8 +41,6 @@
#include "core/templates/rid.h"
#include "core/templates/safe_refcount.h"
-class CommandQueueMT;
-
class WorkerThreadPool : public Object {
GDCLASS(WorkerThreadPool, Object)
public:
@@ -163,7 +161,10 @@ private:
static WorkerThreadPool *singleton;
- static thread_local CommandQueueMT *flushing_cmd_queue;
+#ifdef THREADS_ENABLED
+ static const uint32_t MAX_UNLOCKABLE_MUTEXES = 2;
+ static thread_local uintptr_t unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES];
+#endif
TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description);
GroupID _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);
@@ -190,6 +191,13 @@ private:
void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task);
+#ifdef THREADS_ENABLED
+ static uint32_t _thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary);
+#endif
+
+ void _lock_unlockable_mutexes();
+ void _unlock_unlockable_mutexes();
+
protected:
static void _bind_methods();
@@ -232,8 +240,14 @@ public:
static WorkerThreadPool *get_singleton() { return singleton; }
static int get_thread_index();
- static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue);
- static void thread_exit_command_queue_mt_flush();
+#ifdef THREADS_ENABLED
+ static uint32_t thread_enter_unlock_allowance_zone(Mutex *p_mutex);
+ static uint32_t thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex);
+ static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id);
+#else
+ static uint32_t thread_enter_unlock_allowance_zone(void *p_mutex) { return UINT32_MAX; }
+ static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {}
+#endif
void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3);
void finish();
diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h
index 349404d75b..0748e9cb83 100644
--- a/core/templates/command_queue_mt.h
+++ b/core/templates/command_queue_mt.h
@@ -364,7 +364,7 @@ class CommandQueueMT {
lock();
- WorkerThreadPool::thread_enter_command_queue_mt_flush(this);
+ uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&mutex);
while (flush_read_ptr < command_mem.size()) {
uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr];
flush_read_ptr += 8;
@@ -383,7 +383,7 @@ class CommandQueueMT {
flush_read_ptr += size;
}
- WorkerThreadPool::thread_exit_command_queue_mt_flush();
+ WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
command_mem.clear();
flush_read_ptr = 0;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 30a8facd67..c1ef31c784 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -951,7 +951,7 @@ bool Variant::is_zero() const {
return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID();
}
case OBJECT: {
- return get_validated_object() == nullptr;
+ return _get_obj().obj == nullptr;
}
case CALLABLE: {
return reinterpret_cast<const Callable *>(_data._mem)->is_null();
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index 0b94d79a97..ac39a4135f 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -548,14 +548,14 @@ public:
class OperatorEvaluatorEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const ObjectID &a = VariantInternal::get_object_id(&p_left);
- const ObjectID &b = VariantInternal::get_object_id(&p_right);
+ const Object *a = p_left.get_validated_object();
+ const Object *b = p_right.get_validated_object();
*r_ret = a == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const ObjectID &a = VariantInternal::get_object_id(left);
- const ObjectID &b = VariantInternal::get_object_id(right);
+ const Object *a = left->get_validated_object();
+ const Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -567,12 +567,12 @@ public:
class OperatorEvaluatorEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *a = p_left.operator Object *();
+ const Object *a = p_left.get_validated_object();
*r_ret = a == nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *a = left->operator Object *();
+ const Object *a = left->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -584,12 +584,12 @@ public:
class OperatorEvaluatorEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const Object *b = p_right.operator Object *();
+ const Object *b = p_right.get_validated_object();
*r_ret = nullptr == b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const Object *b = right->operator Object *();
+ const Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -619,14 +619,14 @@ public:
class OperatorEvaluatorNotEqualObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- const ObjectID &a = VariantInternal::get_object_id(&p_left);
- const ObjectID &b = VariantInternal::get_object_id(&p_right);
+ Object *a = p_left.get_validated_object();
+ Object *b = p_right.get_validated_object();
*r_ret = a != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- const ObjectID &a = VariantInternal::get_object_id(left);
- const ObjectID &b = VariantInternal::get_object_id(right);
+ Object *a = left->get_validated_object();
+ Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -638,12 +638,12 @@ public:
class OperatorEvaluatorNotEqualObjectNil {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *a = p_left.operator Object *();
+ Object *a = p_left.get_validated_object();
*r_ret = a != nullptr;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *a = left->operator Object *();
+ Object *a = left->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
@@ -655,12 +655,12 @@ public:
class OperatorEvaluatorNotEqualNilObject {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
- Object *b = p_right.operator Object *();
+ Object *b = p_right.get_validated_object();
*r_ret = nullptr != b;
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- Object *b = right->operator Object *();
+ Object *b = right->get_validated_object();
*VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b;
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index c635eba2ab..8a493ce91f 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -27,6 +27,13 @@
<param index="1" name="library" type="AnimationLibrary" />
<description>
Adds [param library] to the animation player, under the key [param name].
+ AnimationMixer has a global library by default with an empty string as key. For adding an animation to the global library:
+ [codeblocks]
+ [gdscript]
+ var global_library = mixer.get_animation_library("")
+ global_library.add_animation("animation_name", animation_resource)
+ [/gdscript]
+ [/codeblocks]
</description>
</method>
<method name="advance">
@@ -182,10 +189,10 @@
func _process(delta):
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
- var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_Quaternion_accumulator()
+ var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_rotation_accumulator()
var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator
prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator
- transform.basis *= difference
+ transform.basis *= Basis(difference)
[/gdscript]
[/codeblocks]
However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
diff --git a/doc/classes/AudioEffectSpectrumAnalyzer.xml b/doc/classes/AudioEffectSpectrumAnalyzer.xml
index fbc0c2275f..b90f87ef5b 100644
--- a/doc/classes/AudioEffectSpectrumAnalyzer.xml
+++ b/doc/classes/AudioEffectSpectrumAnalyzer.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
This audio effect does not affect sound output, but can be used for real-time audio visualizations.
+ This resource configures an [AudioEffectSpectrumAnalyzerInstance], which performs the actual analysis at runtime. An instance can be acquired with [method AudioServer.get_bus_effect_instance].
See also [AudioStreamGenerator] for procedurally generating sounds.
</description>
<tutorials>
diff --git a/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml b/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml
index e8c6394073..184f80db2e 100644
--- a/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml
+++ b/doc/classes/AudioEffectSpectrumAnalyzerInstance.xml
@@ -1,10 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AudioEffectSpectrumAnalyzerInstance" inherits="AudioEffectInstance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
+ Queryable instance of an [AudioEffectSpectrumAnalyzer].
</brief_description>
<description>
+ The runtime part of an [AudioEffectSpectrumAnalyzer], which can be used to query the magnitude of a frequency range on its host bus.
+ An instance of this class can be acquired with [method AudioServer.get_bus_effect_instance].
</description>
<tutorials>
+ <link title="Audio Spectrum Visualizer Demo">https://godotengine.org/asset-library/asset/2762</link>
</tutorials>
<methods>
<method name="get_magnitude_for_frequency_range" qualifiers="const">
@@ -13,15 +17,17 @@
<param index="1" name="to_hz" type="float" />
<param index="2" name="mode" type="int" enum="AudioEffectSpectrumAnalyzerInstance.MagnitudeMode" default="1" />
<description>
+ Returns the magnitude of the frequencies from [param from_hz] to [param to_hz] in linear energy as a Vector2. The [code]x[/code] component of the return value represents the left stereo channel, and [code]y[/code] represents the right channel.
+ [param mode] determines how the frequency range will be processed. See [enum MagnitudeMode].
</description>
</method>
</methods>
<constants>
<constant name="MAGNITUDE_AVERAGE" value="0" enum="MagnitudeMode">
- Use the average value as magnitude.
+ Use the average value across the frequency range as magnitude.
</constant>
<constant name="MAGNITUDE_MAX" value="1" enum="MagnitudeMode">
- Use the maximum value as magnitude.
+ Use the maximum value of the frequency range as magnitude.
</constant>
</constants>
</class>
diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml
index 41bda1033d..322d2ab9d4 100644
--- a/doc/classes/Basis.xml
+++ b/doc/classes/Basis.xml
@@ -8,7 +8,7 @@
A [Basis] is composed by 3 axis vectors, each representing a column of the matrix: [member x], [member y], and [member z]. The length of each axis ([method Vector3.length]) influences the basis's scale, while the direction of all axes influence the rotation. Usually, these axes are perpendicular to one another. However, when you rotate any axis individually, the basis becomes sheared. Applying a sheared basis to a 3D model will make the model appear distorted.
A [Basis] is [b]orthogonal[/b] if its axes are perpendicular to each other. A basis is [b]normalized[/b] if the length of every axis is [code]1[/code]. A basis is [b]uniform[/b] if all axes share the same length (see [method get_scale]). A basis is [b]orthonormal[/b] if it is both orthogonal and normalized, which allows it to only represent rotations. A basis is [b]conformal[/b] if it is both orthogonal and uniform, which ensures it is not distorted.
For a general introduction, see the [url=$DOCS_URL/tutorials/math/matrices_and_transforms.html]Matrices and transforms[/url] tutorial.
- [b]Note:[/b] Godot uses a [url=https://en.wikipedia.org/wiki/Right-hand_rule]right-handed coordinate system[/url], which is a common standard. For directions, the convention for built-in types like [Camera3D] is for -Z to point forward (+X is right, +Y is up, and +Z is back). Other objects may use different direction conventions. For more information, see the [url=$DOCS_URL/tutorials/assets_pipeline/importing_scenes.html#d-asset-direction-conventions]Importing 3D Scenes[/url] tutorial.
+ [b]Note:[/b] Godot uses a [url=https://en.wikipedia.org/wiki/Right-hand_rule]right-handed coordinate system[/url], which is a common standard. For directions, the convention for built-in types like [Camera3D] is for -Z to point forward (+X is right, +Y is up, and +Z is back). Other objects may use different direction conventions. For more information, see the [url=$DOCS_URL/tutorials/assets_pipeline/importing_3d_scenes/model_export_considerations.html#d-asset-direction-conventions]3D asset direction conventions[/url] tutorial.
[b]Note:[/b] The basis matrices are exposed as [url=https://www.mindcontrol.org/~hplus/graphics/matrix-layout.html]column-major[/url] order, which is the same as OpenGL. However, they are stored internally in row-major order, which is the same as DirectX.
</description>
<tutorials>
diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml
index 98f25ed573..68fb918904 100644
--- a/doc/classes/Button.xml
+++ b/doc/classes/Button.xml
@@ -119,7 +119,7 @@
Icon modulate [Color] used when the [Button] is being pressed.
</theme_item>
<theme_item name="align_to_largest_stylebox" data_type="constant" type="int" default="0">
- This constant acts as a boolean. If [code]true[/code], text and icon are always aligned to the largest stylebox margins, otherwise it's aligned to the current button state stylebox margins.
+ This constant acts as a boolean. If [code]true[/code], the minimum size of the button and text/icon alignment is always based on the largest stylebox margins, otherwise it's based on the current button state stylebox margins.
</theme_item>
<theme_item name="h_separation" data_type="constant" type="int" default="4">
The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used.
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index 207045b065..8bee6c3470 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -623,8 +623,8 @@
[b]Note:[/b] For controls that inherit [Popup], the correct way to make them visible is to call one of the multiple [code]popup*()[/code] functions instead.
</member>
<member name="y_sort_enabled" type="bool" setter="set_y_sort_enabled" getter="is_y_sort_enabled" default="false">
- If [code]true[/code], this and child [CanvasItem] nodes with a lower Y position are rendered in front of nodes with a higher Y position. If [code]false[/code], this and child [CanvasItem] nodes are rendered normally in scene tree order.
- With Y-sorting enabled on a parent node ('A') but disabled on a child node ('B'), the child node ('B') is sorted but its children ('C1', 'C2', etc) render together on the same Y position as the child node 'B'. This allows you to organize the render order of a scene without changing the scene tree.
+ If [code]true[/code], this and child [CanvasItem] nodes with a higher Y position are rendered in front of nodes with a lower Y position. If [code]false[/code], this and child [CanvasItem] nodes are rendered normally in scene tree order.
+ With Y-sorting enabled on a parent node ('A') but disabled on a child node ('B'), the child node ('B') is sorted but its children ('C1', 'C2', etc) render together on the same Y position as the child node ('B'). This allows you to organize the render order of a scene without changing the scene tree.
Nodes sort relative to each other only if they are on the same [member z_index].
</member>
<member name="z_as_relative" type="bool" setter="set_z_as_relative" getter="is_z_relative" default="true">
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 3e2b3ea111..4d304cf5fd 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -17,7 +17,7 @@
<param index="1" name="features" type="PackedStringArray" />
<description>
Return [code]true[/code] if this plugin will customize resources based on the platform and features used.
- When enabled, [method _get_customization_configuration_hash], [method _customize_resource] and [method _customize_scene] will be called and must be implemented.
+ When enabled, [method _get_customization_configuration_hash] and [method _customize_resource] will be called and must be implemented.
</description>
</method>
<method name="_begin_customize_scenes" qualifiers="virtual const">
@@ -25,7 +25,8 @@
<param index="0" name="platform" type="EditorExportPlatform" />
<param index="1" name="features" type="PackedStringArray" />
<description>
- Return true if this plugin will customize scenes based on the platform and features used.
+ Return [code]true[/code] if this plugin will customize scenes based on the platform and features used.
+ When enabled, [method _get_customization_configuration_hash] and [method _customize_scene] will be called and must be implemented.
</description>
</method>
<method name="_customize_resource" qualifiers="virtual">
@@ -35,6 +36,7 @@
<description>
Customize a resource. If changes are made to it, return the same or a new resource. Otherwise, return [code]null[/code].
The [i]path[/i] argument is only used when customizing an actual file, otherwise this means that this resource is part of another one and it will be empty.
+ Calling [method skip] inside this callback will make the file not included in the export.
Implementing this method is required if [method _begin_customize_resources] returns [code]true[/code].
</description>
</method>
@@ -44,6 +46,7 @@
<param index="1" name="path" type="String" />
<description>
Customize a scene. If changes are made to it, return the same or a new scene. Otherwise, return [code]null[/code]. If a new scene is returned, it is up to you to dispose of the old one.
+ Calling [method skip] inside this callback will make the file not included in the export.
Implementing this method is required if [method _begin_customize_scenes] returns [code]true[/code].
</description>
</method>
@@ -81,8 +84,9 @@
<param index="1" name="type" type="String" />
<param index="2" name="features" type="PackedStringArray" />
<description>
- Virtual method to be overridden by the user. Called for each exported file, providing arguments that can be used to identify the file. [param path] is the path of the file, [param type] is the [Resource] represented by the file (e.g. [PackedScene]) and [param features] is the list of features for the export.
+ Virtual method to be overridden by the user. Called for each exported file, except for imported resources (resources that have an associated [code].import[/code] file). The arguments can be used to identify the file. [param path] is the path of the file, [param type] is the [Resource] represented by the file (e.g. [PackedScene]), and [param features] is the list of features for the export.
Calling [method skip] inside this callback will make the file not included in the export.
+ Use [method _customize_resource] for imported resources that are not handled by this function.
</description>
</method>
<method name="_get_android_dependencies" qualifiers="virtual const">
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index 89b9e7d6c2..c3ea440aea 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -444,7 +444,7 @@
<param index="2" name="script" type="Script" />
<param index="3" name="icon" type="Texture2D" />
<description>
- Adds a custom type, which will appear in the list of nodes or resources. An icon can be optionally passed.
+ Adds a custom type, which will appear in the list of nodes or resources.
When a given node or resource is selected, the base type will be instantiated (e.g. "Node3D", "Control", "Resource"), then the script will be loaded and set to this object.
[b]Note:[/b] The base type is the base engine class which this type's class hierarchy inherits, not any custom type parent classes.
You can use the virtual method [method _handles] to check if your custom object is being edited by checking the script or using the [code]is[/code] keyword.
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index c5737b235f..c97ae197d9 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -1044,7 +1044,7 @@
[b]Note:[/b] The [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_styleguide.html]GDScript style guide[/url] recommends using tabs for indentation. It is advised to change this setting only if you need to work on a project that currently uses spaces for indentation.
</member>
<member name="text_editor/behavior/navigation/custom_word_separators" type="String" setter="" getter="">
- The characters to consider as word delimiters if [member text_editor/behavior/navigation/use_custom_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]#_![/code].
+ The characters to consider as word delimiters if [member text_editor/behavior/navigation/use_custom_word_separators] is [code]true[/code]. This is in addition to default characters if [member text_editor/behavior/navigation/use_default_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]_♥=[/code].
</member>
<member name="text_editor/behavior/navigation/drag_and_drop_selection" type="bool" setter="" getter="">
If [code]true[/code], allows drag-and-dropping text in the script editor to move text. Disable this if you find yourself accidentally drag-and-dropping text in the script editor.
@@ -1066,10 +1066,10 @@
If [code]true[/code], prevents automatically switching between the Script and 2D/3D screens when selecting a node in the Scene tree dock.
</member>
<member name="text_editor/behavior/navigation/use_custom_word_separators" type="bool" setter="" getter="">
- If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member text_editor/behavior/navigation/use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member text_editor/behavior/navigation/custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ If [code]true[/code], uses the characters in [member text_editor/behavior/navigation/custom_word_separators] as word separators for word navigation and operations. This is in addition to the default characters if [member text_editor/behavior/navigation/use_default_word_separators] is also enabled. Word navigation and operations include double-clicking on a word or holding [kbd]Ctrl[/kbd] ([kbd]Cmd[/kbd] on macOS) while pressing [kbd]left[/kbd], [kbd]right[/kbd], [kbd]backspace[/kbd], or [kbd]delete[/kbd].
</member>
<member name="text_editor/behavior/navigation/use_default_word_separators" type="bool" setter="" getter="">
- If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|&lt;&gt;[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ If [code]true[/code], uses the characters in [code]`!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^`{|}~[/code], the Unicode General Punctuation table, and the Unicode CJK Punctuation table as word separators for word navigation and operations. If [code]false[/code], a subset of these characters are used and does not include the characters [code]&lt;&gt;$~^=+|[/code]. This is in addition to custom characters if [member text_editor/behavior/navigation/use_custom_word_separators] is also enabled. These characters are used to determine where a word stops. Word navigation and operations include double-clicking on a word or holding [kbd]Ctrl[/kbd] ([kbd]Cmd[/kbd] on macOS) while pressing [kbd]left[/kbd], [kbd]right[/kbd], [kbd]backspace[/kbd], or [kbd]delete[/kbd].
</member>
<member name="text_editor/behavior/navigation/v_scroll_speed" type="int" setter="" getter="">
The number of pixels to scroll with every mouse wheel increment. Higher values make the script scroll by faster when using the mouse wheel.
@@ -1091,7 +1091,7 @@
The delay in seconds after which autocompletion suggestions should be displayed when the user stops typing.
</member>
<member name="text_editor/completion/code_complete_enabled" type="bool" setter="" getter="">
- If [code]true[/code], code completion will be triggered automatically after [member text_editor/completion/code_complete_delay]. If [code]false[/code], you can still trigger completion manually by pressing [kbd]Ctrl + Space[/kbd] ([kbd]Cmd + Space[/kbd] on macOS).
+ If [code]true[/code], code completion will be triggered automatically after [member text_editor/completion/code_complete_delay]. Even if [code]false[/code], code completion can be triggered manually with the [code]ui_text_completion_query[/code] action (by default [kbd]Ctrl + Space[/kbd] or [kbd]Cmd + Space[/kbd] on macOS).
</member>
<member name="text_editor/completion/colorize_suggestions" type="bool" setter="" getter="">
If [code]true[/code] enables the coloring for some items in the autocompletion suggestions, like vector components.
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml
index 001839d745..670df10a89 100644
--- a/doc/classes/GraphEdit.xml
+++ b/doc/classes/GraphEdit.xml
@@ -7,6 +7,7 @@
[GraphEdit] provides tools for creation, manipulation, and display of various graphs. Its main purpose in the engine is to power the visual programming systems, such as visual shaders, but it is also available for use in user projects.
[GraphEdit] by itself is only an empty container, representing an infinite grid where [GraphNode]s can be placed. Each [GraphNode] represents a node in the graph, a single unit of data in the connected scheme. [GraphEdit], in turn, helps to control various interactions with nodes and between nodes. When the user attempts to connect, disconnect, or delete a [GraphNode], a signal is emitted in the [GraphEdit], but no action is taken by default. It is the responsibility of the programmer utilizing this control to implement the necessary logic to determine how each request should be handled.
[b]Performance:[/b] It is greatly advised to enable low-processor usage mode (see [member OS.low_processor_usage_mode]) when using GraphEdits.
+ [b]Note:[/b] Keep in mind that [method Node.get_children] will also return the connection layer node named [code]_connection_layer[/code] due to technical limitations. This behavior may change in future releases.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml
index 6b5a50d97b..7f4dd5af37 100644
--- a/doc/classes/Mesh.xml
+++ b/doc/classes/Mesh.xml
@@ -223,6 +223,7 @@
</constant>
<constant name="ARRAY_NORMAL" value="1" enum="ArrayType">
[PackedVector3Array] of vertex normals.
+ [b]Note:[/b] The array has to consist of normal vectors, otherwise they will be normalized by the engine, potentially causing visual discrepancies.
</constant>
<constant name="ARRAY_TANGENT" value="2" enum="ArrayType">
[PackedFloat32Array] of vertex tangents. Each element in groups of 4 floats, first 3 floats determine the tangent, and the last the binormal direction as -1 or 1.
diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml
index b4969b8906..d0ec81ab45 100644
--- a/doc/classes/NodePath.xml
+++ b/doc/classes/NodePath.xml
@@ -23,11 +23,12 @@
[/codeblock]
Despite their name, node paths may also point to a property:
[codeblock]
- ^"position" # Points to this object's position.
- ^"position:x" # Points to this object's position in the x axis.
+ ^":position" # Points to this object's position.
+ ^":position:x" # Points to this object's position in the x axis.
^"Camera3D:rotation:y" # Points to the child Camera3D and its y rotation.
^"/root:size:x" # Points to the root Window and its width.
[/codeblock]
+ In some situations, it's possible to omit the leading [code]:[/code] when pointing to an object's property. As an example, this is the case with [method Object.set_indexed] and [method Tween.tween_property], as those methods call [method NodePath.get_as_property_path] under the hood. However, it's generally recommended to keep the [code]:[/code] prefix.
Node paths cannot check whether they are valid and may point to nodes or properties that do not exist. Their meaning depends entirely on the context in which they're used.
You usually do not have to worry about the [NodePath] type, as strings are automatically converted to the type when necessary. There are still times when defining node paths is useful. For example, exported [NodePath] properties allow you to easily select any node within the currently edited scene. They are also automatically updated when moving, renaming or deleting nodes in the scene tree editor. See also [annotation @GDScript.@export_node_path].
See also [StringName], which is a similar type designed for optimized strings.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 8085b20730..b1556783d5 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -477,6 +477,9 @@
<member name="debug/gdscript/warnings/assert_always_true" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an [code]assert[/code] call always evaluates to true.
</member>
+ <member name="debug/gdscript/warnings/confusable_capture_reassignment" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable captured by a lambda is reassigned, since this does not modify the outer local variable.
+ </member>
<member name="debug/gdscript/warnings/confusable_identifier" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when an identifier contains characters that can be confused with something else, like when mixing different alphabets.
</member>
@@ -2352,7 +2355,6 @@
</member>
<member name="rendering/anti_aliasing/quality/msaa_3d" type="int" setter="" getter="" default="0">
Sets the number of MSAA samples to use for 3D rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. See also [member rendering/scaling_3d/mode] for supersampling, which provides higher quality but is much more expensive. This has no effect on shader-induced aliasing or texture aliasing.
- [b]Note:[/b] MSAA is only supported in the Forward+ and Mobile rendering methods, not Compatibility.
</member>
<member name="rendering/anti_aliasing/quality/screen_space_aa" type="int" setter="" getter="" default="0">
Sets the screen-space antialiasing mode for the default screen [Viewport]. Screen-space antialiasing works by selectively blurring edges in a post-process shader. It differs from MSAA which takes multiple coverage samples while rendering objects. Screen-space AA methods are typically faster than MSAA and will smooth out specular aliasing, but tend to make scenes appear blurry. The blurriness is partially counteracted by automatically using a negative mipmap LOD bias (see [member rendering/textures/default_filters/texture_mipmap_bias]).
@@ -2924,11 +2926,11 @@
</member>
<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
- [b]Note:[/b] Only works on compatibility renderer.
+ [b]Note:[/b] Only works on the Compatibility rendering method.
</member>
<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default="&quot;0&quot;">
Applied foveation level if supported: 0 = off, 1 = low, 2 = medium, 3 = high.
- [b]Note:[/b] Only works on compatibility renderer.
+ [b]Note:[/b] Only works on the Compatibility rendering method. On platforms other than Android, if [member rendering/anti_aliasing/quality/msaa_3d] is enabled, this feature will be disabled.
</member>
<member name="xr/openxr/reference_space" type="int" setter="" getter="" default="&quot;1&quot;">
Specify the default reference space.
diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml
index 1961ca2b0e..cb0db46595 100644
--- a/doc/classes/ResourceLoader.xml
+++ b/doc/classes/ResourceLoader.xml
@@ -87,7 +87,7 @@
<param index="0" name="path" type="String" />
<description>
Returns the resource loaded by [method load_threaded_request].
- If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading.
+ If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading. However, it's recommended to use [method load_threaded_get_status] to known when the load has actually completed.
</description>
</method>
<method name="load_threaded_get_status">
@@ -97,6 +97,7 @@
<description>
Returns the status of a threaded loading operation started with [method load_threaded_request] for the resource at [param path]. See [enum ThreadLoadStatus] for possible return values.
An array variable can optionally be passed via [param progress], and will return a one-element array containing the percentage of completion of the threaded loading.
+ [b]Note:[/b] The recommended way of using this method is to call it during different frames (e.g., in [method Node._process], instead of a loop).
</description>
</method>
<method name="load_threaded_request">
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index f82671b533..f71601f9ae 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1370,7 +1370,7 @@
If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
</member>
<member name="use_default_word_separators" type="bool" setter="set_use_default_word_separators" getter="is_default_word_separators_enabled" default="true">
- If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|&lt;&gt;[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is part of [code]!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^`{|}~[/code], the Unicode General Punctuation table, or the Unicode CJK Punctuation table. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
</member>
<member name="virtual_keyboard_enabled" type="bool" setter="set_virtual_keyboard_enabled" getter="is_virtual_keyboard_enabled" default="true">
If [code]true[/code], the native virtual keyboard is shown when focused on platforms that support it.
diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml
index 2a0bbc46af..30c141659a 100644
--- a/doc/classes/Transform3D.xml
+++ b/doc/classes/Transform3D.xml
@@ -6,7 +6,7 @@
<description>
The [Transform3D] built-in [Variant] type is a 3×4 matrix representing a transformation in 3D space. It contains a [Basis], which on its own can represent rotation, scale, and shear. Additionally, combined with its own [member origin], the transform can also represent a translation.
For a general introduction, see the [url=$DOCS_URL/tutorials/math/matrices_and_transforms.html]Matrices and transforms[/url] tutorial.
- [b]Note:[/b] Godot uses a [url=https://en.wikipedia.org/wiki/Right-hand_rule]right-handed coordinate system[/url], which is a common standard. For directions, the convention for built-in types like [Camera3D] is for -Z to point forward (+X is right, +Y is up, and +Z is back). Other objects may use different direction conventions. For more information, see the [url=$DOCS_URL/tutorials/assets_pipeline/importing_scenes.html#d-asset-direction-conventions]Importing 3D Scenes[/url] tutorial.
+ [b]Note:[/b] Godot uses a [url=https://en.wikipedia.org/wiki/Right-hand_rule]right-handed coordinate system[/url], which is a common standard. For directions, the convention for built-in types like [Camera3D] is for -Z to point forward (+X is right, +Y is up, and +Z is back). Other objects may use different direction conventions. For more information, see the [url=$DOCS_URL/tutorials/assets_pipeline/importing_3d_scenes/model_export_considerations.html#d-asset-direction-conventions]3D asset direction conventions[/url] tutorial.
</description>
<tutorials>
<link title="Math documentation index">$DOCS_URL/tutorials/math/index.html</link>
diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp
index ca2b392e66..a6bce1d79a 100644
--- a/drivers/d3d12/rendering_device_driver_d3d12.cpp
+++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp
@@ -872,6 +872,7 @@ RDD::BufferID RenderingDeviceDriverD3D12::buffer_create(uint64_t p_size, BitFiel
D3D12MA::ALLOCATION_DESC allocation_desc = {};
allocation_desc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
+ D3D12_RESOURCE_STATES initial_state = D3D12_RESOURCE_STATE_COMMON;
switch (p_allocation_type) {
case MEMORY_ALLOCATION_TYPE_CPU: {
bool is_src = p_usage.has_flag(BUFFER_USAGE_TRANSFER_FROM_BIT);
@@ -879,10 +880,12 @@ RDD::BufferID RenderingDeviceDriverD3D12::buffer_create(uint64_t p_size, BitFiel
if (is_src && !is_dst) {
// Looks like a staging buffer: CPU maps, writes sequentially, then GPU copies to VRAM.
allocation_desc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
+ initial_state = D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (is_dst && !is_src) {
// Looks like a readback buffer: GPU copies from VRAM, then CPU maps and reads.
allocation_desc.HeapType = D3D12_HEAP_TYPE_READBACK;
+ initial_state = D3D12_RESOURCE_STATE_COPY_DEST;
}
} break;
case MEMORY_ALLOCATION_TYPE_GPU: {
@@ -911,7 +914,7 @@ RDD::BufferID RenderingDeviceDriverD3D12::buffer_create(uint64_t p_size, BitFiel
res = allocator->CreateResource(
&allocation_desc,
reinterpret_cast<const D3D12_RESOURCE_DESC *>(&resource_desc),
- D3D12_RESOURCE_STATE_COMMON,
+ initial_state,
nullptr,
allocation.GetAddressOf(),
IID_PPV_ARGS(buffer.GetAddressOf()));
@@ -925,7 +928,7 @@ RDD::BufferID RenderingDeviceDriverD3D12::buffer_create(uint64_t p_size, BitFiel
buf_info->resource = buffer.Get();
buf_info->owner_info.resource = buffer;
buf_info->owner_info.allocation = allocation;
- buf_info->owner_info.states.subresource_states.push_back(D3D12_RESOURCE_STATE_COMMON);
+ buf_info->owner_info.states.subresource_states.push_back(initial_state);
buf_info->states_ptr = &buf_info->owner_info.states;
buf_info->size = p_size;
buf_info->flags.usable_as_uav = (resource_desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index bacf607c66..21790d29b5 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1111,15 +1111,15 @@ MaterialStorage::MaterialStorage() {
global_shader_uniforms.buffer_size = MAX(16, (int)GLOBAL_GET("rendering/limits/global_shader_variables/buffer_size"));
if (global_shader_uniforms.buffer_size * sizeof(GlobalShaderUniforms::Value) > uint32_t(Config::get_singleton()->max_uniform_buffer_size)) {
+ // Limit to maximum support UBO size.
global_shader_uniforms.buffer_size = uint32_t(Config::get_singleton()->max_uniform_buffer_size) / sizeof(GlobalShaderUniforms::Value);
- WARN_PRINT("Project setting \"rendering/limits/global_shader_variables/buffer_size\" exceeds maximum uniform buffer size of: " + itos(Config::get_singleton()->max_uniform_buffer_size / sizeof(GlobalShaderUniforms::Value)) + ". Falling back on maximum buffer size.");
}
global_shader_uniforms.buffer_values = memnew_arr(GlobalShaderUniforms::Value, global_shader_uniforms.buffer_size);
memset(global_shader_uniforms.buffer_values, 0, sizeof(GlobalShaderUniforms::Value) * global_shader_uniforms.buffer_size);
global_shader_uniforms.buffer_usage = memnew_arr(GlobalShaderUniforms::ValueUsage, global_shader_uniforms.buffer_size);
- global_shader_uniforms.buffer_dirty_regions = memnew_arr(bool, global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
- memset(global_shader_uniforms.buffer_dirty_regions, 0, sizeof(bool) * global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
+ global_shader_uniforms.buffer_dirty_regions = memnew_arr(bool, 1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE));
+ memset(global_shader_uniforms.buffer_dirty_regions, 0, sizeof(bool) * (1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE)));
glGenBuffers(1, &global_shader_uniforms.buffer);
glBindBuffer(GL_UNIFORM_BUFFER, global_shader_uniforms.buffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(GlobalShaderUniforms::Value) * global_shader_uniforms.buffer_size, nullptr, GL_DYNAMIC_DRAW);
@@ -1788,7 +1788,7 @@ void MaterialStorage::global_shader_parameter_add(const StringName &p_name, RS::
//is vector, allocate in buffer and update index
gv.buffer_index = _global_shader_uniform_allocate(gv.buffer_elements);
- ERR_FAIL_COND_MSG(gv.buffer_index < 0, vformat("Failed allocating global variable '%s' out of buffer memory. Consider increasing it in the Project Settings.", String(p_name)));
+ ERR_FAIL_COND_MSG(gv.buffer_index < 0, vformat("Failed allocating global variable '%s' out of buffer memory. Consider increasing rendering/limits/global_shader_variables/buffer_size in the Project Settings. Maximum items supported by this hardware is: %d.", String(p_name), Config::get_singleton()->max_uniform_buffer_size / sizeof(GlobalShaderUniforms::Value)));
global_shader_uniforms.buffer_usage[gv.buffer_index].elements = gv.buffer_elements;
_global_shader_uniform_store_in_buffer(gv.buffer_index, gv.type, gv.value);
_global_shader_uniform_mark_buffer_dirty(gv.buffer_index, gv.buffer_elements);
@@ -1999,7 +1999,7 @@ int32_t MaterialStorage::global_shader_parameters_instance_allocate(RID p_instan
ERR_FAIL_COND_V(global_shader_uniforms.instance_buffer_pos.has(p_instance), -1);
int32_t pos = _global_shader_uniform_allocate(ShaderLanguage::MAX_INSTANCE_UNIFORM_INDICES);
global_shader_uniforms.instance_buffer_pos[p_instance] = pos; //save anyway
- ERR_FAIL_COND_V_MSG(pos < 0, -1, "Too many instances using shader instance variables. Increase buffer size in Project Settings.");
+ ERR_FAIL_COND_V_MSG(pos < 0, -1, vformat("Too many instances using shader instance variables. Consider increasing rendering/limits/global_shader_variables/buffer_size in the Project Settings. Maximum items supported by this hardware is: %d.", Config::get_singleton()->max_uniform_buffer_size / sizeof(GlobalShaderUniforms::Value)));
global_shader_uniforms.buffer_usage[pos].elements = ShaderLanguage::MAX_INSTANCE_UNIFORM_INDICES;
return pos;
}
@@ -2079,7 +2079,7 @@ void MaterialStorage::global_shader_parameters_instance_update(RID p_instance, i
void MaterialStorage::_update_global_shader_uniforms() {
MaterialStorage *material_storage = MaterialStorage::get_singleton();
if (global_shader_uniforms.buffer_dirty_region_count > 0) {
- uint32_t total_regions = global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE;
+ uint32_t total_regions = 1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
if (total_regions / global_shader_uniforms.buffer_dirty_region_count <= 4) {
// 25% of regions dirty, just update all buffer
glBindBuffer(GL_UNIFORM_BUFFER, global_shader_uniforms.buffer);
diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub
index 80d5f35305..1efef5ad77 100644
--- a/drivers/vulkan/SCsub
+++ b/drivers/vulkan/SCsub
@@ -16,14 +16,14 @@ if env["use_volk"]:
if env["platform"] == "android":
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_ANDROID_KHR"])
elif env["platform"] == "ios":
- env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK"])
+ env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_IOS_MVK", "VK_USE_PLATFORM_METAL_EXT"])
elif env["platform"] == "linuxbsd":
if env["x11"]:
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_XLIB_KHR"])
if env["wayland"]:
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_WAYLAND_KHR"])
elif env["platform"] == "macos":
- env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_MACOS_MVK"])
+ env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_MACOS_MVK", "VK_USE_PLATFORM_METAL_EXT"])
elif env["platform"] == "windows":
env.AppendUnique(CPPDEFINES=["VK_USE_PLATFORM_WIN32_KHR"])
diff --git a/drivers/vulkan/rendering_context_driver_vulkan.cpp b/drivers/vulkan/rendering_context_driver_vulkan.cpp
index 7cba820978..fe2ff5e0da 100644
--- a/drivers/vulkan/rendering_context_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_context_driver_vulkan.cpp
@@ -102,6 +102,10 @@ Error RenderingContextDriverVulkan::_initialize_instance_extensions() {
// This extension allows us to use the properties2 features to query additional device capabilities.
_register_requested_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
+#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED))
+ _register_requested_instance_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, true);
+#endif
+
// Only enable debug utils in verbose mode or DEV_ENABLED.
// End users would get spammed with messages of varying verbosity due to the
// mess that thirdparty layers/extensions and drivers seem to leave in their
@@ -360,6 +364,11 @@ Error RenderingContextDriverVulkan::_initialize_instance() {
VkInstanceCreateInfo instance_info = {};
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+
+#if defined(USE_VOLK) && (defined(MACOS_ENABLED) || defined(IOS_ENABLED))
+ instance_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+#endif
+
instance_info.pApplicationInfo = &app_info;
instance_info.enabledExtensionCount = enabled_extension_names.size();
instance_info.ppEnabledExtensionNames = enabled_extension_names.ptr();
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index ffd609d925..aa8962b580 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -682,6 +682,7 @@ void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) {
editor = p_editor;
connect("clear_selection", callable_mp(editor, &AnimationTrackEditor::_clear_selection).bind(false));
connect("select_key", callable_mp(editor, &AnimationTrackEditor::_key_selected), CONNECT_DEFERRED);
+ connect("deselect_key", callable_mp(editor, &AnimationTrackEditor::_key_deselected), CONNECT_DEFERRED);
}
void AnimationBezierTrackEdit::_play_position_draw() {
@@ -871,14 +872,14 @@ void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::Hand
}
void AnimationBezierTrackEdit::_clear_selection_for_anim(const Ref<Animation> &p_anim) {
- if (!(animation == p_anim)) {
+ if (!(animation == p_anim) || !is_visible()) {
return;
}
_clear_selection();
}
-void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos) {
- if (!(animation == p_anim)) {
+void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single) {
+ if (!(animation == p_anim) || !is_visible()) {
return;
}
@@ -886,7 +887,7 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int
ERR_FAIL_COND(idx < 0);
selection.insert(IntPair(p_track, idx));
- emit_signal(SNAME("select_key"), idx, true, p_track);
+ emit_signal(SNAME("select_key"), idx, p_single, p_track);
queue_redraw();
}
@@ -1002,7 +1003,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
} else if (ED_IS_SHORTCUT("animation_bezier_editor/select_all_keys", p_event)) {
for (int i = 0; i < edit_points.size(); ++i) {
- selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
+ _select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), i == 0);
}
queue_redraw();
@@ -1010,6 +1011,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
return;
} else if (ED_IS_SHORTCUT("animation_bezier_editor/deselect_all_keys", p_event)) {
selection.clear();
+ emit_signal(SNAME("clear_selection"));
queue_redraw();
accept_event();
@@ -1218,7 +1220,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
//insert new point
if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_or_control_pressed()) {
float h = (get_size().height / 2.0 - mb->get_position().y) * timeline_v_zoom + timeline_v_scroll;
- Array new_point = make_default_bezier_key(h);
+ Array new_point = animation->make_default_bezier_key(h);
real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
@@ -1235,7 +1237,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
int index = animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX);
ERR_FAIL_COND(index == -1);
_clear_selection();
- selection.insert(IntPair(selected_track, index));
+ _select_at_anim(animation, selected_track, animation->track_get_key_time(selected_track, index), true);
moving_selection_attempt = true;
moving_selection = false;
@@ -1277,13 +1279,15 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
Rect2 selection_rect(bs_from, bs_to - bs_from);
bool track_set = false;
+ int j = 0;
for (int i = 0; i < edit_points.size(); i++) {
if (edit_points[i].point_rect.intersects(selection_rect)) {
- selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
+ _select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), j == 0 && !box_selecting_add);
if (!track_set) {
track_set = true;
set_animation_and_track(animation, edit_points[i].track, read_only);
}
+ j++;
}
}
} else {
@@ -1418,21 +1422,22 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
// 7-reselect
-
+ int i = 0;
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
real_t oldpos = animation->track_get_key_time(E->get().first, E->get().second);
real_t newpos = oldpos + moving_selection_offset.x;
- undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos);
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos, i == 0);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos, i == 0);
+ i++;
}
undo_redo->commit_action();
} else if (select_single_attempt != IntPair(-1, -1)) {
selection.clear();
- selection.insert(select_single_attempt);
set_animation_and_track(animation, select_single_attempt.first, read_only);
+ _select_at_anim(animation, select_single_attempt.first, animation->track_get_key_time(select_single_attempt.first, select_single_attempt.second), true);
}
moving_selection = false;
@@ -1567,9 +1572,10 @@ bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p
if (selection.has(pair)) {
if (p_deselectable) {
selection.erase(pair);
+ emit_signal(SNAME("deselect_key"), edit_points[i].key, edit_points[i].track);
}
} else {
- selection.insert(pair);
+ _select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), false);
}
queue_redraw();
select_single_attempt = IntPair(-1, -1);
@@ -1595,7 +1601,7 @@ bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p
set_animation_and_track(animation, pair.first, read_only);
if (!selection.has(pair)) {
selection.clear();
- selection.insert(pair);
+ _select_at_anim(animation, edit_points[i].track, animation->track_get_key_time(edit_points[i].track, edit_points[i].key), true);
}
}
return true;
@@ -1637,19 +1643,6 @@ void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_ori
queue_redraw();
}
-Array AnimationBezierTrackEdit::make_default_bezier_key(float p_value) {
- Array new_point;
- new_point.resize(5);
-
- new_point[0] = p_value;
- new_point[1] = -0.25;
- new_point[2] = 0;
- new_point[3] = 0.25;
- new_point[4] = 0;
-
- return new_point;
-}
-
float AnimationBezierTrackEdit::get_bezier_key_value(Array p_bezier_key_array) {
return p_bezier_key_array[0];
}
@@ -1669,7 +1662,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
time += 0.001;
}
float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
- Array new_point = make_default_bezier_key(h);
+ Array new_point = animation->make_default_bezier_key(h);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Bezier Point"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
@@ -1763,12 +1756,16 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
// Reselect duplicated.
+ int i = 0;
for (const Pair<int, real_t> &E : new_selection_values) {
- undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);
+ i++;
}
+ i = 0;
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
real_t time = animation->track_get_key_time(E->get().first, E->get().second);
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, time);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, time, i == 0);
+ i++;
}
undo_redo->add_do_method(this, "queue_redraw");
@@ -1805,16 +1802,20 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {
undo_redo->create_action(TTR("Animation Cut Keys"), UndoRedo::MERGE_DISABLE, animation.ptr());
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+ int i = 0;
for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {
int track_idx = E->key().track;
int key_idx = E->key().key;
float time = E->value().pos;
undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track_idx, time);
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track_idx, time, animation->track_get_key_value(track_idx, key_idx), animation->track_get_key_transition(track_idx, key_idx));
- undo_redo->add_undo_method(this, "_select_at_anim", animation, track_idx, time);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, track_idx, time, i == 0);
+ i++;
}
+ i = 0;
for (RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo>::Element *E = keys.back(); E; E = E->prev()) {
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos);
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->value().pos, i == 0);
+ i++;
}
undo_redo->commit_action();
}
@@ -1863,7 +1864,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
Variant value = key.value;
if (key.track_type != Animation::TYPE_BEZIER) {
- value = make_default_bezier_key(key.value);
+ value = animation->make_default_bezier_key(key.value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, dst_time, value, key.transition);
@@ -1883,11 +1884,15 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
// Reselect pasted.
+ int i = 0;
for (const Pair<int, float> &E : new_selection_values) {
- undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
+ undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second, i == 0);
+ i++;
}
+ i = 0;
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
- undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, animation->track_get_key_time(E->get().first, E->get().second));
+ undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, animation->track_get_key_time(E->get().first, E->get().second), i == 0);
+ i++;
}
undo_redo->commit_action();
@@ -1928,6 +1933,7 @@ void AnimationBezierTrackEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_bezier_track_insert_key"), &AnimationBezierTrackEdit::_bezier_track_insert_key);
ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));
+ ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));
ADD_SIGNAL(MethodInfo("clear_selection"));
}
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index ab667421f3..3067c923b9 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -144,7 +144,7 @@ class AnimationBezierTrackEdit : public Control {
void _clear_selection();
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
- void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos);
+ void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos, bool p_single);
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false);
@@ -198,7 +198,6 @@ protected:
void _notification(int p_what);
public:
- static Array make_default_bezier_key(float p_value);
static float get_bezier_key_value(Array p_bezier_key_array);
virtual String get_tooltip(const Point2 &p_pos) const override;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index b80a8d77f4..9c7c275053 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -874,15 +874,15 @@ bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p
undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
}
Vector2 prev = animation->bezier_track_get_key_out_handle(track, key);
- undo_redo->add_do_method(this, "_bezier_track_set_key_out_handle", track, key, value);
- undo_redo->add_undo_method(this, "_bezier_track_set_key_out_handle", track, key, prev);
+ undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, value);
+ undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, key, prev);
update_obj = true;
} else if (name == "handle_mode") {
const Variant &value = p_value;
if (!setting) {
setting = true;
- undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS);
+ undo_redo->create_action(TTR("Animation Multi Change Keyframe Value"), UndoRedo::MERGE_ENDS, animation.ptr());
}
int prev_mode = animation->bezier_track_get_key_handle_mode(track, key);
Vector2 prev_in_handle = animation->bezier_track_get_key_in_handle(track, key);
@@ -4456,16 +4456,8 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
} break;
case Animation::TYPE_BEZIER: {
- Array array;
- array.resize(5);
- array[0] = p_id.value;
- array[1] = -0.25;
- array[2] = 0;
- array[3] = 0.25;
- array[4] = 0;
- value = array;
+ value = animation->make_default_bezier_key(p_id.value);
bezier_edit_icon->set_disabled(false);
-
} break;
default: {
// Other track types shouldn't use this code path.
@@ -5084,17 +5076,7 @@ void AnimationTrackEditor::_fetch_value_track_options(const NodePath &p_path, An
PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np);
animation->remove_track(animation->get_track_count() - 1); // Hack.
switch (h.type) {
- case Variant::FLOAT: {
-#ifdef DISABLE_DEPRECATED
- bool is_angle = h.type == Variant::FLOAT && h.hint_string.contains("radians_as_degrees");
-#else
- bool is_angle = h.type == Variant::FLOAT && h.hint_string.contains("radians");
-#endif // DISABLE_DEPRECATED
- if (is_angle) {
- *r_interpolation_type = Animation::INTERPOLATION_LINEAR_ANGLE;
- }
- [[fallthrough]];
- }
+ case Variant::FLOAT:
case Variant::VECTOR2:
case Variant::RECT2:
case Variant::VECTOR3:
@@ -5277,15 +5259,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
NodePath bp;
Variant value;
_find_hint_for_track(p_track, bp, &value);
- Array arr;
- arr.resize(5);
- arr[0] = value;
- arr[1] = -0.25;
- arr[2] = 0;
- arr[3] = 0.25;
- arr[4] = 0;
-
- id.value = arr;
+ id.value = animation->make_default_bezier_key(value);
} break;
case Animation::TYPE_AUDIO: {
Dictionary ak;
@@ -5829,7 +5803,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i
if (key_is_bezier && !track_is_bezier) {
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
} else if (!key_is_bezier && track_is_bezier) {
- value = AnimationBezierTrackEdit::make_default_bezier_key(value);
+ value = animation->make_default_bezier_key(value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, animation->track_get_key_transition(E->key().track, E->key().key));
@@ -5973,7 +5947,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p
if (key_is_bezier && !track_is_bezier) {
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
} else if (!key_is_bezier && track_is_bezier) {
- value = AnimationBezierTrackEdit::make_default_bezier_key(value);
+ value = animation->make_default_bezier_key(value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, key.transition);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 7e07e626b1..a9d37a6a99 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -347,18 +347,18 @@ void FindReplaceBar::_replace_all() {
needs_to_count_results = true;
}
-void FindReplaceBar::_get_search_from(int &r_line, int &r_col, bool p_is_searching_next) {
+void FindReplaceBar::_get_search_from(int &r_line, int &r_col, SearchMode p_search_mode) {
if (!text_editor->has_selection(0) || is_selection_only()) {
r_line = text_editor->get_caret_line(0);
r_col = text_editor->get_caret_column(0);
- if (!p_is_searching_next && r_line == result_line && r_col >= result_col && r_col <= result_col + get_search_text().length()) {
+ if (p_search_mode == SEARCH_PREV && r_line == result_line && r_col >= result_col && r_col <= result_col + get_search_text().length()) {
r_col = result_col;
}
return;
}
- if (p_is_searching_next) {
+ if (p_search_mode == SEARCH_NEXT) {
r_line = text_editor->get_selection_to_line();
r_col = text_editor->get_selection_to_column();
} else {
@@ -368,7 +368,11 @@ void FindReplaceBar::_get_search_from(int &r_line, int &r_col, bool p_is_searchi
}
void FindReplaceBar::_update_results_count() {
- if (!needs_to_count_results && (result_line != -1) && results_count_to_current > 0) {
+ int caret_line, caret_column;
+ _get_search_from(caret_line, caret_column, SEARCH_CURRENT);
+ bool match_selected = caret_line == result_line && caret_column == result_col && !is_selection_only() && text_editor->has_selection(0);
+
+ if (match_selected && !needs_to_count_results && result_line != -1 && results_count_to_current > 0) {
results_count_to_current += (flags & TextEdit::SEARCH_BACKWARDS) ? -1 : 1;
if (results_count_to_current > results_count) {
@@ -385,9 +389,10 @@ void FindReplaceBar::_update_results_count() {
return;
}
- needs_to_count_results = false;
+ needs_to_count_results = !match_selected;
results_count = 0;
+ results_count_to_current = 0;
for (int i = 0; i < text_editor->get_line_count(); i++) {
String line_text = text_editor->get_line(i);
@@ -417,18 +422,26 @@ void FindReplaceBar::_update_results_count() {
results_count++;
- if (i == result_line) {
- if (col_pos == result_col) {
- results_count_to_current = results_count;
- } else if (col_pos < result_col && col_pos + searched.length() > result_col) {
- col_pos = result_col;
- results_count_to_current = results_count;
- }
+ if (i <= result_line && col_pos <= result_col) {
+ results_count_to_current = results_count;
+ }
+ if (i == result_line && col_pos < result_col && col_pos + searched.length() > result_col) {
+ // Searching forwards and backwards with repeating text can lead to different matches.
+ col_pos = result_col;
}
-
col_pos += searched.length();
}
}
+ if (!match_selected) {
+ // Current result should refer to the match before the caret, if the caret is not on a match.
+ if (caret_line != result_line || caret_column != result_col) {
+ results_count_to_current -= 1;
+ }
+ if (results_count_to_current == 0 && (caret_line > result_line || (caret_line == result_line && caret_column > result_col))) {
+ // Caret is after all matches.
+ results_count_to_current = results_count;
+ }
+ }
}
void FindReplaceBar::_update_matches_display() {
@@ -457,7 +470,7 @@ bool FindReplaceBar::search_current() {
_update_flags(false);
int line, col;
- _get_search_from(line, col);
+ _get_search_from(line, col, SEARCH_CURRENT);
return _search(flags, line, col);
}
@@ -473,10 +486,14 @@ bool FindReplaceBar::search_prev() {
String text = get_search_text();
+ if ((flags & TextEdit::SEARCH_BACKWARDS) == 0) {
+ needs_to_count_results = true;
+ }
+
_update_flags(true);
int line, col;
- _get_search_from(line, col);
+ _get_search_from(line, col, SEARCH_PREV);
col -= text.length();
if (col < 0) {
@@ -499,10 +516,14 @@ bool FindReplaceBar::search_next() {
popup_search(true);
}
+ if (flags & TextEdit::SEARCH_BACKWARDS) {
+ needs_to_count_results = true;
+ }
+
_update_flags(false);
int line, col;
- _get_search_from(line, col, true);
+ _get_search_from(line, col, SEARCH_NEXT);
return _search(flags, line, col);
}
@@ -653,6 +674,7 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) {
}
if (base_text_editor) {
+ text_editor->set_search_text(String());
base_text_editor->remove_find_replace_bar();
base_text_editor = nullptr;
text_editor->disconnect(SceneStringName(text_changed), callable_mp(this, &FindReplaceBar::_editor_text_changed));
@@ -670,8 +692,7 @@ void FindReplaceBar::set_text_edit(CodeTextEditor *p_text_editor) {
text_editor = base_text_editor->get_text_editor();
text_editor->connect(SceneStringName(text_changed), callable_mp(this, &FindReplaceBar::_editor_text_changed));
- _update_results_count();
- _update_matches_display();
+ _editor_text_changed();
}
void FindReplaceBar::_bind_methods() {
diff --git a/editor/code_editor.h b/editor/code_editor.h
index af33a3fac8..28f6944b66 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -64,6 +64,12 @@ class CodeTextEditor;
class FindReplaceBar : public HBoxContainer {
GDCLASS(FindReplaceBar, HBoxContainer);
+ enum SearchMode {
+ SEARCH_CURRENT,
+ SEARCH_NEXT,
+ SEARCH_PREV,
+ };
+
LineEdit *search_text = nullptr;
Label *matches_label = nullptr;
Button *find_prev = nullptr;
@@ -94,7 +100,7 @@ class FindReplaceBar : public HBoxContainer {
bool replace_all_mode = false;
bool preserve_cursor = false;
- void _get_search_from(int &r_line, int &r_col, bool p_is_searching_next = false);
+ void _get_search_from(int &r_line, int &r_col, SearchMode p_search_mode);
void _update_results_count();
void _update_matches_display();
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index bfc4d91af7..eb0ab1174b 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -1377,6 +1377,8 @@ void ConnectionsDock::_notification(int p_what) {
slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), get_editor_theme_icon(SNAME("Unlinked")));
tree->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
+
+ update_tree();
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 545404ec8e..b62351256e 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -55,11 +55,6 @@ void EditorAutoloadSettings::_notification(int p_what) {
file_dialog->add_filter("*." + E);
}
- for (const AutoloadInfo &info : autoload_cache) {
- if (info.node && info.in_editor) {
- callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED);
- }
- }
browse_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
} break;
@@ -419,6 +414,8 @@ Node *EditorAutoloadSettings::_create_autoload(const String &p_path) {
Ref<Script> scr = res;
if (scr.is_valid()) {
+ ERR_FAIL_COND_V_MSG(!scr->is_valid(), nullptr, vformat("Failed to create an autoload, script '%s' is not compiling.", p_path));
+
StringName ibt = scr->get_instance_base_type();
bool valid_type = ClassDB::is_parent_class(ibt, "Node");
ERR_FAIL_COND_V_MSG(!valid_type, nullptr, vformat("Failed to create an autoload, script '%s' does not inherit from 'Node'.", p_path));
@@ -436,6 +433,35 @@ Node *EditorAutoloadSettings::_create_autoload(const String &p_path) {
return n;
}
+void EditorAutoloadSettings::init_autoloads() {
+ for (AutoloadInfo &info : autoload_cache) {
+ info.node = _create_autoload(info.path);
+
+ if (info.node) {
+ Ref<Script> scr = info.node->get_script();
+ info.in_editor = scr.is_valid() && scr->is_tool();
+ info.node->set_name(info.name);
+ }
+
+ if (info.is_singleton) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptServer::get_language(i)->add_named_global_constant(info.name, info.node);
+ }
+ }
+
+ if (!info.is_singleton && !info.in_editor && info.node != nullptr) {
+ memdelete(info.node);
+ info.node = nullptr;
+ }
+ }
+
+ for (const AutoloadInfo &info : autoload_cache) {
+ if (info.node && info.in_editor) {
+ callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED);
+ }
+ }
+}
+
void EditorAutoloadSettings::update_autoload() {
if (updating_autoload) {
return;
@@ -857,27 +883,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
autoload_cache.push_back(info);
}
- for (AutoloadInfo &info : autoload_cache) {
- info.node = _create_autoload(info.path);
-
- if (info.node) {
- Ref<Script> scr = info.node->get_script();
- info.in_editor = scr.is_valid() && scr->is_tool();
- info.node->set_name(info.name);
- }
-
- if (info.is_singleton) {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptServer::get_language(i)->add_named_global_constant(info.name, info.node);
- }
- }
-
- if (!info.is_singleton && !info.in_editor && info.node != nullptr) {
- memdelete(info.node);
- info.node = nullptr;
- }
- }
-
HBoxContainer *hbc = memnew(HBoxContainer);
add_child(hbc);
diff --git a/editor/editor_autoload_settings.h b/editor/editor_autoload_settings.h
index e4ac62e700..2ab969eb59 100644
--- a/editor/editor_autoload_settings.h
+++ b/editor/editor_autoload_settings.h
@@ -104,6 +104,7 @@ protected:
static void _bind_methods();
public:
+ void init_autoloads();
void update_autoload();
bool autoload_add(const String &p_name, const String &p_path);
void autoload_remove(const String &p_name);
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index d84ccb0c03..65ae25e046 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
+#include "editor/project_settings_editor.h"
#include "scene/resources/packed_scene.h"
EditorFileSystem *EditorFileSystem::singleton = nullptr;
@@ -206,17 +207,68 @@ EditorFileSystemDirectory::EditorFileSystemDirectory() {
}
EditorFileSystemDirectory::~EditorFileSystemDirectory() {
- for (int i = 0; i < files.size(); i++) {
- memdelete(files[i]);
+ for (EditorFileSystemDirectory::FileInfo *fi : files) {
+ memdelete(fi);
}
- for (int i = 0; i < subdirs.size(); i++) {
- memdelete(subdirs[i]);
+ for (EditorFileSystemDirectory *dir : subdirs) {
+ memdelete(dir);
+ }
+}
+
+EditorFileSystem::ScannedDirectory::~ScannedDirectory() {
+ for (ScannedDirectory *dir : subdirs) {
+ memdelete(dir);
+ }
+}
+
+void EditorFileSystem::_first_scan_filesystem() {
+ Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ first_scan_root_dir = memnew(ScannedDirectory);
+ first_scan_root_dir->full_path = "res://";
+ HashSet<String> existing_class_names;
+
+ 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.
+ _first_scan_process_scripts(first_scan_root_dir, existing_class_names);
+
+ // Removing invalid global class to prevent having invalid paths in ScriptServer.
+ _remove_invalid_global_class_names(existing_class_names);
+
+ // 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.
+ ProjectSettingsEditor::get_singleton()->init_autoloads();
+ EditorNode::get_singleton()->init_plugins();
+}
+
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) {
+ for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
+ _first_scan_process_scripts(scan_sub_dir, p_existing_class_names);
+ }
+
+ for (const String &scan_file : p_scan_dir->files) {
+ String path = p_scan_dir->full_path.path_join(scan_file);
+ String type = ResourceLoader::get_resource_type(path);
+
+ if (ClassDB::is_parent_class(type, SNAME("Script"))) {
+ String script_class_extends;
+ String script_class_icon_path;
+ String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path);
+ _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path);
+
+ if (!script_class_name.is_empty()) {
+ p_existing_class_names.insert(script_class_name);
+ }
+ }
}
}
void EditorFileSystem::_scan_filesystem() {
- ERR_FAIL_COND(!scanning || new_filesystem);
+ // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem.
+ ERR_FAIL_COND(!scanning || new_filesystem || (first_scan && !first_scan_root_dir));
//read .fscache
String cpath;
@@ -274,8 +326,8 @@ void EditorFileSystem::_scan_filesystem() {
FileCache fc;
fc.type = split[1];
if (fc.type.contains("/")) {
- fc.type = fc.type.get_slice("/", 0);
- fc.resource_script_class = fc.type.get_slice("/", 1);
+ fc.type = split[1].get_slice("/", 0);
+ fc.resource_script_class = split[1].get_slice("/", 1);
}
fc.uid = split[2].to_int();
fc.modification_time = split[3].to_int();
@@ -318,23 +370,33 @@ void EditorFileSystem::_scan_filesystem() {
}
EditorProgressBG scan_progress("efs", "ScanFS", 1000);
-
ScanProgress sp;
- sp.low = 0;
- sp.hi = 1;
+ sp.hi = nb_files_total;
sp.progress = &scan_progress;
new_filesystem = memnew(EditorFileSystemDirectory);
new_filesystem->parent = nullptr;
- Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- d->change_dir("res://");
- _scan_new_dir(new_filesystem, d, sp);
- dep_update_list.clear();
+ ScannedDirectory *sd;
+ // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem.
+ if (first_scan) {
+ sd = first_scan_root_dir;
+ } else {
+ Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ sd = memnew(ScannedDirectory);
+ sd->full_path = "res://";
+ nb_files_total = _scan_new_dir(sd, d);
+ }
+
+ _process_file_system(sd, new_filesystem, sp);
+ dep_update_list.clear();
file_cache.clear(); //clear caches, no longer needed
- if (!first_scan) {
+ if (first_scan) {
+ memdelete(first_scan_root_dir);
+ first_scan_root_dir = nullptr;
+ } else {
//on the first scan this is done from the main thread after re-importing
_save_filesystem_cache();
}
@@ -567,6 +629,10 @@ bool EditorFileSystem::_scan_import_support(const Vector<String> &reimports) {
bool EditorFileSystem::_update_scan_actions() {
sources_changed.clear();
+ // We need to update the script global class names before the reimports to be sure that
+ // all the importer classes that depends on class names will work.
+ _update_script_classes();
+
bool fs_changed = false;
Vector<String> reimports;
@@ -615,7 +681,7 @@ bool EditorFileSystem::_update_scan_actions() {
fs_changed = true;
if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) {
- _queue_update_script_class(ia.dir->get_file_path(idx));
+ _queue_update_script_class(ia.dir->get_file_path(idx), ia.new_file->type, ia.new_file->script_class_name, ia.new_file->script_class_extends, ia.new_file->script_class_icon_path);
}
if (ia.new_file->type == SNAME("PackedScene")) {
_queue_update_scene_groups(ia.dir->get_file_path(idx));
@@ -626,8 +692,9 @@ bool EditorFileSystem::_update_scan_actions() {
int idx = ia.dir->find_file_index(ia.file);
ERR_CONTINUE(idx == -1);
+ String script_class_name = ia.dir->files[idx]->script_class_name;
if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
- _queue_update_script_class(ia.dir->get_file_path(idx));
+ _queue_update_script_class(ia.dir->get_file_path(idx), "", "", "", "");
}
if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(ia.dir->get_file_path(idx));
@@ -637,6 +704,15 @@ bool EditorFileSystem::_update_scan_actions() {
memdelete(ia.dir->files[idx]);
ia.dir->files.remove_at(idx);
+ // Restore another script with the same global class name if it exists.
+ if (!script_class_name.is_empty()) {
+ EditorFileSystemDirectory::FileInfo *old_fi = nullptr;
+ String old_file = _get_file_by_class_name(filesystem, script_class_name, old_fi);
+ if (!old_file.is_empty() && old_fi) {
+ _queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path);
+ }
+ }
+
fs_changed = true;
} break;
@@ -667,10 +743,11 @@ bool EditorFileSystem::_update_scan_actions() {
ERR_CONTINUE(idx == -1);
String full_path = ia.dir->get_file_path(idx);
- if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
- _queue_update_script_class(full_path);
+ const EditorFileSystemDirectory::FileInfo *fi = ia.dir->files[idx];
+ if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
+ _queue_update_script_class(full_path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
}
- if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
+ if (fi->type == SNAME("PackedScene")) {
_queue_update_scene_groups(full_path);
}
@@ -711,6 +788,10 @@ bool EditorFileSystem::_update_scan_actions() {
_save_filesystem_cache();
}
+ // Moving the processing of pending updates before the resources_reload event to be sure all global class names
+ // are updated. Script.cpp listens on resources_reload and reloads updated scripts.
+ _process_update_pending();
+
if (reloads.size()) {
emit_signal(SNAME("resources_reload"), reloads);
}
@@ -728,6 +809,14 @@ void EditorFileSystem::scan() {
return;
}
+ // The first scan must be on the main thread because, after the first scan and update
+ // of global class names, we load the plugins and autoloads. These need to
+ // be added on the main thread because they are nodes, and we need to wait for them
+ // to be loaded to continue the scan and reimportations.
+ if (first_scan) {
+ _first_scan_filesystem();
+ }
+
_update_extensions();
if (!use_threads) {
@@ -741,14 +830,14 @@ void EditorFileSystem::scan() {
filesystem = new_filesystem;
new_filesystem = nullptr;
_update_scan_actions();
- scanning = false;
- _update_pending_script_classes();
- _update_pending_scene_groups();
// Update all icons so they are loaded for the FileSystemDock.
_update_files_icon_path();
+ scanning = false;
+ // 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;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
- first_scan = false;
} else {
ERR_FAIL_COND(thread.is_started());
set_process(true);
@@ -762,28 +851,19 @@ void EditorFileSystem::scan() {
}
}
-void EditorFileSystem::ScanProgress::update(int p_current, int p_total) const {
- float ratio = low + ((hi - low) / p_total) * p_current;
- progress->step(ratio * 1000);
+void EditorFileSystem::ScanProgress::increment() {
+ current++;
+ float ratio = current / MAX(hi, 1.0f);
+ progress->step(ratio * 1000.0f);
EditorFileSystem::singleton->scan_total = ratio;
}
-EditorFileSystem::ScanProgress EditorFileSystem::ScanProgress::get_sub(int p_current, int p_total) const {
- ScanProgress sp = *this;
- float slice = (sp.hi - sp.low) / p_total;
- sp.low += slice * p_current;
- sp.hi = slice;
- return sp;
-}
-
-void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress) {
+int EditorFileSystem::_scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da) {
List<String> dirs;
List<String> files;
String cd = da->get_current_dir();
- p_dir->modified_time = FileAccess::get_modified_time(cd);
-
da->list_dir_begin();
while (true) {
String f = da->get_next();
@@ -816,55 +896,59 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
dirs.sort_custom<FileNoCaseComparator>();
files.sort_custom<FileNoCaseComparator>();
- int total = dirs.size() + files.size();
- int idx = 0;
+ int nb_files_total_scan = 0;
- for (List<String>::Element *E = dirs.front(); E; E = E->next(), idx++) {
+ for (List<String>::Element *E = dirs.front(); E; E = E->next()) {
if (da->change_dir(E->get()) == OK) {
String d = da->get_current_dir();
if (d == cd || !d.begins_with(cd)) {
da->change_dir(cd); //avoid recursion
} else {
- EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
+ ScannedDirectory *sd = memnew(ScannedDirectory);
+ sd->name = E->get();
+ sd->full_path = p_dir->full_path.path_join(sd->name);
- efd->parent = p_dir;
- efd->name = E->get();
+ nb_files_total_scan += _scan_new_dir(sd, da);
- _scan_new_dir(efd, da, p_progress.get_sub(idx, total));
-
- int idx2 = 0;
- for (int i = 0; i < p_dir->subdirs.size(); i++) {
- if (efd->name.filenocasecmp_to(p_dir->subdirs[i]->name) < 0) {
- break;
- }
- idx2++;
- }
- if (idx2 == p_dir->subdirs.size()) {
- p_dir->subdirs.push_back(efd);
- } else {
- p_dir->subdirs.insert(idx2, efd);
- }
+ p_dir->subdirs.push_back(sd);
da->change_dir("..");
}
} else {
ERR_PRINT("Cannot go into subdir '" + E->get() + "'.");
}
+ }
+
+ p_dir->files = files;
+ nb_files_total_scan += files.size();
+
+ return nb_files_total_scan;
+}
+
+void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) {
+ p_dir->modified_time = FileAccess::get_modified_time(p_scan_dir->full_path);
- p_progress.update(idx, total);
+ for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
+ EditorFileSystemDirectory *sub_dir = memnew(EditorFileSystemDirectory);
+ sub_dir->parent = p_dir;
+ sub_dir->name = scan_sub_dir->name;
+ p_dir->subdirs.push_back(sub_dir);
+ _process_file_system(scan_sub_dir, sub_dir, p_progress);
}
- for (List<String>::Element *E = files.front(); E; E = E->next(), idx++) {
- String ext = E->get().get_extension().to_lower();
+ for (const String &scan_file : p_scan_dir->files) {
+ String ext = scan_file.get_extension().to_lower();
if (!valid_extensions.has(ext)) {
+ p_progress.increment();
continue; //invalid
}
- EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
- fi->file = E->get();
+ String path = p_scan_dir->full_path.path_join(scan_file);
- String path = cd.path_join(fi->file);
+ EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo);
+ fi->file = scan_file;
+ p_dir->files.push_back(fi);
FileCache *fc = file_cache.getptr(path);
uint64_t mt = FileAccess::get_modified_time(path);
@@ -894,7 +978,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
ItemAction ia;
ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
ia.dir = p_dir;
- ia.file = E->get();
+ ia.file = fi->file;
scan_actions.push_back(ia);
}
@@ -923,7 +1007,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
ItemAction ia;
ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
ia.dir = p_dir;
- ia.file = E->get();
+ ia.file = fi->file;
scan_actions.push_back(ia);
}
} else {
@@ -939,6 +1023,21 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
fi->script_class_name = fc->script_class_name;
fi->script_class_extends = fc->script_class_extends;
fi->script_class_icon_path = fc->script_class_icon_path;
+
+ if (first_scan && ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
+ bool update_script = false;
+ String old_class_name = fi->script_class_name;
+ fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
+ if (old_class_name != fi->script_class_name) {
+ update_script = true;
+ } else if (!fi->script_class_name.is_empty() && (!ScriptServer::is_global_class(fi->script_class_name) || ScriptServer::get_global_class_path(fi->script_class_name) != path)) {
+ // This script has a class name but is not in the global class names or the path of the class has changed.
+ update_script = true;
+ }
+ if (update_script) {
+ _queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+ }
+ }
} else {
//new or modified time
fi->type = ResourceLoader::get_resource_type(path);
@@ -956,7 +1055,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
// Files in dep_update_list are forced for rescan to update dependencies. They don't need other updates.
if (!dep_update_list.has(path)) {
if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
- _queue_update_script_class(path);
+ _queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
}
if (fi->type == SNAME("PackedScene")) {
_queue_update_scene_groups(path);
@@ -973,16 +1072,16 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
}
}
- p_dir->files.push_back(fi);
- p_progress.update(idx, total);
+ p_progress.increment();
}
}
-void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress) {
+void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) {
uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path());
bool updated_dir = false;
String cd = p_dir->get_path();
+ int diff_nb_files = 0;
if (current_mtime != p_dir->modified_time || using_fat32_or_exfat) {
updated_dir = true;
@@ -999,6 +1098,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
p_dir->get_subdir(i)->verified = false;
}
+ diff_nb_files -= p_dir->files.size();
+
//then scan files and directories and check what's different
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
@@ -1024,17 +1125,25 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
int idx = p_dir->find_dir_index(f);
if (idx == -1) {
- if (_should_skip_directory(cd.path_join(f))) {
+ String dir_path = cd.path_join(f);
+ if (_should_skip_directory(dir_path)) {
continue;
}
- EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
+ ScannedDirectory sd;
+ sd.name = f;
+ sd.full_path = dir_path;
+ EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
efd->parent = p_dir;
efd->name = f;
+
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- d->change_dir(cd.path_join(f));
- _scan_new_dir(efd, d, p_progress.get_sub(1, 1));
+ d->change_dir(dir_path);
+ int nb_files_dir = _scan_new_dir(&sd, d);
+ p_progress.hi += nb_files_dir;
+ diff_nb_files += nb_files_dir;
+ _process_file_system(&sd, efd, p_progress);
ItemAction ia;
ia.action = ItemAction::ACTION_DIR_ADD;
@@ -1088,7 +1197,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
ia.file = f;
scan_actions.push_back(ia);
}
-
+ diff_nb_files++;
} else {
p_dir->files[idx]->verified = true;
}
@@ -1106,6 +1215,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
ia.dir = p_dir;
ia.file = p_dir->files[i]->file;
scan_actions.push_back(ia);
+ diff_nb_files--;
continue;
}
@@ -1152,10 +1262,16 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
scan_actions.push_back(ia);
}
}
+
+ p_progress.increment();
}
for (int i = 0; i < p_dir->subdirs.size(); i++) {
if ((updated_dir && !p_dir->subdirs[i]->verified) || _should_skip_directory(p_dir->subdirs[i]->get_path())) {
+ // Add all the files of the folder to be sure _update_scan_actions process the removed files
+ // for global class names.
+ diff_nb_files += _insert_actions_delete_files_directory(p_dir->subdirs[i]);
+
//this directory was removed or ignored, add action to remove it
ItemAction ia;
ia.action = ItemAction::ACTION_DIR_REMOVE;
@@ -1165,6 +1281,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
}
_scan_fs_changes(p_dir->get_subdir(i), p_progress);
}
+
+ nb_files_total = MAX(nb_files_total + diff_nb_files, 0);
}
void EditorFileSystem::_delete_internal_files(const String &p_file) {
@@ -1179,19 +1297,64 @@ void EditorFileSystem::_delete_internal_files(const String &p_file) {
}
}
+int EditorFileSystem::_insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir) {
+ int nb_files = 0;
+ for (EditorFileSystemDirectory::FileInfo *fi : p_dir->files) {
+ ItemAction ia;
+ ia.action = ItemAction::ACTION_FILE_REMOVE;
+ ia.dir = p_dir;
+ ia.file = fi->file;
+ scan_actions.push_back(ia);
+ nb_files++;
+ }
+
+ for (EditorFileSystemDirectory *sub_dir : p_dir->subdirs) {
+ nb_files += _insert_actions_delete_files_directory(sub_dir);
+ }
+
+ return nb_files;
+}
+
void EditorFileSystem::_thread_func_sources(void *_userdata) {
EditorFileSystem *efs = (EditorFileSystem *)_userdata;
if (efs->filesystem) {
EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
ScanProgress sp;
sp.progress = &pr;
- sp.hi = 1;
- sp.low = 0;
+ sp.hi = efs->nb_files_total;
efs->_scan_fs_changes(efs->filesystem, sp);
}
efs->scanning_changes_done.set();
}
+void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) {
+ List<StringName> global_classes;
+ ScriptServer::get_global_class_list(&global_classes);
+ for (const StringName &class_name : global_classes) {
+ if (!p_existing_class_names.has(class_name)) {
+ ScriptServer::remove_global_class(class_name);
+ }
+ }
+}
+
+String EditorFileSystem::_get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info) {
+ for (EditorFileSystemDirectory::FileInfo *fi : p_dir->files) {
+ if (fi->script_class_name == p_class_name) {
+ r_file_info = fi;
+ return p_dir->get_path().path_join(fi->file);
+ }
+ }
+
+ for (EditorFileSystemDirectory *sub_dir : p_dir->subdirs) {
+ String file = _get_file_by_class_name(sub_dir, p_class_name, r_file_info);
+ if (!file.is_empty()) {
+ return file;
+ }
+ }
+ r_file_info = nullptr;
+ return "";
+}
+
void EditorFileSystem::scan_changes() {
if (first_scan || // Prevent a premature changes scan from inhibiting the first full scan
scanning || scanning_changes || thread.is_started()) {
@@ -1210,14 +1373,10 @@ void EditorFileSystem::scan_changes() {
EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
ScanProgress sp;
sp.progress = &pr;
- sp.hi = 1;
- sp.low = 0;
+ sp.hi = nb_files_total;
scan_total = 0;
_scan_fs_changes(filesystem, sp);
- bool changed = _update_scan_actions();
- _update_pending_script_classes();
- _update_pending_scene_groups();
- if (changed) {
+ if (_update_scan_actions()) {
emit_signal(SNAME("filesystem_changed"));
}
}
@@ -1282,13 +1441,13 @@ void EditorFileSystem::_notification(int p_what) {
thread_sources.wait_to_finish();
}
bool changed = _update_scan_actions();
- _update_pending_script_classes();
- _update_pending_scene_groups();
+ // 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;
if (changed) {
emit_signal(SNAME("filesystem_changed"));
}
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
- first_scan = false;
scanning_changes = false; // Changed to false here to prevent recursive triggering of scan thread.
done_importing = true;
}
@@ -1302,13 +1461,13 @@ void EditorFileSystem::_notification(int p_what) {
new_filesystem = nullptr;
thread.wait_to_finish();
_update_scan_actions();
- _update_pending_script_classes();
- _update_pending_scene_groups();
// Update all icons so they are loaded for the FileSystemDock.
_update_files_icon_path();
+ // 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;
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
- first_scan = false;
}
if (done_importing && scan_changes_pending) {
@@ -1323,7 +1482,7 @@ void EditorFileSystem::_notification(int p_what) {
}
bool EditorFileSystem::is_scanning() const {
- return scanning || scanning_changes;
+ return scanning || scanning_changes || first_scan;
}
float EditorFileSystem::get_scanning_progress() const {
@@ -1624,14 +1783,41 @@ void EditorFileSystem::_update_files_icon_path(EditorFileSystemDirectory *edp) {
}
void EditorFileSystem::_update_script_classes() {
+ if (update_script_paths.is_empty()) {
+ return;
+ }
+
update_script_mutex.lock();
- for (const String &path : update_script_paths) {
- EditorFileSystem::get_singleton()->register_global_class_script(path, path);
+ 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);
}
- // Parse documentation second, as it requires the class names to be correct and registered
- for (const String &path : update_script_paths) {
+ update_script_paths.clear();
+ update_script_mutex.unlock();
+
+ ScriptServer::save_global_classes();
+ EditorNode::get_editor_data().script_class_save_icon_paths();
+
+ emit_signal("script_classes_updated");
+
+ // Rescan custom loaders and savers.
+ // Doing the following here because the `filesystem_changed` signal fires multiple times and isn't always followed by script classes update.
+ // So I thought it's better to do this when script classes really get updated
+ ResourceLoader::remove_custom_loaders();
+ ResourceLoader::add_custom_loaders();
+ ResourceSaver::remove_custom_savers();
+ ResourceSaver::add_custom_savers();
+}
+
+void EditorFileSystem::_update_script_documentation() {
+ if (update_script_paths_documentation.is_empty()) {
+ return;
+ }
+
+ update_script_mutex.lock();
+
+ for (const String &path : update_script_paths_documentation) {
int index = -1;
EditorFileSystemDirectory *efd = find_file(path, &index);
@@ -1655,40 +1841,38 @@ void EditorFileSystem::_update_script_classes() {
}
}
- update_script_paths.clear();
+ update_script_paths_documentation.clear();
update_script_mutex.unlock();
-
- ScriptServer::save_global_classes();
- EditorNode::get_editor_data().script_class_save_icon_paths();
- emit_signal("script_classes_updated");
-
- // Rescan custom loaders and savers.
- // Doing the following here because the `filesystem_changed` signal fires multiple times and isn't always followed by script classes update.
- // So I thought it's better to do this when script classes really get updated
- ResourceLoader::remove_custom_loaders();
- ResourceLoader::add_custom_loaders();
- ResourceSaver::remove_custom_savers();
- ResourceSaver::add_custom_savers();
}
-void EditorFileSystem::_update_pending_script_classes() {
- if (!update_script_paths.is_empty()) {
- _update_script_classes();
- } else {
- // In case the class cache file was removed somehow, regenerate it.
- if (!FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) {
- ScriptServer::save_global_classes();
- }
- }
+void EditorFileSystem::_process_update_pending() {
+ _update_script_classes();
+ // Parse documentation second, as it requires the class names to be loaded
+ // because _update_script_documentation loads the scripts completely.
+ _update_script_documentation();
+ _update_pending_scene_groups();
}
-void EditorFileSystem::_queue_update_script_class(const String &p_path) {
+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();
- update_script_paths.insert(p_path);
+
+ ScriptInfo si;
+ si.type = p_type;
+ si.script_class_name = p_script_class_name;
+ si.script_class_extends = p_script_class_extends;
+ si.script_class_icon_path = p_script_class_icon_path;
+ update_script_paths.insert(p_path, si);
+
+ update_script_paths_documentation.insert(p_path);
+
update_script_mutex.unlock();
}
void EditorFileSystem::_update_scene_groups() {
+ if (update_scene_paths.is_empty()) {
+ return;
+ }
+
EditorProgress *ep = nullptr;
if (update_scene_paths.size() > 1) {
ep = memnew(EditorProgress("update_scene_groups", TTR("Update Scene Groups"), update_scene_paths.size()));
@@ -1707,7 +1891,7 @@ void EditorFileSystem::_update_scene_groups() {
continue;
}
- const HashSet<StringName> scene_groups = _get_scene_groups(path);
+ 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);
}
@@ -1751,12 +1935,6 @@ void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet
}
}
-HashSet<StringName> EditorFileSystem::_get_scene_groups(const String &p_path) {
- Ref<PackedScene> packed_scene = ResourceLoader::load(p_path);
- ERR_FAIL_COND_V(packed_scene.is_null(), HashSet<StringName>());
- return packed_scene->get_state()->get_all_groups();
-}
-
void EditorFileSystem::update_file(const String &p_file) {
ERR_FAIL_COND(p_file.is_empty());
update_files({ p_file });
@@ -1787,7 +1965,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
}
}
if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
- _queue_update_script_class(file);
+ _queue_update_script_class(file, fs->files[cpos]->type, "", "", "");
if (!fs->files[cpos]->script_class_icon_path.is_empty()) {
update_files_icon_cache = true;
}
@@ -1840,6 +2018,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
}
const String old_script_class_icon_path = fs->files[cpos]->script_class_icon_path;
+ const String old_class_name = fs->files[cpos]->script_class_name;
fs->files[cpos]->type = type;
fs->files[cpos]->resource_script_class = script_class;
fs->files[cpos]->uid = uid;
@@ -1862,23 +2041,31 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
EditorResourcePreview::get_singleton()->check_for_invalidation(file);
if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
- _queue_update_script_class(file);
+ _queue_update_script_class(file, fs->files[cpos]->type, fs->files[cpos]->script_class_name, fs->files[cpos]->script_class_extends, fs->files[cpos]->script_class_icon_path);
}
if (fs->files[cpos]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(file);
}
+
if (fs->files[cpos]->type == SNAME("Resource")) {
files_to_update_icon_path.push_back(fs->files[cpos]);
} else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) {
update_files_icon_cache = true;
}
+
+ // Restore another script as the global class name if multiple scripts had the same old class name.
+ if (!old_class_name.is_empty() && fs->files[cpos]->script_class_name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) {
+ EditorFileSystemDirectory::FileInfo *old_fi = nullptr;
+ String old_file = _get_file_by_class_name(filesystem, old_class_name, old_fi);
+ if (!old_file.is_empty() && old_fi) {
+ _queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path);
+ }
+ }
updated = true;
}
}
if (updated) {
- _update_pending_script_classes();
- _update_pending_scene_groups();
if (update_files_icon_cache) {
_update_files_icon_path();
} else {
@@ -1886,7 +2073,10 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
_update_file_icon_path(fi);
}
}
- call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
+ if (!is_scanning()) {
+ _process_update_pending();
+ call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
+ }
}
}
@@ -1894,31 +2084,37 @@ HashSet<String> EditorFileSystem::get_valid_extensions() const {
return valid_extensions;
}
-void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) {
+void EditorFileSystem::_register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
ScriptServer::remove_global_class_by_path(p_search_path); // First remove, just in case it changed
- int index = -1;
- EditorFileSystemDirectory *efd = find_file(p_search_path, &index);
-
- if (!efd || index < 0) {
- // The file was removed
+ if (p_script_class_name.is_empty()) {
return;
}
- if (!efd->files[index]->script_class_name.is_empty()) {
- String lang;
- for (int j = 0; j < ScriptServer::get_language_count(); j++) {
- if (ScriptServer::get_language(j)->handles_global_class_type(efd->files[index]->type)) {
- lang = ScriptServer::get_language(j)->get_name();
- }
- }
- if (lang.is_empty()) {
- return; // No lang found that can handle this global class
+ String lang;
+ for (int j = 0; j < ScriptServer::get_language_count(); j++) {
+ if (ScriptServer::get_language(j)->handles_global_class_type(p_type)) {
+ lang = ScriptServer::get_language(j)->get_name();
+ break;
}
+ }
+ if (lang.is_empty()) {
+ return; // No lang found that can handle this global class
+ }
- ScriptServer::add_global_class(efd->files[index]->script_class_name, efd->files[index]->script_class_extends, lang, p_target_path);
- EditorNode::get_editor_data().script_class_set_icon_path(efd->files[index]->script_class_name, efd->files[index]->script_class_icon_path);
- EditorNode::get_editor_data().script_class_set_name(p_target_path, efd->files[index]->script_class_name);
+ ScriptServer::add_global_class(p_script_class_name, p_script_class_extends, lang, p_target_path);
+ EditorNode::get_editor_data().script_class_set_icon_path(p_script_class_name, p_script_class_icon_path);
+ EditorNode::get_editor_data().script_class_set_name(p_target_path, p_script_class_name);
+}
+
+void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) {
+ int index_file;
+ EditorFileSystemDirectory *efsd = find_file(p_search_path, &index_file);
+ if (efsd) {
+ const EditorFileSystemDirectory::FileInfo *fi = efsd->files[index_file];
+ EditorFileSystem::get_singleton()->_register_global_class_script(p_search_path, p_target_path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path);
+ } else {
+ ScriptServer::remove_global_class_by_path(p_search_path);
}
}
@@ -2542,8 +2738,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
_save_filesystem_cache();
- _update_pending_script_classes();
- _update_pending_scene_groups();
+ _process_update_pending();
importing = false;
if (!is_scanning()) {
emit_signal(SNAME("filesystem_changed"));
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 88cd67296b..aee40ed23d 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -159,11 +159,21 @@ class EditorFileSystem : public Node {
EditorFileSystemDirectory::FileInfo *new_file = nullptr;
};
+ struct ScannedDirectory {
+ String name;
+ String full_path;
+ Vector<ScannedDirectory *> subdirs;
+ List<String> files;
+
+ ~ScannedDirectory();
+ };
+
bool use_threads = false;
Thread thread;
static void _thread_func(void *_userdata);
EditorFileSystemDirectory *new_filesystem = nullptr;
+ ScannedDirectory *first_scan_root_dir = nullptr;
bool scanning = false;
bool importing = false;
@@ -172,8 +182,11 @@ class EditorFileSystem : public Node {
float scan_total;
String filesystem_settings_version_for_import;
bool revalidate_import_files = false;
+ int nb_files_total = 0;
void _scan_filesystem();
+ void _first_scan_filesystem();
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names);
HashSet<String> late_update_files;
@@ -202,11 +215,10 @@ class EditorFileSystem : public Node {
HashSet<String> dep_update_list;
struct ScanProgress {
- float low = 0;
float hi = 0;
- mutable EditorProgressBG *progress = nullptr;
- void update(int p_current, int p_total) const;
- ScanProgress get_sub(int p_current, int p_total) const;
+ int current = 0;
+ EditorProgressBG *progress = nullptr;
+ void increment();
};
void _save_filesystem_cache();
@@ -214,15 +226,17 @@ class EditorFileSystem : public Node {
bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const;
- void _scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress);
+ void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
void _delete_internal_files(const String &p_file);
+ int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
HashSet<String> textfile_extensions;
HashSet<String> valid_extensions;
HashSet<String> import_extensions;
- void _scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress);
+ int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da);
+ void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress);
Thread thread_sources;
bool scanning_changes = false;
@@ -256,18 +270,26 @@ class EditorFileSystem : public Node {
}
};
+ struct ScriptInfo {
+ String type;
+ String script_class_name;
+ String script_class_extends;
+ String script_class_icon_path;
+ };
+
Mutex update_script_mutex;
- HashSet<String> update_script_paths;
- void _queue_update_script_class(const String &p_path);
+ HashMap<String, ScriptInfo> update_script_paths;
+ HashSet<String> update_script_paths_documentation;
+ void _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);
void _update_script_classes();
- void _update_pending_script_classes();
+ void _update_script_documentation();
+ void _process_update_pending();
Mutex update_scene_mutex;
HashSet<String> update_scene_paths;
void _queue_update_scene_groups(const String &p_path);
void _update_scene_groups();
void _update_pending_scene_groups();
- HashSet<StringName> _get_scene_groups(const String &p_path);
void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
@@ -300,6 +322,10 @@ class EditorFileSystem : public Node {
void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
+ void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names);
+ String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info);
+
+ void _register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path);
protected:
void _notification(int p_what);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index ae7066fea9..632b36c705 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -706,23 +706,7 @@ void EditorNode::_notification(int p_what) {
} break;
case NOTIFICATION_READY: {
- {
- started_timestamp = Time::get_singleton()->get_unix_time_from_system();
- _initializing_plugins = true;
- Vector<String> addons;
- if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
- addons = GLOBAL_GET("editor_plugins/enabled");
- }
-
- for (int i = 0; i < addons.size(); i++) {
- set_addon_plugin_enabled(addons[i], true);
- }
- _initializing_plugins = false;
-
- if (!pending_addons.is_empty()) {
- EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons));
- }
- }
+ started_timestamp = Time::get_singleton()->get_unix_time_from_system();
RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true);
RenderingServer::get_singleton()->viewport_set_environment_mode(get_viewport()->get_viewport_rid(), RenderingServer::VIEWPORT_ENVIRONMENT_DISABLED);
@@ -860,6 +844,23 @@ void EditorNode::_update_update_spinner() {
OS::get_singleton()->set_low_processor_usage_mode(!update_continuously);
}
+void EditorNode::init_plugins() {
+ _initializing_plugins = true;
+ Vector<String> addons;
+ if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) {
+ addons = GLOBAL_GET("editor_plugins/enabled");
+ }
+
+ for (const String &addon : addons) {
+ set_addon_plugin_enabled(addon, true);
+ }
+ _initializing_plugins = false;
+
+ if (!pending_addons.is_empty()) {
+ EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons), CONNECT_ONE_SHOT);
+ }
+}
+
void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) {
Ref<Script> scr = Object::cast_to<Script>(p_script);
if (scr.is_null()) {
@@ -954,6 +955,7 @@ void EditorNode::_fs_changed() {
// FIXME: Move this to a cleaner location, it's hacky to do this in _fs_changed.
String export_error;
Error err = OK;
+ // It's important to wait for the first scan to finish; otherwise, scripts or resources might not be imported.
if (!export_defer.preset.is_empty() && !EditorFileSystem::get_singleton()->is_scanning()) {
String preset_name = export_defer.preset;
// Ensures export_project does not loop infinitely, because notifications may
@@ -5259,6 +5261,14 @@ bool EditorNode::has_scenes_in_session() {
return !scenes.is_empty();
}
+void EditorNode::undo() {
+ trigger_menu_option(EDIT_UNDO, true);
+}
+
+void EditorNode::redo() {
+ trigger_menu_option(EDIT_REDO, true);
+}
+
bool EditorNode::ensure_main_scene(bool p_from_native) {
pick_main_scene->set_meta("from_native", p_from_native); // Whether from play button or native run.
String main_scene = GLOBAL_GET("application/run/main_scene");
@@ -6807,7 +6817,7 @@ EditorNode::EditorNode() {
distraction_free = memnew(Button);
distraction_free->set_theme_type_variation("FlatMenuButton");
ED_SHORTCUT_AND_COMMAND("editor/distraction_free_mode", TTR("Distraction Free Mode"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F11);
- ED_SHORTCUT_OVERRIDE("editor/distraction_free_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::D);
+ ED_SHORTCUT_OVERRIDE("editor/distraction_free_mode", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::D);
ED_SHORTCUT_AND_COMMAND("editor/toggle_last_opened_bottom_panel", TTR("Toggle Last Opened Bottom Panel"), KeyModifierMask::CMD_OR_CTRL | Key::J);
distraction_free->set_shortcut(ED_GET_SHORTCUT("editor/distraction_free_mode"));
distraction_free->set_tooltip_text(TTR("Toggle distraction-free mode."));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 49674dd1c1..7a26156ab8 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -683,6 +683,7 @@ protected:
public:
// Public for use with callable_mp.
+ void init_plugins();
void _on_plugin_ready(Object *p_script, const String &p_activate_name);
void editor_select(int p_which);
@@ -915,6 +916,9 @@ public:
bool has_scenes_in_session();
+ void undo();
+ void redo();
+
int execute_and_show_output(const String &p_title, const String &p_path, const List<String> &p_arguments, bool p_close_on_ok = true, bool p_close_on_errors = false, String *r_output = nullptr);
EditorNode();
diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp
index 0621e5f8d1..dfb87f43da 100644
--- a/editor/editor_quick_open.cpp
+++ b/editor/editor_quick_open.cpp
@@ -32,6 +32,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
+#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
Rect2i EditorQuickOpen::prev_rect = Rect2i();
@@ -119,10 +120,12 @@ void EditorQuickOpen::_update_search() {
sorter.sort(entries.ptrw(), entries.size());
}
+ const int class_icon_size = search_options->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
const int entry_limit = MIN(entries.size(), 300);
for (int i = 0; i < entry_limit; i++) {
TreeItem *ti = search_options->create_item(root);
ti->set_text(0, entries[i].path);
+ ti->set_icon_max_width(0, class_icon_size);
ti->set_icon(0, *icons.lookup_ptr(entries[i].path.get_extension()));
}
diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp
index 6fd6a7103f..a71d43ad51 100644
--- a/editor/editor_settings_dialog.cpp
+++ b/editor/editor_settings_dialog.cpp
@@ -168,28 +168,17 @@ void EditorSettingsDialog::_notification(int p_what) {
}
void EditorSettingsDialog::shortcut_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
bool handled = false;
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
- undo_redo->undo();
+ EditorNode::get_singleton()->undo();
handled = true;
}
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
- undo_redo->redo();
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
+ EditorNode::get_singleton()->redo();
handled = true;
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 29ca1279b2..d4bd97a393 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -546,7 +546,7 @@ void FileSystemDock::_notification(int p_what) {
case NOTIFICATION_PROCESS: {
if (EditorFileSystem::get_singleton()->is_scanning()) {
- scanning_progress->set_value(EditorFileSystem::get_singleton()->get_scanning_progress() * 100);
+ scanning_progress->set_value(EditorFileSystem::get_singleton()->get_scanning_progress() * 100.0f);
}
} break;
diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp
index 18ac4074da..6cfc035fc9 100644
--- a/editor/groups_editor.cpp
+++ b/editor/groups_editor.cpp
@@ -369,6 +369,7 @@ void GroupsEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
filter->set_right_icon(get_editor_theme_icon("Search"));
add->set_icon(get_editor_theme_icon("Add"));
+ _update_tree();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (groups_dirty && is_visible_in_tree()) {
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 55a7208169..2db3fb1f07 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -1959,7 +1959,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate/physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/body_type", PROPERTY_HINT_ENUM, "Static,Dynamic,Area"), 0));
- r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/shape_type", PROPERTY_HINT_ENUM, "Decompose Convex,Simple Convex,Trimesh,Box,Sphere,Cylinder,Capsule", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 2));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/shape_type", PROPERTY_HINT_ENUM, "Decompose Convex,Simple Convex,Trimesh,Box,Sphere,Cylinder,Capsule,Automatic", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 7));
r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "physics/physics_material_override", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"), Variant()));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), 1));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), 1));
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index cd6a725231..f9124ad289 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -212,6 +212,7 @@ class ResourceImporterScene : public ResourceImporter {
SHAPE_TYPE_SPHERE,
SHAPE_TYPE_CYLINDER,
SHAPE_TYPE_CAPSULE,
+ SHAPE_TYPE_AUTOMATIC,
};
static Error _check_resource_save_paths(const Dictionary &p_data);
@@ -324,11 +325,21 @@ public:
template <typename M>
Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale) {
ERR_FAIL_COND_V(p_mesh.is_null(), Vector<Ref<Shape3D>>());
- ShapeType generate_shape_type = SHAPE_TYPE_TRIMESH;
+
+ ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
if (p_options.has(SNAME("physics/shape_type"))) {
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
}
+ if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
+ BodyType body_type = BODY_TYPE_STATIC;
+ if (p_options.has(SNAME("physics/body_type"))) {
+ body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
+ }
+
+ generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
+ }
+
if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) {
Ref<MeshConvexDecompositionSettings> decomposition_settings = Ref<MeshConvexDecompositionSettings>();
decomposition_settings.instantiate();
@@ -482,11 +493,20 @@ template <typename M>
Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
Transform3D transform;
- ShapeType generate_shape_type = SHAPE_TYPE_TRIMESH;
+ ShapeType generate_shape_type = SHAPE_TYPE_AUTOMATIC;
if (p_options.has(SNAME("physics/shape_type"))) {
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
}
+ if (generate_shape_type == SHAPE_TYPE_AUTOMATIC) {
+ BodyType body_type = BODY_TYPE_STATIC;
+ if (p_options.has(SNAME("physics/body_type"))) {
+ body_type = (BodyType)p_options[SNAME("physics/body_type")].operator int();
+ }
+
+ generate_shape_type = body_type == BODY_TYPE_DYNAMIC ? SHAPE_TYPE_DECOMPOSE_CONVEX : SHAPE_TYPE_TRIMESH;
+ }
+
if (generate_shape_type == SHAPE_TYPE_BOX ||
generate_shape_type == SHAPE_TYPE_SPHERE ||
generate_shape_type == SHAPE_TYPE_CYLINDER ||
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index 97f628c4a4..f73c494b25 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -570,7 +570,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl
const Vector2 p = (vertex == edited_point) ? edited_point.pos : (points[i] + offset);
const Vector2 point = xform.xform(p);
- const Color overlay_modulate = vertex == active_point ? Color(0.5, 1, 2) : Color(1, 1, 1);
+ const Color overlay_modulate = vertex == active_point ? Color(0.4, 1, 1) : Color(1, 1, 1);
p_overlay->draw_texture(handle, point - handle->get_size() * 0.5, overlay_modulate);
if (vertex == hover_point) {
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp
index afe7ea83d8..b07db993ba 100644
--- a/editor/plugins/animation_library_editor.cpp
+++ b/editor/plugins/animation_library_editor.cpp
@@ -781,6 +781,27 @@ void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
emit_signal("update_editor", p_mixer);
}
+void AnimationLibraryEditor::shortcut_input(const Ref<InputEvent> &p_event) {
+ const Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed()) {
+ bool handled = false;
+
+ if (ED_IS_SHORTCUT("ui_undo", p_event)) {
+ EditorNode::get_singleton()->undo();
+ handled = true;
+ }
+
+ if (ED_IS_SHORTCUT("ui_redo", p_event)) {
+ EditorNode::get_singleton()->redo();
+ handled = true;
+ }
+
+ if (handled) {
+ set_input_as_handled();
+ }
+ }
+}
+
void AnimationLibraryEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
ADD_SIGNAL(MethodInfo("update_editor"));
@@ -788,6 +809,7 @@ void AnimationLibraryEditor::_bind_methods() {
AnimationLibraryEditor::AnimationLibraryEditor() {
set_title(TTR("Edit Animation Libraries"));
+ set_process_shortcut_input(true);
file_dialog = memnew(EditorFileDialog);
add_child(file_dialog);
diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h
index c8d9274f4f..beb34c6343 100644
--- a/editor/plugins/animation_library_editor.h
+++ b/editor/plugins/animation_library_editor.h
@@ -113,6 +113,7 @@ class AnimationLibraryEditor : public AcceptDialog {
protected:
void _notification(int p_what);
void _update_editor(Object *p_mixer);
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
static void _bind_methods();
public:
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index ee5d90b1f9..4e2940a8cb 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -780,19 +780,24 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po
return still_selected;
}
-List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) const {
+List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool p_retrieve_locked, bool p_remove_canvas_item_if_parent_in_selection, bool *r_has_locked_items) const {
List<CanvasItem *> selection;
for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) {
CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
- if (ci && ci->is_visible_in_tree() && ci->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(ci))) {
- CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
- if (se) {
- selection.push_back(ci);
+ if (ci) {
+ if (ci->is_visible_in_tree() && ci->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (p_retrieve_locked || !_is_node_locked(ci))) {
+ CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
+ if (se) {
+ selection.push_back(ci);
+ }
+ } else if (r_has_locked_items) {
+ // CanvasItem is selected, but can't be interacted with.
+ *r_has_locked_items = true;
}
}
}
- if (remove_canvas_item_if_parent_in_selection) {
+ if (p_remove_canvas_item_if_parent_in_selection) {
List<CanvasItem *> filtered_selection;
for (CanvasItem *E : selection) {
if (!selection.find(E->get_parent())) {
@@ -1454,7 +1459,8 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
if (drag_type == DRAG_NONE) {
if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
if ((b->is_command_or_control_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {
- List<CanvasItem *> selection = _get_edited_canvas_items();
+ bool has_locked_items = false;
+ List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);
// Remove not movable nodes
for (CanvasItem *E : selection) {
@@ -1477,6 +1483,11 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
}
_save_canvas_item_state(drag_selection);
return true;
+ } else {
+ if (has_locked_items) {
+ EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);
+ }
+ return has_locked_items;
}
}
}
@@ -1687,13 +1698,18 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {
_commit_canvas_item_state(
drag_selection,
vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection.front()->get()->get_name()));
+ snap_target[0] = SNAP_TARGET_NONE;
+ snap_target[1] = SNAP_TARGET_NONE;
_reset_drag();
+ viewport->queue_redraw();
return true;
}
// Cancel a drag
if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {
_restore_canvas_item_state(drag_selection);
+ snap_target[0] = SNAP_TARGET_NONE;
+ snap_target[1] = SNAP_TARGET_NONE;
_reset_drag();
viewport->queue_redraw();
return true;
@@ -1912,7 +1928,8 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
// Drag resize handles
if (drag_type == DRAG_NONE) {
if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {
- List<CanvasItem *> selection = _get_edited_canvas_items();
+ bool has_locked_items = false;
+ List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);
if (selection.size() == 1) {
CanvasItem *ci = selection.front()->get();
@@ -1941,6 +1958,11 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
_save_canvas_item_state(drag_selection);
return true;
}
+ } else {
+ if (has_locked_items) {
+ EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);
+ }
+ return has_locked_items;
}
}
}
@@ -2051,7 +2073,8 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
//Start moving the nodes
if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
if ((b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {
- List<CanvasItem *> selection = _get_edited_canvas_items();
+ bool has_locked_items = false;
+ List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);
if (selection.size() > 0) {
drag_selection.clear();
@@ -2084,6 +2107,11 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
_save_canvas_item_state(drag_selection);
return true;
+ } else {
+ if (has_locked_items) {
+ EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);
+ }
+ return has_locked_items;
}
}
}
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index a9de5e9a0b..bae9efebc9 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -188,6 +188,8 @@ private:
GRID_VISIBILITY_HIDE,
};
+ const String locked_transform_warning = TTRC("All selected CanvasItems are either invisible or locked in some way and can't be transformed.");
+
bool selection_menu_additive_selection = false;
Tool tool = TOOL_SELECT;
@@ -430,7 +432,7 @@ private:
ThemePreviewMode theme_preview = THEME_PREVIEW_PROJECT;
void _switch_theme_preview(int p_mode);
- List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true) const;
+ List<CanvasItem *> _get_edited_canvas_items(bool p_retrieve_locked = false, bool p_remove_canvas_item_if_parent_in_selection = true, bool *r_has_locked_items = nullptr) const;
Rect2 _get_encompassing_rect_from_list(const List<CanvasItem *> &p_list);
void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true);
Rect2 _get_encompassing_rect(const Node *p_node);
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index 005407e188..d9f60e155d 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -414,13 +414,19 @@ void EditorPlugin::remove_translation_parser_plugin(const Ref<EditorTranslationP
void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer, bool p_first_priority) {
ERR_FAIL_COND(!p_importer.is_valid());
ResourceFormatImporter::get_singleton()->add_importer(p_importer, p_first_priority);
- callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
+ // Plugins are now loaded during the first scan. It's important not to start another scan,
+ // even a deferred one, as it would cause a scan during a scan at the next main thread iteration.
+ if (!EditorFileSystem::get_singleton()->doing_first_scan()) {
+ callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
+ }
}
void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) {
ERR_FAIL_COND(!p_importer.is_valid());
ResourceFormatImporter::get_singleton()->remove_importer(p_importer);
- if (!EditorNode::get_singleton()->is_exiting()) {
+ // Plugins are now loaded during the first scan. It's important not to start another scan,
+ // even a deferred one, as it would cause a scan during a scan at the next main thread iteration.
+ if (!EditorNode::get_singleton()->is_exiting() && !EditorFileSystem::get_singleton()->doing_first_scan()) {
callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
}
}
diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
index 9ccbea4c3c..7e22e1209c 100644
--- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp
+++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
@@ -213,7 +213,7 @@ void GradientTexture2DEdit::_draw() {
}
// Draw handles.
- const Color focus_modulate = Color(0.5, 1, 2);
+ const Color focus_modulate = Color(0.4, 1, 1);
bool modulate_handle_from = grabbed == HANDLE_FROM || hovered == HANDLE_FROM;
bool modulate_handle_to = grabbed == HANDLE_TO || hovered == HANDLE_TO;
draw_texture(fill_from_icon, (_get_handle_pos(HANDLE_FROM) - handle_size / 2).round(), modulate_handle_from ? focus_modulate : Color(1, 1, 1));
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 3d7647ca5b..c6a0dfb888 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -2903,8 +2903,8 @@ void Node3DEditorViewport::_notification(int p_what) {
}
bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
- if (show_info != info_label->is_visible()) {
- info_label->set_visible(show_info);
+ if (show_info != info_panel->is_visible()) {
+ info_panel->set_visible(show_info);
}
Camera3D *current_camera;
@@ -3087,7 +3087,7 @@ void Node3DEditorViewport::_notification(int p_what) {
frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
- info_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);
+ info_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
frame_time_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
// Set a minimum width to prevent the width from changing all the time
@@ -5382,15 +5382,19 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN);
surface->add_child(bottom_center_vbox);
+ info_panel = memnew(PanelContainer);
+ info_panel->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);
+ info_panel->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);
+ info_panel->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -10 * EDSCALE);
+ info_panel->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
+ info_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);
+ info_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);
+ info_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
+ surface->add_child(info_panel);
+ info_panel->hide();
+
info_label = memnew(Label);
- info_label->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);
- info_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);
- info_label->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -10 * EDSCALE);
- info_label->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
- info_label->set_h_grow_direction(GROW_DIRECTION_BEGIN);
- info_label->set_v_grow_direction(GROW_DIRECTION_BEGIN);
- surface->add_child(info_label);
- info_label->hide();
+ info_panel->add_child(info_label);
cinema_label = memnew(Label);
cinema_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 10 * EDSCALE);
@@ -5475,6 +5479,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
top_right_vbox->add_child(rotation_control);
frame_time_panel = memnew(PanelContainer);
+ frame_time_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
top_right_vbox->add_child(frame_time_panel);
frame_time_panel->hide();
@@ -8644,7 +8649,8 @@ Node3DEditor::Node3DEditor() {
ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), Key::K);
ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), Key::O);
ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), Key::F);
- ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::M);
+ ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTR("Align Transform with View"),
+ { int32_t(KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::KP_0), int32_t(KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::M) });
ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F);
ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F);
ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`.
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 580c001238..5bd14748c0 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -241,6 +241,7 @@ private:
real_t freelook_speed;
Vector2 previous_mouse_position;
+ PanelContainer *info_panel = nullptr;
Label *info_label = nullptr;
Label *cinema_label = nullptr;
Label *locked_label = nullptr;
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index b5db7bef70..e442c37edd 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -52,6 +52,31 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/view_panner.h"
+class UVEditDialog : public AcceptDialog {
+ GDCLASS(UVEditDialog, AcceptDialog);
+
+ void shortcut_input(const Ref<InputEvent> &p_event) override {
+ const Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed()) {
+ bool handled = false;
+
+ if (ED_IS_SHORTCUT("ui_undo", p_event)) {
+ EditorNode::get_singleton()->undo();
+ handled = true;
+ }
+
+ if (ED_IS_SHORTCUT("ui_redo", p_event)) {
+ EditorNode::get_singleton()->redo();
+ handled = true;
+ }
+
+ if (handled) {
+ set_input_as_handled();
+ }
+ }
+ }
+};
+
Node2D *Polygon2DEditor::_get_node() const {
return node;
}
@@ -1305,9 +1330,10 @@ Polygon2DEditor::Polygon2DEditor() {
button_uv->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_menu_option).bind(MODE_EDIT_UV));
uv_mode = UV_MODE_EDIT_POINT;
- uv_edit = memnew(AcceptDialog);
- add_child(uv_edit);
+ uv_edit = memnew(UVEditDialog);
uv_edit->set_title(TTR("Polygon 2D UV Editor"));
+ uv_edit->set_process_shortcut_input(true);
+ add_child(uv_edit);
uv_edit->connect(SceneStringName(confirmed), callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide));
uv_edit->connect("canceled", callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide));
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 27056a6cc4..48087e3166 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -953,7 +953,7 @@ void SpriteFramesEditor::_sync_animation() {
}
void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) {
- if (!frames->has_animation(p_name)) {
+ if (frames.is_null() || !frames->has_animation(p_name)) {
return;
}
edited_anim = p_name;
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index 62090a5d17..e8a7b3b514 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -138,6 +138,11 @@ void GenericTilePolygonEditor::_base_control_draw() {
const Ref<Texture2D> add_handle = get_editor_theme_icon(SNAME("EditorHandleAdd"));
const Ref<StyleBox> focus_stylebox = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles));
+ // Get the background data.
+ TileData *tile_data = background_atlas_source->get_tile_data(background_atlas_coords, background_alternative_id);
+ ERR_FAIL_NULL(tile_data);
+ Rect2 background_region = background_atlas_source->get_tile_texture_region(background_atlas_coords);
+
// Draw the focus rectangle.
if (base_control->has_focus()) {
base_control->draw_style_box(focus_stylebox, Rect2(Vector2(), base_control->get_size()));
@@ -145,42 +150,49 @@ void GenericTilePolygonEditor::_base_control_draw() {
// Draw tile-related things.
const Size2 base_tile_size = tile_set->get_tile_size();
- const Size2 tile_size = background_region.size;
Transform2D xform;
xform.set_origin(base_control->get_size() / 2 + panning);
xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom()));
base_control->draw_set_transform_matrix(xform);
- // Draw the tile shape filled.
- Transform2D tile_xform;
- tile_xform.set_scale(tile_size);
- tile_set->draw_tile_shape(base_control, tile_xform, Color(1.0, 1.0, 1.0, 0.3), true);
+ // Draw fill rect under texture region.
+ Rect2 texture_rect(-background_region.size / 2 - tile_data->get_texture_origin(), background_region.size);
+ base_control->draw_rect(texture_rect, Color(1, 1, 1, 0.3));
// Draw the background.
- if (background_texture.is_valid()) {
+ if (background_atlas_source->get_texture().is_valid()) {
Size2 region_size = background_region.size;
- if (background_h_flip) {
+ if (tile_data->get_flip_h()) {
region_size.x = -region_size.x;
}
- if (background_v_flip) {
+ if (tile_data->get_flip_v()) {
region_size.y = -region_size.y;
}
- base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, region_size), background_region, background_modulate, background_transpose);
+ base_control->draw_texture_rect_region(background_atlas_source->get_texture(), Rect2(-background_region.size / 2 - tile_data->get_texture_origin(), region_size), background_region, tile_data->get_modulate(), tile_data->get_transpose());
}
+ // Compute and draw the grid area.
+ Rect2 grid_area = Rect2(-base_tile_size / 2, base_tile_size);
+ grid_area.expand_to(-background_region.get_size() / 2 - tile_data->get_texture_origin());
+ grid_area.expand_to(background_region.get_size() / 2 - tile_data->get_texture_origin());
+ base_control->draw_rect(grid_area, Color(1, 1, 1, 0.3), false);
+
// Draw grid.
if (current_snap_option == SNAP_GRID) {
Vector2 spacing = base_tile_size / snap_subdivision->get_value();
- Vector2 offset = -tile_size / 2;
- int w = snap_subdivision->get_value() * (tile_size / base_tile_size).x;
- int h = snap_subdivision->get_value() * (tile_size / base_tile_size).y;
-
- for (int y = 1; y < h; y++) {
- for (int x = 1; x < w; x++) {
- base_control->draw_line(Vector2(spacing.x * x, 0) + offset, Vector2(spacing.x * x, tile_size.y) + offset, Color(1, 1, 1, 0.33));
- base_control->draw_line(Vector2(0, spacing.y * y) + offset, Vector2(tile_size.x, spacing.y * y) + offset, Color(1, 1, 1, 0.33));
- }
+ Vector2 origin = -base_tile_size / 2;
+ for (real_t y = origin.y; y < grid_area.get_end().y; y += spacing.y) {
+ base_control->draw_line(Vector2(grid_area.get_position().x, y), Vector2(grid_area.get_end().x, y), Color(1, 1, 1, 0.33));
+ }
+ for (real_t y = origin.y - spacing.y; y > grid_area.get_position().y; y -= spacing.y) {
+ base_control->draw_line(Vector2(grid_area.get_position().x, y), Vector2(grid_area.get_end().x, y), Color(1, 1, 1, 0.33));
+ }
+ for (real_t x = origin.x; x < grid_area.get_end().x; x += spacing.x) {
+ base_control->draw_line(Vector2(x, grid_area.get_position().y), Vector2(x, grid_area.get_end().y), Color(1, 1, 1, 0.33));
+ }
+ for (real_t x = origin.x - spacing.x; x > grid_area.get_position().x; x -= spacing.x) {
+ base_control->draw_line(Vector2(x, grid_area.get_position().y), Vector2(x, grid_area.get_end().y), Color(1, 1, 1, 0.33));
}
}
@@ -237,7 +249,7 @@ void GenericTilePolygonEditor::_base_control_draw() {
for (int i = 0; i < (int)polygons.size(); i++) {
const Vector<Vector2> &polygon = polygons[i];
for (int j = 0; j < polygon.size(); j++) {
- const Color poly_modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1);
+ const Color poly_modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.4, 1, 1) : Color(1, 1, 1);
base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, poly_modulate);
}
}
@@ -253,7 +265,7 @@ void GenericTilePolygonEditor::_base_control_draw() {
}
if (drag_type == DRAG_TYPE_CREATE_POINT) {
- base_control->draw_texture(handle, xform.xform(in_creation_point) - handle->get_size() / 2, Color(0.5, 1, 2));
+ base_control->draw_texture(handle, xform.xform(in_creation_point) - handle->get_size() / 2, Color(0.4, 1, 1));
}
// Draw the point creation preview in edit mode.
@@ -263,6 +275,8 @@ void GenericTilePolygonEditor::_base_control_draw() {
// Draw the tile shape line.
base_control->draw_set_transform_matrix(xform);
+ Transform2D tile_xform;
+ tile_xform.set_scale(base_tile_size);
tile_set->draw_tile_shape(base_control, tile_xform, grid_color, false);
base_control->draw_set_transform_matrix(Transform2D());
}
@@ -721,8 +735,18 @@ void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) {
add_polygon(polygon);
}
+ // Trigger a redraw on tile_set change.
+ Callable callable = callable_mp((CanvasItem *)base_control, &CanvasItem::queue_redraw);
+ if (tile_set.is_valid()) {
+ tile_set->disconnect_changed(callable);
+ }
+
tile_set = p_tile_set;
+ if (tile_set.is_valid()) {
+ tile_set->connect_changed(callable);
+ }
+
// Set the default zoom value.
int default_control_y_size = 200 * EDSCALE;
Vector2 zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size();
@@ -746,14 +770,11 @@ void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) {
_zoom_changed();
}
-void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_region, Vector2 p_offset, bool p_flip_h, bool p_flip_v, bool p_transpose, Color p_modulate) {
- background_texture = p_texture;
- background_region = p_region;
- background_offset = p_offset;
- background_h_flip = p_flip_h;
- background_v_flip = p_flip_v;
- background_transpose = p_transpose;
- background_modulate = p_modulate;
+void GenericTilePolygonEditor::set_background_tile(const TileSetAtlasSource *p_atlas_source, const Vector2 &p_atlas_coords, int p_alternative_id) {
+ ERR_FAIL_NULL(p_atlas_source);
+ background_atlas_source = p_atlas_source;
+ background_atlas_coords = p_atlas_coords;
+ background_alternative_id = p_alternative_id;
base_control->queue_redraw();
}
@@ -1457,7 +1478,7 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile
if (occluder_polygon.is_valid()) {
polygon_editor->add_polygon(occluder_polygon->get_polygon());
}
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
@@ -1466,7 +1487,7 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl
Ref<OccluderPolygon2D> occluder_polygon = p_value;
tile_data->set_occluder(occlusion_layer, occluder_polygon);
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
@@ -1613,7 +1634,7 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_
E.value->update_property();
}
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
@@ -1632,7 +1653,7 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so
tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]);
}
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
@@ -2873,7 +2894,7 @@ void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set
polygon_editor->add_polygon(nav_polygon->get_outline(i));
}
}
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) {
@@ -2882,7 +2903,7 @@ void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_s
Ref<NavigationPolygon> nav_polygon = p_value;
tile_data->set_navigation_polygon(navigation_layer, nav_polygon);
- polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile);
}
Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) {
diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h
index f9d8f7d077..9b1eadf331 100644
--- a/editor/plugins/tiles/tile_data_editors.h
+++ b/editor/plugins/tiles/tile_data_editors.h
@@ -142,13 +142,9 @@ private:
Vector2 panning;
bool initializing = true;
- Ref<Texture2D> background_texture;
- Rect2 background_region;
- Vector2 background_offset;
- bool background_h_flip = false;
- bool background_v_flip = false;
- bool background_transpose = false;
- Color background_modulate;
+ Ref<TileSetAtlasSource> background_atlas_source;
+ Vector2i background_atlas_coords;
+ int background_alternative_id;
Color polygon_color = Color(1.0, 0.0, 0.0);
@@ -183,7 +179,7 @@ public:
void set_use_undo_redo(bool p_use_undo_redo);
void set_tile_set(Ref<TileSet> p_tile_set);
- void set_background(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0));
+ void set_background_tile(const TileSetAtlasSource *p_atlas_source, const Vector2 &p_atlas_coords, int p_alternative_id);
int get_polygon_count();
int add_polygon(const Vector<Point2> &p_polygon, int p_index = -1);
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 41b8b88f73..d3afd25502 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -763,8 +763,13 @@ bool TileMapLayerEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEven
}
} else {
- // Released
- _stop_dragging();
+ // Released.
+ if (drag_type == DRAG_TYPE_NONE) {
+ drag_erasing = false;
+ return false;
+ } else {
+ _stop_dragging();
+ }
drag_erasing = false;
}
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 94e68ccc7c..7e34a36a6e 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -651,7 +651,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() {
tile_data_editors_tree->add_theme_constant_override("v_separation", 1);
tile_data_editors_tree->add_theme_constant_override("h_separation", 3);
- Color group_color = get_theme_color(SNAME("prop_category"), EditorStringName(Editor));
+ Color group_color = get_theme_color(SNAME("separator_color"), EditorStringName(Editor));
// List of editors.
// --- Rendering ---
@@ -2451,6 +2451,8 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
resize_handle = get_editor_theme_icon(SNAME("EditorHandle"));
resize_handle_disabled = get_editor_theme_icon(SNAME("EditorHandleDisabled"));
+
+ tile_data_editors_tree->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), "PopupPanel"));
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
@@ -2820,13 +2822,15 @@ void EditorPropertyTilePolygon::update_property() {
ERR_FAIL_COND(atlas_tile_proxy_object->get_edited_tiles().is_empty());
Ref<TileSetAtlasSource> tile_set_atlas_source = atlas_tile_proxy_object->get_edited_tile_set_atlas_source();
- generic_tile_polygon_editor->set_tile_set(Ref<TileSet>(tile_set_atlas_source->get_tile_set()));
+ Ref<TileSet> tile_set(tile_set_atlas_source->get_tile_set());
+
+ // Update the polyugon editor tile_set.
+ generic_tile_polygon_editor->set_tile_set(tile_set);
// Set the background
Vector2i coords = atlas_tile_proxy_object->get_edited_tiles().front()->get().tile;
int alternative = atlas_tile_proxy_object->get_edited_tiles().front()->get().alternative;
- TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative);
- generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_data->get_texture_origin(), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate());
+ generic_tile_polygon_editor->set_background_tile(*tile_set_atlas_source, coords, alternative);
// Reset the polygons.
generic_tile_polygon_editor->clear_polygons();
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 607c446e1b..2f36198b23 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -133,9 +133,9 @@ void VSRerouteNode::_notification(int p_what) {
connect(SceneStringName(mouse_exited), callable_mp(this, &VSRerouteNode::_on_mouse_exited));
} break;
case NOTIFICATION_DRAW: {
- Vector2 offset = Vector2(0, -16);
+ Vector2 offset = Vector2(0, -16 * EDSCALE);
Color drag_bg_color = get_theme_color(SNAME("drag_background"), SNAME("VSRerouteNode"));
- draw_circle(get_size() * 0.5 + offset, 16, Color(drag_bg_color, selected ? 1 : icon_opacity));
+ draw_circle(get_size() * 0.5 + offset, 16 * EDSCALE, Color(drag_bg_color, selected ? 1 : icon_opacity), true, -1, true);
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("ToolMove"));
Point2 icon_offset = -icon->get_size() * 0.5 + get_size() * 0.5 + offset;
@@ -154,6 +154,7 @@ VSRerouteNode::VSRerouteNode() {
title_lbl->hide();
const Size2 size = Size2(32, 32) * EDSCALE;
+ print_line("VSRerouteNode size: " + size);
Control *slot_area = memnew(Control);
slot_area->set_custom_minimum_size(size);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index c2d89b28f5..bdf4e41c5f 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -91,6 +91,10 @@ void ProjectSettingsEditor::update_plugins() {
plugin_settings->update_plugins();
}
+void ProjectSettingsEditor::init_autoloads() {
+ autoload_settings->init_autoloads();
+}
+
void ProjectSettingsEditor::_setting_edited(const String &p_name) {
queue_save();
}
@@ -231,28 +235,17 @@ void ProjectSettingsEditor::_select_type(Variant::Type p_type) {
}
void ProjectSettingsEditor::shortcut_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
bool handled = false;
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
- undo_redo->undo();
+ EditorNode::get_singleton()->undo();
handled = true;
}
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
- undo_redo->redo();
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
+ EditorNode::get_singleton()->redo();
handled = true;
}
diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h
index fb294c6bbd..5890ed2c2d 100644
--- a/editor/project_settings_editor.h
+++ b/editor/project_settings_editor.h
@@ -123,6 +123,7 @@ public:
void set_plugins_page();
void set_general_page(const String &p_category);
void update_plugins();
+ void init_autoloads();
EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
GroupSettingsEditor *get_group_settings() { return group_settings; }
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 7f60fbbdb9..94bd3e16d3 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -77,12 +77,12 @@ void SceneTreeDock::_quick_open() {
void SceneTreeDock::_inspect_hovered_node() {
select_node_hovered_at_end_of_drag = true;
- if (tree_item_inspected != nullptr) {
- tree_item_inspected->clear_custom_color(0);
- }
Tree *tree = scene_tree->get_scene_tree();
TreeItem *item = tree->get_item_with_metadata(node_hovered_now->get_path());
if (item) {
+ if (tree_item_inspected) {
+ tree_item_inspected->clear_custom_color(0);
+ }
tree_item_inspected = item;
tree_item_inspected->set_custom_color(0, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
}
@@ -133,8 +133,9 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) {
}
if (mb->is_released()) {
- if (tree_item_inspected != nullptr) {
+ if (tree_item_inspected) {
tree_item_inspected->clear_custom_color(0);
+ tree_item_inspected = nullptr;
}
_reset_hovering_timer();
}
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 9f0865d950..03752656c0 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1665,7 +1665,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// GraphFrame's title Label.
p_theme->set_type_variation("GraphFrameTitleLabel", "Label");
p_theme->set_stylebox(CoreStringName(normal), "GraphFrameTitleLabel", memnew(StyleBoxEmpty));
- p_theme->set_font_size(SceneStringName(font_size), "GraphFrameTitleLabel", 22);
+ p_theme->set_font_size(SceneStringName(font_size), "GraphFrameTitleLabel", 22 * EDSCALE);
p_theme->set_color(SceneStringName(font_color), "GraphFrameTitleLabel", Color(1, 1, 1));
p_theme->set_color("font_shadow_color", "GraphFrameTitleLabel", Color(0, 0, 0, 0));
p_theme->set_color("font_outline_color", "GraphFrameTitleLabel", Color(1, 1, 1));
@@ -1680,7 +1680,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
{
Ref<StyleBox> vs_reroute_panel_style = make_empty_stylebox();
Ref<StyleBox> vs_reroute_titlebar_style = vs_reroute_panel_style->duplicate();
- vs_reroute_titlebar_style->set_content_margin_all(16);
+ vs_reroute_titlebar_style->set_content_margin_all(16 * EDSCALE);
p_theme->set_stylebox(SceneStringName(panel), "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("panel_selected", "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("titlebar", "VSRerouteNode", vs_reroute_titlebar_style);
diff --git a/main/main.cpp b/main/main.cpp
index 1cbd732747..9743a8086f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2552,7 +2552,7 @@ Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Ref<R
return OK;
}
-Error Main::setup2() {
+Error Main::setup2(bool p_show_boot_logo) {
Thread::make_main_thread(); // Make whatever thread call this the main thread.
set_current_thread_safe_for_nodes(true);
@@ -2896,12 +2896,6 @@ Error Main::setup2() {
MAIN_PRINT("Main: Setup Logo");
-#if !defined(TOOLS_ENABLED) && defined(WEB_ENABLED)
- bool show_logo = false;
-#else
- bool show_logo = true;
-#endif
-
if (init_windowed) {
//do none..
} else if (init_maximized) {
@@ -2913,67 +2907,11 @@ Error Main::setup2() {
DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP, true);
}
- MAIN_PRINT("Main: Load Boot Image");
-
Color clear = GLOBAL_DEF_BASIC("rendering/environment/defaults/default_clear_color", Color(0.3, 0.3, 0.3));
RenderingServer::get_singleton()->set_default_clear_color(clear);
- if (show_logo) { //boot logo!
- const bool boot_logo_image = GLOBAL_DEF_BASIC("application/boot_splash/show_image", true);
- const String boot_logo_path = String(GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png"), String())).strip_edges();
- const bool boot_logo_scale = GLOBAL_DEF_BASIC("application/boot_splash/fullsize", true);
- const bool boot_logo_filter = GLOBAL_DEF_BASIC("application/boot_splash/use_filter", true);
-
- Ref<Image> boot_logo;
-
- if (boot_logo_image) {
- if (!boot_logo_path.is_empty()) {
- 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.");
- }
- }
- } else {
- // Create a 1×1 transparent image. This will effectively hide the splash image.
- boot_logo.instantiate();
- boot_logo->initialize_data(1, 1, false, Image::FORMAT_RGBA8);
- boot_logo->set_pixel(0, 0, Color(0, 0, 0, 0));
- }
-
- Color boot_bg_color = GLOBAL_GET("application/boot_splash/bg_color");
-
-#if defined(TOOLS_ENABLED) && !defined(NO_EDITOR_SPLASH)
- boot_bg_color =
- GLOBAL_DEF_BASIC("application/boot_splash/bg_color",
- (editor || project_manager) ? boot_splash_editor_bg_color : boot_splash_bg_color);
-#endif
- if (boot_logo.is_valid()) {
- RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale,
- boot_logo_filter);
-
- } else {
-#ifndef NO_DEFAULT_BOOT_LOGO
- MAIN_PRINT("Main: Create bootsplash");
-#if defined(TOOLS_ENABLED) && !defined(NO_EDITOR_SPLASH)
- Ref<Image> splash = (editor || project_manager) ? memnew(Image(boot_splash_editor_png)) : memnew(Image(boot_splash_png));
-#else
- Ref<Image> splash = memnew(Image(boot_splash_png));
-#endif
-
- MAIN_PRINT("Main: ClearColor");
- RenderingServer::get_singleton()->set_default_clear_color(boot_bg_color);
- MAIN_PRINT("Main: Image");
- RenderingServer::get_singleton()->set_boot_image(splash, boot_bg_color, false);
-#endif
- }
-
-#if defined(TOOLS_ENABLED) && defined(MACOS_ENABLED)
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_ICON) && OS::get_singleton()->get_bundle_icon_path().is_empty()) {
- Ref<Image> icon = memnew(Image(app_icon_png));
- DisplayServer::get_singleton()->set_icon(icon);
- }
-#endif
+ if (p_show_boot_logo) {
+ setup_boot_logo();
}
MAIN_PRINT("Main: Clear Color");
@@ -3216,6 +3154,71 @@ Error Main::setup2() {
return OK;
}
+void Main::setup_boot_logo() {
+ MAIN_PRINT("Main: Load Boot Image");
+
+#if !defined(TOOLS_ENABLED) && defined(WEB_ENABLED)
+ bool show_logo = false;
+#else
+ bool show_logo = true;
+#endif
+
+ if (show_logo) { //boot logo!
+ const bool boot_logo_image = GLOBAL_DEF_BASIC("application/boot_splash/show_image", true);
+ const String boot_logo_path = String(GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png"), String())).strip_edges();
+ const bool boot_logo_scale = GLOBAL_DEF_BASIC("application/boot_splash/fullsize", true);
+ const bool boot_logo_filter = GLOBAL_DEF_BASIC("application/boot_splash/use_filter", true);
+
+ Ref<Image> boot_logo;
+
+ if (boot_logo_image) {
+ if (!boot_logo_path.is_empty()) {
+ 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.");
+ }
+ }
+ } else {
+ // Create a 1×1 transparent image. This will effectively hide the splash image.
+ boot_logo.instantiate();
+ boot_logo->initialize_data(1, 1, false, Image::FORMAT_RGBA8);
+ boot_logo->set_pixel(0, 0, Color(0, 0, 0, 0));
+ }
+
+ Color boot_bg_color = GLOBAL_GET("application/boot_splash/bg_color");
+
+#if defined(TOOLS_ENABLED) && !defined(NO_EDITOR_SPLASH)
+ boot_bg_color = GLOBAL_DEF_BASIC("application/boot_splash/bg_color", (editor || project_manager) ? boot_splash_editor_bg_color : boot_splash_bg_color);
+#endif
+ if (boot_logo.is_valid()) {
+ RenderingServer::get_singleton()->set_boot_image(boot_logo, boot_bg_color, boot_logo_scale, boot_logo_filter);
+
+ } else {
+#ifndef NO_DEFAULT_BOOT_LOGO
+ MAIN_PRINT("Main: Create bootsplash");
+#if defined(TOOLS_ENABLED) && !defined(NO_EDITOR_SPLASH)
+ Ref<Image> splash = (editor || project_manager) ? memnew(Image(boot_splash_editor_png)) : memnew(Image(boot_splash_png));
+#else
+ Ref<Image> splash = memnew(Image(boot_splash_png));
+#endif
+
+ MAIN_PRINT("Main: ClearColor");
+ RenderingServer::get_singleton()->set_default_clear_color(boot_bg_color);
+ MAIN_PRINT("Main: Image");
+ RenderingServer::get_singleton()->set_boot_image(splash, boot_bg_color, false);
+#endif
+ }
+
+#if defined(TOOLS_ENABLED) && defined(MACOS_ENABLED)
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_ICON) && OS::get_singleton()->get_bundle_icon_path().is_empty()) {
+ Ref<Image> icon = memnew(Image(app_icon_png));
+ DisplayServer::get_singleton()->set_icon(icon);
+ }
+#endif
+ }
+}
+
String Main::get_rendering_driver_name() {
return rendering_driver;
}
diff --git a/main/main.h b/main/main.h
index ff0fba6b51..b1cfcd3c2d 100644
--- a/main/main.h
+++ b/main/main.h
@@ -72,8 +72,9 @@ public:
static int test_entrypoint(int argc, char *argv[], bool &tests_need_run);
static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true);
- static Error setup2(); // The thread calling setup2() will effectively become the main thread.
+ static Error setup2(bool p_show_boot_logo = true); // The thread calling setup2() will effectively become the main thread.
static String get_rendering_driver_name();
+ static void setup_boot_logo();
#ifdef TESTS_ENABLED
static Error test_setup();
static void test_cleanup();
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 20d424894a..73b1e44db3 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -115,7 +115,7 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p
}
GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) {
- if (p_script->initializer) {
+ if (likely(p_script->valid) && p_script->initializer) {
return p_script->initializer;
} else {
GDScript *base_src = p_script->_base;
@@ -136,7 +136,11 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance
}
}
ERR_FAIL_NULL(p_script->implicit_initializer);
- p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
+ if (likely(valid)) {
+ p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error);
+ } else {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+ }
}
GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {
@@ -662,7 +666,7 @@ String GDScript::_get_debug_path() const {
}
Error GDScript::_static_init() {
- if (static_initializer) {
+ if (likely(valid) && static_initializer) {
Callable::CallError call_err;
static_initializer->call(nullptr, nullptr, 0, call_err);
if (call_err.error != Callable::CallError::CALL_OK) {
@@ -679,8 +683,6 @@ Error GDScript::_static_init() {
return err;
}
-#ifdef TOOLS_ENABLED
-
void GDScript::_static_default_init() {
for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) {
const GDScriptDataType &type = E.value.data_type;
@@ -702,6 +704,8 @@ void GDScript::_static_default_init() {
}
}
+#ifdef TOOLS_ENABLED
+
void GDScript::_save_old_static_data() {
old_static_variables_indices = static_variables_indices;
old_static_variables = static_variables;
@@ -873,9 +877,6 @@ Error GDScript::reload(bool p_keep_state) {
#ifdef TOOLS_ENABLED
if (can_run && p_keep_state) {
_restore_old_static_data();
- } else if (!can_run) {
- // Initialize static variables with sane default values even if the constructor isn't called.
- _static_default_init();
}
#endif
@@ -912,18 +913,15 @@ void GDScript::unload_static() const {
}
Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- if (unlikely(!valid)) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return Variant();
- }
-
GDScript *top = this;
while (top) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method);
- if (E) {
- ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script.");
+ if (likely(top->valid)) {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method);
+ if (E) {
+ ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script.");
- return E->value->call(nullptr, p_args, p_argcount, r_error);
+ return E->value->call(nullptr, p_args, p_argcount, r_error);
+ }
}
top = top->_base;
}
@@ -939,10 +937,6 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
- if (unlikely(!valid)) {
- return false;
- }
-
const GDScript *top = this;
while (top) {
{
@@ -956,7 +950,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
{
HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
if (E) {
- if (E->value.getter) {
+ if (likely(top->valid) && E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
return true;
@@ -966,7 +960,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
}
}
- {
+ if (likely(top->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name);
if (E && E->value->is_static()) {
if (top->rpc_config.has(p_name)) {
@@ -999,10 +993,6 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
return true;
}
- if (unlikely(!valid)) {
- return false;
- }
-
GDScript *top = this;
while (top) {
HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name);
@@ -1017,7 +1007,7 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
return false;
}
}
- if (member->setter) {
+ if (likely(top->valid) && member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
@@ -1037,10 +1027,6 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) {
void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
- if (unlikely(!valid)) {
- return;
- }
-
List<const GDScript *> classes;
const GDScript *top = this;
while (top) {
@@ -1657,10 +1643,6 @@ GDScript::~GDScript() {
//////////////////////////////
bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
- if (unlikely(!script->valid)) {
- return false;
- }
-
{
HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name);
if (E) {
@@ -1674,7 +1656,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
return false;
}
}
- if (member->setter) {
+ if (likely(script->valid) && member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
@@ -1701,7 +1683,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
return false;
}
}
- if (member->setter) {
+ if (likely(sptr->valid) && member->setter) {
const Variant *args = &value;
Callable::CallError err;
callp(member->setter, &args, 1, err);
@@ -1713,7 +1695,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
}
}
- {
+ if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set);
if (E) {
Variant name = p_name;
@@ -1734,14 +1716,10 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
}
bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
- if (unlikely(!script->valid)) {
- return false;
- }
-
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name);
if (E) {
- if (E->value.getter) {
+ 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) {
@@ -1766,7 +1744,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
{
HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name);
if (E) {
- if (E->value.getter) {
+ if (likely(sptr->valid) && E->value.getter) {
Callable::CallError ce;
r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
return true;
@@ -1784,7 +1762,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
}
- {
+ if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name);
if (E) {
if (sptr->rpc_config.has(p_name)) {
@@ -1804,7 +1782,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
}
}
- {
+ if (likely(sptr->valid)) {
HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get);
if (E) {
Variant name = p_name;
@@ -1844,13 +1822,15 @@ void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
const GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
- if (E) {
- Callable::CallError err;
- Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
- if (err.error == Callable::CallError::CALL_OK) {
- p_property = PropertyInfo::from_dict(property);
- return;
+ if (likely(sptr->valid)) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK) {
+ p_property = PropertyInfo::from_dict(property);
+ return;
+ }
}
}
sptr = sptr->_base;
@@ -1858,49 +1838,47 @@ void GDScriptInstance::validate_property(PropertyInfo &p_property) const {
}
void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
- if (unlikely(!script->valid)) {
- return;
- }
-
// exported members, not done yet!
const GDScript *sptr = script.ptr();
List<PropertyInfo> props;
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
- if (E) {
- Callable::CallError err;
- Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
-
- Array arr = ret;
- for (int i = 0; i < arr.size(); i++) {
- Dictionary d = arr[i];
- ERR_CONTINUE(!d.has("name"));
- ERR_CONTINUE(!d.has("type"));
-
- PropertyInfo pinfo;
- pinfo.name = d["name"];
- pinfo.type = Variant::Type(d["type"].operator int());
- if (d.has("hint")) {
- pinfo.hint = PropertyHint(d["hint"].operator int());
- }
- if (d.has("hint_string")) {
- pinfo.hint_string = d["hint_string"];
- }
- if (d.has("usage")) {
- pinfo.usage = d["usage"];
- }
- if (d.has("class_name")) {
- pinfo.class_name = d["class_name"];
- }
+ if (likely(sptr->valid)) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err);
+ if (err.error == Callable::CallError::CALL_OK) {
+ ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
+
+ Array arr = ret;
+ for (int i = 0; i < arr.size(); i++) {
+ Dictionary d = arr[i];
+ ERR_CONTINUE(!d.has("name"));
+ ERR_CONTINUE(!d.has("type"));
+
+ PropertyInfo pinfo;
+ pinfo.name = d["name"];
+ pinfo.type = Variant::Type(d["type"].operator int());
+ if (d.has("hint")) {
+ pinfo.hint = PropertyHint(d["hint"].operator int());
+ }
+ if (d.has("hint_string")) {
+ pinfo.hint_string = d["hint_string"];
+ }
+ if (d.has("usage")) {
+ pinfo.usage = d["usage"];
+ }
+ if (d.has("class_name")) {
+ pinfo.class_name = d["class_name"];
+ }
- ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE));
- ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX);
+ ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE));
+ ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX);
- props.push_back(pinfo);
+ props.push_back(pinfo);
+ }
}
}
}
@@ -1940,21 +1918,19 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
}
bool GDScriptInstance::property_can_revert(const StringName &p_name) const {
- if (unlikely(!script->valid)) {
- return false;
- }
-
Variant name = p_name;
const Variant *args[1] = { &name };
const GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert);
- if (E) {
- Callable::CallError err;
- Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
- if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
- return true;
+ if (likely(sptr->valid)) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) {
+ return true;
+ }
}
}
sptr = sptr->_base;
@@ -1964,22 +1940,20 @@ bool GDScriptInstance::property_can_revert(const StringName &p_name) const {
}
bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const {
- if (unlikely(!script->valid)) {
- return false;
- }
-
Variant name = p_name;
const Variant *args[1] = { &name };
const GDScript *sptr = script.ptr();
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert);
- if (E) {
- Callable::CallError err;
- Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
- if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
- r_ret = ret;
- return true;
+ if (likely(sptr->valid)) {
+ HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert);
+ if (E) {
+ Callable::CallError err;
+ Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err);
+ if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) {
+ r_ret = ret;
+ return true;
+ }
}
}
sptr = sptr->_base;
@@ -2035,30 +2009,28 @@ void GDScriptInstance::_call_implicit_ready_recursively(GDScript *p_script) {
if (p_script->_base) {
_call_implicit_ready_recursively(p_script->_base);
}
- if (p_script->implicit_ready) {
+ if (likely(p_script->valid) && p_script->implicit_ready) {
Callable::CallError err;
p_script->implicit_ready->call(this, nullptr, 0, err);
}
}
Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
- if (unlikely(!script->valid)) {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return Variant();
- }
-
GDScript *sptr = script.ptr();
if (unlikely(p_method == SceneStringName(_ready))) {
// Call implicit ready first, including for the super classes recursively.
_call_implicit_ready_recursively(sptr);
}
while (sptr) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method);
- if (E) {
- return E->value->call(this, p_args, p_argcount, r_error);
+ if (likely(sptr->valid)) {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method);
+ if (E) {
+ return E->value->call(this, p_args, p_argcount, r_error);
+ }
}
sptr = sptr->_base;
}
+
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
}
@@ -2083,12 +2055,14 @@ void GDScriptInstance::notification(int p_notification, bool p_reversed) {
sptr = sptr->_base;
}
for (GDScript *sc : pl) {
- HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
- if (E) {
- Callable::CallError err;
- E->value->call(this, args, 1, err);
- if (err.error != Callable::CallError::CALL_OK) {
- //print error about notification call
+ if (likely(sc->valid)) {
+ HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification);
+ if (E) {
+ Callable::CallError err;
+ E->value->call(this, args, 1, err);
+ if (err.error != Callable::CallError::CALL_OK) {
+ //print error about notification call
+ }
}
}
}
@@ -2759,7 +2733,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
String source = f->get_as_utf8_string();
GDScriptParser parser;
- err = parser.parse(source, p_path, false);
+ err = parser.parse(source, p_path, false, false);
const GDScriptParser::ClassNode *c = parser.get_tree();
if (!c) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 728459de44..d097cb193b 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -169,9 +169,7 @@ private:
GDScriptFunction *static_initializer = nullptr;
Error _static_init();
-#ifdef TOOLS_ENABLED
void _static_default_init(); // Initialize static variables with default values based on their types.
-#endif
int subclass_count = 0;
RBSet<Object *> instances;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 76e690a083..a6b4bce000 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2663,6 +2663,44 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
reduce_expression(p_assignment->assignee);
+#ifdef DEBUG_ENABLED
+ {
+ bool is_subscript = false;
+ GDScriptParser::ExpressionNode *base = p_assignment->assignee;
+ while (base && base->type == GDScriptParser::Node::SUBSCRIPT) {
+ is_subscript = true;
+ base = static_cast<GDScriptParser::SubscriptNode *>(base)->base;
+ }
+ if (base && base->type == GDScriptParser::Node::IDENTIFIER) {
+ GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(base);
+ if (current_lambda && current_lambda->captures_indices.has(id->name)) {
+ bool need_warn = false;
+ if (is_subscript) {
+ const GDScriptParser::DataType &id_type = id->datatype;
+ if (id_type.is_hard_type()) {
+ switch (id_type.kind) {
+ case GDScriptParser::DataType::BUILTIN:
+ // TODO: Change `Variant::is_type_shared()` to include packed arrays?
+ need_warn = !Variant::is_type_shared(id_type.builtin_type) && id_type.builtin_type < Variant::PACKED_BYTE_ARRAY;
+ break;
+ case GDScriptParser::DataType::ENUM:
+ need_warn = true;
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ need_warn = true;
+ }
+ if (need_warn) {
+ parser->push_warning(p_assignment, GDScriptWarning::CONFUSABLE_CAPTURE_REASSIGNMENT, id->name);
+ }
+ }
+ }
+ }
+#endif
+
if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) {
return;
}
diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp
index ac6f5f05c6..7c9fba799d 100644
--- a/modules/gdscript/gdscript_cache.cpp
+++ b/modules/gdscript/gdscript_cache.cpp
@@ -42,6 +42,10 @@ GDScriptParserRef::Status GDScriptParserRef::get_status() const {
return status;
}
+String GDScriptParserRef::get_path() const {
+ return path;
+}
+
uint32_t GDScriptParserRef::get_source_hash() const {
return source_hash;
}
@@ -91,12 +95,8 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
result = get_analyzer()->resolve_interface();
} break;
case INTERFACE_SOLVED: {
- status = BODY_SOLVED;
- result = get_analyzer()->resolve_body();
- } break;
- case BODY_SOLVED: {
status = FULLY_SOLVED;
- result = get_analyzer()->resolve_dependencies();
+ result = get_analyzer()->resolve_body();
} break;
case FULLY_SOLVED: {
return result;
@@ -135,9 +135,7 @@ void GDScriptParserRef::clear() {
GDScriptParserRef::~GDScriptParserRef() {
clear();
-
- MutexLock lock(GDScriptCache::singleton->mutex);
- GDScriptCache::singleton->parser_map.erase(path);
+ GDScriptCache::remove_parser(path);
}
GDScriptCache *GDScriptCache::singleton = nullptr;
@@ -158,6 +156,11 @@ void GDScriptCache::move_script(const String &p_from, const String &p_to) {
}
singleton->parser_map.erase(p_from);
+ if (singleton->parser_inverse_dependencies.has(p_from) && !p_from.is_empty()) {
+ singleton->parser_inverse_dependencies[p_to] = singleton->parser_inverse_dependencies[p_from];
+ }
+ singleton->parser_inverse_dependencies.erase(p_from);
+
if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) {
singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from];
}
@@ -181,13 +184,11 @@ void GDScriptCache::remove_script(const String &p_path) {
}
if (singleton->parser_map.has(p_path)) {
- // Keep a local reference until it goes out of scope.
- // Clearing it can trigger a reference to itself to go out of scope, destructing it before clear finishes.
- Ref<GDScriptParserRef> parser_ref = singleton->parser_map[p_path];
- singleton->parser_map.erase(p_path);
- parser_ref->clear();
+ singleton->parser_map[p_path]->clear();
}
+ remove_parser(p_path);
+
singleton->dependencies.erase(p_path);
singleton->shallow_gdscript_cache.erase(p_path);
singleton->full_gdscript_cache.erase(p_path);
@@ -198,6 +199,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
Ref<GDScriptParserRef> ref;
if (!p_owner.is_empty()) {
singleton->dependencies[p_owner].insert(p_path);
+ singleton->parser_inverse_dependencies[p_path].insert(p_owner);
}
if (singleton->parser_map.has(p_path)) {
ref = Ref<GDScriptParserRef>(singleton->parser_map[p_path]);
@@ -229,6 +231,13 @@ void GDScriptCache::remove_parser(const String &p_path) {
MutexLock lock(singleton->mutex);
// Can't clear the parser because some other parser might be currently using it in the chain of calls.
singleton->parser_map.erase(p_path);
+
+ // Have to copy while iterating, because parser_inverse_dependencies is modified.
+ HashSet<String> ideps = singleton->parser_inverse_dependencies[p_path];
+ singleton->parser_inverse_dependencies.erase(p_path);
+ for (String idep_path : ideps) {
+ remove_parser(idep_path);
+ }
}
String GDScriptCache::get_source_code(const String &p_path) {
@@ -344,7 +353,11 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
}
}
+ // Allowing lifting the lock might cause a script to be reloaded multiple times,
+ // which, as a last resort deadlock prevention strategy, is a good tradeoff.
+ uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&singleton->mutex);
r_error = script->reload(true);
+ WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
if (r_error) {
return script;
}
@@ -417,6 +430,8 @@ void GDScriptCache::clear() {
}
singleton->cleared = true;
+ singleton->parser_inverse_dependencies.clear();
+
RBSet<Ref<GDScriptParserRef>> parser_map_refs;
for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) {
parser_map_refs.insert(E.value);
diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h
index c738233beb..c927317e19 100644
--- a/modules/gdscript/gdscript_cache.h
+++ b/modules/gdscript/gdscript_cache.h
@@ -48,7 +48,6 @@ public:
PARSED,
INHERITANCE_SOLVED,
INTERFACE_SOLVED,
- BODY_SOLVED,
FULLY_SOLVED,
};
@@ -66,6 +65,7 @@ private:
public:
Status get_status() const;
+ String get_path() const;
uint32_t get_source_hash() const;
GDScriptParser *get_parser();
GDScriptAnalyzer *get_analyzer();
@@ -83,6 +83,7 @@ class GDScriptCache {
HashMap<String, Ref<GDScript>> full_gdscript_cache;
HashMap<String, Ref<GDScript>> static_gdscript_cache;
HashMap<String, HashSet<String>> dependencies;
+ HashMap<String, HashSet<String>> parser_inverse_dependencies;
friend class GDScript;
friend class GDScriptParserRef;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index eeffc13a10..5469dad3f7 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -3006,6 +3006,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser:
has_static_data = has_static_data || inner_class->has_static_data;
}
+ p_script->_static_default_init();
+
p_script->valid = true;
return OK;
}
@@ -3228,11 +3230,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
GDScriptCache::add_static_script(p_script);
}
- err = GDScriptCache::finish_compiling(main_script->path);
- if (err) {
- main_script->valid = false;
- }
- return err;
+ return GDScriptCache::finish_compiling(main_script->path);
}
String GDScriptCompiler::get_error() const {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index aaa09134f0..a1ea94667d 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -309,13 +309,14 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) {
completion_call_stack.back()->get().argument = p_argument;
}
-Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) {
+Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) {
clear();
String source = p_source_code;
int cursor_line = -1;
int cursor_column = -1;
for_completion = p_for_completion;
+ parse_body = p_parse_body;
int tab_size = 4;
#ifdef TOOLS_ENABLED
@@ -689,6 +690,12 @@ void GDScriptParser::parse_program() {
}
}
+ // When the only thing needed is the class name and the icon, we don't need to parse the hole file.
+ // It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script.
+ if (!parse_body) {
+ return;
+ }
+
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
parse_class_body(true);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 96358165c0..21942222cf 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1329,6 +1329,7 @@ private:
bool _is_tool = false;
String script_path;
bool for_completion = false;
+ bool parse_body = true;
bool panic_mode = false;
bool can_break = false;
bool can_continue = false;
@@ -1560,7 +1561,7 @@ private:
#endif // TOOLS_ENABLED
public:
- Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion);
+ Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body = true);
Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path);
ClassNode *get_tree() const { return head; }
bool is_tool() const { return _is_tool; }
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 611a9ad2d9..e8fb1d94b3 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const {
case CONFUSABLE_LOCAL_USAGE:
CHECK_SYMBOLS(1);
return vformat(R"(The identifier "%s" will be shadowed below in the block.)", symbols[0]);
+ case CONFUSABLE_CAPTURE_REASSIGNMENT:
+ CHECK_SYMBOLS(1);
+ return vformat(R"(Reassigning lambda capture does not modify the outer local variable "%s".)", symbols[0]);
case INFERENCE_ON_VARIANT:
CHECK_SYMBOLS(1);
return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]);
@@ -231,6 +234,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"CONFUSABLE_IDENTIFIER",
"CONFUSABLE_LOCAL_DECLARATION",
"CONFUSABLE_LOCAL_USAGE",
+ "CONFUSABLE_CAPTURE_REASSIGNMENT",
"INFERENCE_ON_VARIANT",
"NATIVE_METHOD_OVERRIDE",
"GET_NODE_DEFAULT_WITHOUT_ONREADY",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 3ad9488138..1c806bb4e2 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -85,6 +85,7 @@ public:
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
CONFUSABLE_LOCAL_DECLARATION, // The parent block declares an identifier with the same name below.
CONFUSABLE_LOCAL_USAGE, // The identifier will be shadowed below in the block.
+ CONFUSABLE_CAPTURE_REASSIGNMENT, // Reassigning lambda capture does not modify the outer local variable.
INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant.
NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended.
GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation.
@@ -137,6 +138,7 @@ public:
WARN, // CONFUSABLE_IDENTIFIER
WARN, // CONFUSABLE_LOCAL_DECLARATION
WARN, // CONFUSABLE_LOCAL_USAGE
+ WARN, // CONFUSABLE_CAPTURE_REASSIGNMENT
ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type.
ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected.
ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd
new file mode 100644
index 0000000000..9e1041db54
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.gd
@@ -0,0 +1,23 @@
+var member := 1
+
+func test():
+ var number := 1
+ var string := "1"
+ var vector := Vector2i(1, 0)
+ var array_assign := [1]
+ var array_index := [1]
+ var dictionary := { x = 0 }
+
+ var lambda := func ():
+ member = 2 # Member variable, not captured.
+ number = 2 # Local variable, captured.
+ string += "2" # Test compound assignment operator.
+ vector.x = 2 # Test subscript assignment.
+ array_assign = [2] # Pass-by-reference type, reassignment.
+ array_index[0] = 2 # Pass-by-reference type, index access.
+ dictionary.x = 2 # Pass-by-reference type, attribute access.
+
+ prints("lambda", member, number, string, vector, array_assign, array_index, dictionary)
+
+ lambda.call()
+ prints("outer", member, number, string, vector, array_assign, array_index, dictionary)
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out
new file mode 100644
index 0000000000..8d953818eb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out
@@ -0,0 +1,19 @@
+GDTEST_OK
+>> WARNING
+>> Line: 13
+>> CONFUSABLE_CAPTURE_REASSIGNMENT
+>> Reassigning lambda capture does not modify the outer local variable "number".
+>> WARNING
+>> Line: 14
+>> CONFUSABLE_CAPTURE_REASSIGNMENT
+>> Reassigning lambda capture does not modify the outer local variable "string".
+>> WARNING
+>> Line: 15
+>> CONFUSABLE_CAPTURE_REASSIGNMENT
+>> Reassigning lambda capture does not modify the outer local variable "vector".
+>> WARNING
+>> Line: 16
+>> CONFUSABLE_CAPTURE_REASSIGNMENT
+>> Reassigning lambda capture does not modify the outer local variable "array_assign".
+lambda 2 2 12 (2, 0) [2] [2] { "x": 2 }
+outer 2 1 1 (1, 0) [1] [2] { "x": 2 }
diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
index 46b6856d22..c3a42288c7 100644
--- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
+++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd
@@ -9,6 +9,7 @@ func four_parameters(_a, callable : Callable, b=func(): print(10)):
func test():
var v
+ @warning_ignore("confusable_capture_reassignment")
v=func():v=1
if true: v=1
print(v)
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 32fe37d9af..e0bdd4cf33 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -5785,15 +5785,17 @@ struct SceneFormatImporterGLTFInterpolate {
return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
}
- T bezier(T start, T control_1, T control_2, T end, float t) {
- /* Formula from Wikipedia article on Bezier curves. */
- const real_t omt = (1.0 - t);
- const real_t omt2 = omt * omt;
- const real_t omt3 = omt2 * omt;
+ T hermite(T start, T tan_start, T end, T tan_end, float t) {
+ /* Formula from the glTF 2.0 specification. */
const real_t t2 = t * t;
const real_t t3 = t2 * t;
- return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+ const real_t h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
+ const real_t h10 = t3 - 2.0 * t2 + t;
+ const real_t h01 = -2.0 * t3 + 3.0 * t2;
+ const real_t h11 = t3 - t2;
+
+ return start * h00 + tan_start * h10 + end * h01 + tan_end * h11;
}
};
@@ -5814,7 +5816,7 @@ struct SceneFormatImporterGLTFInterpolate<Quaternion> {
return p1.slerp(p2, c).normalized();
}
- Quaternion bezier(const Quaternion start, const Quaternion control_1, const Quaternion control_2, const Quaternion end, const float t) {
+ Quaternion hermite(const Quaternion start, const Quaternion tan_start, const Quaternion end, const Quaternion tan_end, const float t) {
ERR_FAIL_COND_V_MSG(!start.is_normalized(), Quaternion(), vformat("The start quaternion %s must be normalized.", start));
ERR_FAIL_COND_V_MSG(!end.is_normalized(), Quaternion(), vformat("The end quaternion %s must be normalized.", end));
@@ -5879,14 +5881,15 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
return p_values[(p_times.size() - 1) * 3 + 1];
}
- const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]);
+ const float td = (p_times[idx + 1] - p_times[idx]);
+ const float c = (p_time - p_times[idx]) / td;
const T &from = p_values[idx * 3 + 1];
- const T c1 = from + p_values[idx * 3 + 2];
+ const T tan_from = td * p_values[idx * 3 + 2];
const T &to = p_values[idx * 3 + 4];
- const T c2 = to + p_values[idx * 3 + 3];
+ const T tan_to = td * p_values[idx * 3 + 3];
- return interp.bezier(from, c1, c2, to, c);
+ return interp.hermite(from, tan_from, to, tan_to, c);
} break;
}
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 8a7da64eb5..f917c988ea 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -644,6 +644,26 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector
}
}
}
+ // 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;
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index 541e369925..04edde8300 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -902,6 +902,47 @@ bool OpenXRAPI::setup_play_space() {
new_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
will_emulate_local_floor = true;
+
+ if (local_floor_emulation.local_space == XR_NULL_HANDLE) {
+ XrReferenceSpaceCreateInfo create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
+ identityPose, // poseInReferenceSpace
+ };
+
+ XrResult result = xrCreateReferenceSpace(session, &create_info, &local_floor_emulation.local_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ will_emulate_local_floor = false;
+ }
+ }
+
+ if (local_floor_emulation.stage_space == XR_NULL_HANDLE) {
+ XrReferenceSpaceCreateInfo create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_STAGE, // referenceSpaceType
+ identityPose, // poseInReferenceSpace
+ };
+
+ XrResult result = xrCreateReferenceSpace(session, &create_info, &local_floor_emulation.stage_space);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
+ will_emulate_local_floor = false;
+ }
+ }
+
+ if (!will_emulate_local_floor) {
+ if (local_floor_emulation.local_space != XR_NULL_HANDLE) {
+ xrDestroySpace(local_floor_emulation.local_space);
+ local_floor_emulation.local_space = XR_NULL_HANDLE;
+ }
+ if (local_floor_emulation.stage_space != XR_NULL_HANDLE) {
+ xrDestroySpace(local_floor_emulation.stage_space);
+ local_floor_emulation.stage_space = XR_NULL_HANDLE;
+ }
+ }
} else {
// Fallback on LOCAL, which all OpenXR runtimes are required to support.
print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space."));
@@ -931,16 +972,11 @@ bool OpenXRAPI::setup_play_space() {
play_space = new_play_space;
reference_space = new_reference_space;
- emulating_local_floor = will_emulate_local_floor;
- if (emulating_local_floor) {
- // We'll use the STAGE space to get the floor height, but we can't do that until
- // after xrWaitFrame(), so just set this flag for now.
- // Render state will be updated then.
- should_reset_emulated_floor_height = true;
- } else {
- // Update render state so this play space is used rendering the upcoming frame.
- set_render_play_space(play_space);
- }
+ local_floor_emulation.enabled = will_emulate_local_floor;
+ local_floor_emulation.should_reset_floor_height = will_emulate_local_floor;
+
+ // Update render state so this play space is used rendering the upcoming frame.
+ set_render_play_space(play_space);
return true;
}
@@ -975,63 +1011,39 @@ bool OpenXRAPI::setup_view_space() {
}
bool OpenXRAPI::reset_emulated_floor_height() {
- ERR_FAIL_COND_V(!emulating_local_floor, false);
-
- // This is based on the example code in the OpenXR spec which shows how to
- // emulate LOCAL_FLOOR if it's not supported.
- // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor
+ ERR_FAIL_COND_V(!local_floor_emulation.enabled, false);
+ ERR_FAIL_COND_V(local_floor_emulation.local_space == XR_NULL_HANDLE, false);
+ ERR_FAIL_COND_V(local_floor_emulation.stage_space == XR_NULL_HANDLE, false);
XrResult result;
- XrPosef identityPose = {
- { 0.0, 0.0, 0.0, 1.0 },
- { 0.0, 0.0, 0.0 }
- };
-
- XrSpace local_space = XR_NULL_HANDLE;
- XrSpace stage_space = XR_NULL_HANDLE;
-
- XrReferenceSpaceCreateInfo create_info = {
- XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
- nullptr, // next
- XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
- identityPose, // poseInReferenceSpace
- };
-
- result = xrCreateReferenceSpace(session, &create_info, &local_space);
- if (XR_FAILED(result)) {
- print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
- return false;
- }
-
- create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
- result = xrCreateReferenceSpace(session, &create_info, &stage_space);
- if (XR_FAILED(result)) {
- print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
- xrDestroySpace(local_space);
- return false;
- }
-
XrSpaceLocation stage_location = {
XR_TYPE_SPACE_LOCATION, // type
nullptr, // next
0, // locationFlags
- identityPose, // pose
+ { { 0.0, 0.0, 0.0, 1.0 }, { 0.0, 0.0, 0.0 } }, // pose
};
- result = xrLocateSpace(stage_space, local_space, get_predicted_display_time(), &stage_location);
-
- xrDestroySpace(local_space);
- xrDestroySpace(stage_space);
+ result = xrLocateSpace(local_floor_emulation.stage_space, local_floor_emulation.local_space, get_predicted_display_time(), &stage_location);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
return false;
}
+ XrPosef pose = {
+ { 0.0, 0.0, 0.0, 1.0 },
+ { 0.0, stage_location.pose.position.y, 0.0 }
+ };
+
+ XrReferenceSpaceCreateInfo create_info = {
+ XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
+ nullptr, // next
+ XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType
+ pose, // poseInReferenceSpace
+ };
+
XrSpace new_play_space;
- create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
- create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y;
result = xrCreateReferenceSpace(session, &create_info, &new_play_space);
if (XR_FAILED(result)) {
print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]");
@@ -1275,6 +1287,16 @@ void OpenXRAPI::destroy_session() {
xrDestroySpace(view_space);
view_space = XR_NULL_HANDLE;
}
+ if (local_floor_emulation.local_space != XR_NULL_HANDLE) {
+ xrDestroySpace(local_floor_emulation.local_space);
+ local_floor_emulation.local_space = XR_NULL_HANDLE;
+ }
+ if (local_floor_emulation.stage_space != XR_NULL_HANDLE) {
+ xrDestroySpace(local_floor_emulation.stage_space);
+ local_floor_emulation.stage_space = XR_NULL_HANDLE;
+ }
+ local_floor_emulation.enabled = false;
+ local_floor_emulation.should_reset_floor_height = false;
if (supported_reference_spaces != nullptr) {
// free previous results
@@ -1953,8 +1975,8 @@ bool OpenXRAPI::poll_events() {
XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
- if (emulating_local_floor) {
- should_reset_emulated_floor_height = true;
+ if (local_floor_emulation.enabled) {
+ local_floor_emulation.should_reset_floor_height = true;
}
if (event->poseValid && xr_interface) {
xr_interface->on_pose_recentered();
@@ -2097,16 +2119,18 @@ bool OpenXRAPI::process() {
set_render_display_info(frame_state.predictedDisplayTime, frame_state.shouldRender);
+ // This is before setup_play_space() to ensure that it happens on the frame after
+ // the play space has been created.
+ if (unlikely(local_floor_emulation.should_reset_floor_height && !play_space_is_dirty)) {
+ reset_emulated_floor_height();
+ local_floor_emulation.should_reset_floor_height = false;
+ }
+
if (unlikely(play_space_is_dirty)) {
setup_play_space();
play_space_is_dirty = false;
}
- if (unlikely(should_reset_emulated_floor_height)) {
- reset_emulated_floor_height();
- should_reset_emulated_floor_height = false;
- }
-
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_process();
}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index f9d2e60148..748ef3af94 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -165,8 +165,16 @@ private:
XrSpace view_space = XR_NULL_HANDLE;
XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
- bool emulating_local_floor = false;
- bool should_reset_emulated_floor_height = false;
+ // When LOCAL_FLOOR isn't supported, we use an approach based on the example code in the
+ // OpenXR spec in order to emulate it.
+ // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor
+ struct LocalFloorEmulation {
+ bool enabled = false;
+ XrSpace local_space = XR_NULL_HANDLE;
+ XrSpace stage_space = XR_NULL_HANDLE;
+ bool should_reset_floor_height = false;
+ } local_floor_emulation;
+
bool reset_emulated_floor_height();
bool load_layer_properties();
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 288c37be29..06b304dcde 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -607,6 +607,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
return;
}
@@ -627,6 +628,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
ERR_PRINT(vformat("Failed to create %s window.", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
return;
}
@@ -635,7 +637,13 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+ rendering_device = nullptr;
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 40068745d6..8493a8e932 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -67,6 +67,13 @@ static AndroidInputHandler *input_handler = nullptr;
static GodotJavaWrapper *godot_java = nullptr;
static GodotIOJavaWrapper *godot_io_java = nullptr;
+enum StartupStep {
+ STEP_TERMINATED = -1,
+ STEP_SETUP,
+ STEP_SHOW_LOGO,
+ STEP_STARTED
+};
+
static SafeNumeric<int> step; // Shared between UI and render threads
static Size2 new_size;
@@ -76,7 +83,7 @@ static Vector3 magnetometer;
static Vector3 gyroscope;
static void _terminate(JNIEnv *env, bool p_restart = false) {
- step.set(-1); // Ensure no further steps are attempted and no further events are sent
+ step.set(STEP_TERMINATED); // Ensure no further steps are attempted and no further events are sent
// lets cleanup
// Unregister android plugins
@@ -203,7 +210,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
os_android->set_display_size(Size2i(p_width, p_height));
// No need to reset the surface during startup
- if (step.get() > 0) {
+ if (step.get() > STEP_SETUP) {
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
os_android->set_native_window(native_window);
@@ -216,7 +223,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface) {
if (os_android) {
- if (step.get() == 0) {
+ if (step.get() == STEP_SETUP) {
// During startup
if (p_surface) {
ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface);
@@ -230,7 +237,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) {
- if (step.get() == 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -244,20 +251,26 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *e
}
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
- if (step.get() == -1) {
+ if (step.get() == STEP_TERMINATED) {
return true;
}
- if (step.get() == 0) {
+ if (step.get() == STEP_SETUP) {
// Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id,
// but for Godot purposes, the main thread is the one running the game loop
- Main::setup2();
+ Main::setup2(false); // The logo is shown in the next frame otherwise we run into rendering issues
input_handler = new AndroidInputHandler();
step.increment();
return true;
}
- if (step.get() == 1) {
+ if (step.get() == STEP_SHOW_LOGO) {
+ Main::setup_boot_logo();
+ step.increment();
+ return true;
+ }
+
+ if (step.get() == STEP_STARTED) {
if (Main::start() != EXIT_SUCCESS) {
return true; // should exit instead and print the error
}
@@ -283,7 +296,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JNIEnv *env, jclass clazz, jint p_event_type, jint p_button_mask, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y, jboolean p_double_click, jboolean p_source_mouse_relative, jfloat p_pressure, jfloat p_tilt_x, jfloat p_tilt_y) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -292,7 +305,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchMouseEvent(JN
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JNIEnv *env, jclass clazz, jint ev, jint pointer, jint pointer_count, jfloatArray position, jboolean p_double_tap) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -313,7 +326,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_dispatchTouchEvent(JN
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_factor) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
input_handler->process_magnify(Point2(p_x, p_y), p_factor);
@@ -321,7 +334,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_magnify(JNIEnv *env,
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jclass clazz, jfloat p_x, jfloat p_y, jfloat p_delta_x, jfloat p_delta_y) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
input_handler->process_pan(Point2(p_x, p_y), Vector2(p_delta_x, p_delta_y));
@@ -329,7 +342,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_pan(JNIEnv *env, jcla
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -344,7 +357,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -359,7 +372,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env,
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -396,7 +409,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(
// Called on the UI thread
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_physical_keycode, jint p_unicode, jint p_key_label, jboolean p_pressed, jboolean p_echo) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
input_handler->process_key_event(p_physical_keycode, p_unicode, p_key_label, p_pressed, p_echo);
@@ -419,7 +432,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -427,7 +440,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env,
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -516,7 +529,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
@@ -528,7 +541,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) {
- if (step.get() <= 0) {
+ if (step.get() <= STEP_SETUP) {
return;
}
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index a454dd5ba0..802fbefc0d 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -91,6 +91,7 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver));
memdelete(rendering_context);
rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
return;
}
@@ -107,7 +108,13 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode);
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+ rendering_device = nullptr;
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 3fad8c2987..adc9beed66 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -1111,6 +1111,28 @@ Key DisplayServerWayland::keyboard_get_keycode_from_physical(Key p_keycode) cons
return key;
}
+void DisplayServerWayland::try_suspend() {
+ // Due to various reasons, we manually handle display synchronization by
+ // waiting for a frame event (request to draw) or, if available, the actual
+ // window's suspend status. When a window is suspended, we can avoid drawing
+ // altogether, either because the compositor told us that we don't need to or
+ // because the pace of the frame events became unreliable.
+ if (emulate_vsync) {
+ bool frame = wayland_thread.wait_frame_suspend_ms(1000);
+ if (!frame) {
+ suspended = true;
+ }
+ } else {
+ if (wayland_thread.is_suspended()) {
+ suspended = true;
+ }
+ }
+
+ if (suspended) {
+ DEBUG_LOG_WAYLAND("Window suspended.");
+ }
+}
+
void DisplayServerWayland::process_events() {
wayland_thread.mutex.lock();
@@ -1193,30 +1215,32 @@ void DisplayServerWayland::process_events() {
wayland_thread.keyboard_echo_keys();
if (!suspended) {
- if (emulate_vsync) {
- // Due to various reasons, we manually handle display synchronization by
- // waiting for a frame event (request to draw) or, if available, the actual
- // window's suspend status. When a window is suspended, we can avoid drawing
- // altogether, either because the compositor told us that we don't need to or
- // because the pace of the frame events became unreliable.
- bool frame = wayland_thread.wait_frame_suspend_ms(1000);
- if (!frame) {
- suspended = true;
+ // Due to the way legacy suspension works, we have to treat low processor
+ // usage mode very differently than the regular one.
+ if (OS::get_singleton()->is_in_low_processor_usage_mode()) {
+ // NOTE: We must avoid committing a surface if we expect a new frame, as we
+ // might otherwise commit some inconsistent data (e.g. buffer scale). Note
+ // that if a new frame is expected it's going to be committed by the renderer
+ // soon anyways.
+ if (!RenderingServer::get_singleton()->has_changed()) {
+ // We _can't_ commit in a different thread (such as in the frame callback
+ // itself) because we would risk to step on the renderer's feet, which would
+ // cause subtle but severe issues, such as crashes on setups with explicit
+ // sync. This isn't normally a problem, as the renderer commits at every
+ // frame (which is what we need for atomic surface updates anyways), but in
+ // low processor usage mode that expectation is broken. When it's on, our
+ // frame rate stops being constant. This also reflects in the frame
+ // information we use for legacy suspension. In order to avoid issues, let's
+ // manually commit all surfaces, so that we can get fresh frame data.
+ wayland_thread.commit_surfaces();
+ try_suspend();
}
} else {
- if (wayland_thread.is_suspended()) {
- suspended = true;
- }
- }
-
- if (suspended) {
- DEBUG_LOG_WAYLAND("Window suspended.");
- }
- } else {
- if (wayland_thread.get_reset_frame()) {
- // At last, a sign of life! We're no longer suspended.
- suspended = false;
+ try_suspend();
}
+ } else if (wayland_thread.get_reset_frame()) {
+ // At last, a sign of life! We're no longer suspended.
+ suspended = false;
}
#ifdef DBUS_ENABLED
@@ -1445,7 +1469,14 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
#ifdef RD_ENABLED
if (rendering_context) {
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+ memdelete(rendering_device);
+ rendering_device = nullptr;
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h
index f02640a12e..e611533664 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.h
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -154,6 +154,8 @@ class DisplayServerWayland : public DisplayServer {
virtual void _show_window();
+ void try_suspend();
+
public:
virtual bool has_feature(Feature p_feature) const override;
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index 63a8db07df..341cc517e3 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -968,7 +968,6 @@ void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *w
ws->frame_callback = wl_surface_frame(ws->wl_surface),
wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
- wl_surface_commit(ws->wl_surface);
if (ws->wl_surface && ws->buffer_scale_changed) {
// NOTE: We're only now setting the buffer scale as the idea is to get this
@@ -980,11 +979,6 @@ void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *w
// rendering if needed.
wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));
}
-
- // NOTE: Remember to set here also other buffer-dependent states (e.g. opaque
- // region) if used, to be as close as possible to an atomic surface update.
- // Ideally we'd only have one surface commit, but it's not really doable given
- // the current state of things.
}
void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
@@ -3241,10 +3235,6 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid
ws.frame_callback = wl_surface_frame(ws.wl_surface);
wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
- // NOTE: This commit is only called once to start the whole frame callback
- // "loop".
- wl_surface_commit(ws.wl_surface);
-
if (registry.xdg_exporter) {
ws.xdg_exported = zxdg_exporter_v1_export(registry.xdg_exporter, ws.wl_surface);
zxdg_exported_v1_add_listener(ws.xdg_exported, &xdg_exported_listener, &ws);
@@ -4120,6 +4110,10 @@ void WaylandThread::primary_set_text(const String &p_text) {
wl_display_roundtrip(wl_display);
}
+void WaylandThread::commit_surfaces() {
+ wl_surface_commit(main_window.wl_surface);
+}
+
void WaylandThread::set_frame() {
frame = true;
}
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
index 75d03181e2..775ca71346 100644
--- a/platform/linuxbsd/wayland/wayland_thread.h
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -992,6 +992,8 @@ public:
void primary_set_text(const String &p_text);
+ void commit_surfaces();
+
void set_frame();
bool get_reset_frame();
bool wait_frame_suspend_ms(int p_timeout);
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index a605e664ce..edf3a40ccb 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -3040,7 +3040,7 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win
XWindowAttributes xwa;
XSync(x11_display, False);
XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
- if (xwa.map_state == IsViewable && _window_focus_check()) {
+ if (xwa.map_state == IsViewable) {
_set_input_focus(wd.x11_xim_window, RevertToParent);
}
XSetICFocus(wd.xic);
@@ -6262,7 +6262,14 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+ memdelete(rendering_device);
+ rendering_device = nullptr;
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
diff --git a/platform/web/SCsub b/platform/web/SCsub
index 3d36a888d6..e81f2ec516 100644
--- a/platform/web/SCsub
+++ b/platform/web/SCsub
@@ -71,8 +71,6 @@ if env["dlink_enabled"]:
sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"])
sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"])
sys_env.Append(LINKFLAGS=["-s", "WARN_ON_UNDEFINED_SYMBOLS=0"])
- # Force exporting the standard library (printf, malloc, etc.)
- sys_env["ENV"]["EMCC_FORCE_STDLIBS"] = "libc,libc++,libc++abi"
sys_env["CCFLAGS"].remove("-fvisibility=hidden")
sys_env["LINKFLAGS"].remove("-fvisibility=hidden")
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index 4bca13d2d6..531dbdaeab 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -142,7 +142,7 @@ class Sample {
* @returns {void}
*/
clear() {
- this.audioBuffer = null;
+ this.setAudioBuffer(null);
GodotAudio.Sample.delete(this.id);
}
@@ -432,7 +432,7 @@ class SampleNode {
/** @type {number} */
this._playbackRate = 44100;
/** @type {LoopMode} */
- this.loopMode = 'disabled';
+ this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
/** @type {number} */
this._pitchScale = 1;
/** @type {number} */
@@ -445,7 +445,6 @@ class SampleNode {
this._onended = null;
this.setPlaybackRate(options.playbackRate ?? 44100);
- this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
this._source.buffer = this.getSample().getAudioBuffer();
this._addEndedListener();
@@ -777,8 +776,7 @@ class Bus {
*/
static move(fromIndex, toIndex) {
const movedBus = GodotAudio.Bus.getBus(fromIndex);
- let buses = GodotAudio.buses;
- buses = buses.filter((_, i) => i !== fromIndex);
+ const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
// Inserts at index.
buses.splice(toIndex - 1, 0, movedBus);
GodotAudio.buses = buses;
@@ -1369,7 +1367,7 @@ const _GodotAudio = {
*/
set_sample_bus_volume_db: function (busIndex, volumeDb) {
const bus = GodotAudio.Bus.getBus(busIndex);
- bus.volumeDb = volumeDb;
+ bus.setVolumeDb(volumeDb);
},
/**
diff --git a/platform/web/js/libs/library_godot_javascript_singleton.js b/platform/web/js/libs/library_godot_javascript_singleton.js
index b17fde1544..6bb69bca95 100644
--- a/platform/web/js/libs/library_godot_javascript_singleton.js
+++ b/platform/web/js/libs/library_godot_javascript_singleton.js
@@ -81,11 +81,16 @@ const GodotJSWrapper = {
case 0:
return null;
case 1:
- return !!GodotRuntime.getHeapValue(val, 'i64');
- case 2:
- return GodotRuntime.getHeapValue(val, 'i64');
+ return Boolean(GodotRuntime.getHeapValue(val, 'i64'));
+ case 2: {
+ // `heap_value` may be a bigint.
+ const heap_value = GodotRuntime.getHeapValue(val, 'i64');
+ return heap_value >= Number.MIN_SAFE_INTEGER && heap_value <= Number.MAX_SAFE_INTEGER
+ ? Number(heap_value)
+ : heap_value;
+ }
case 3:
- return GodotRuntime.getHeapValue(val, 'double');
+ return Number(GodotRuntime.getHeapValue(val, 'double'));
case 4:
return GodotRuntime.parseString(GodotRuntime.getHeapValue(val, '*'));
case 24: // OBJECT
@@ -110,6 +115,9 @@ const GodotJSWrapper = {
}
GodotRuntime.setHeapValue(p_exchange, p_val, 'double');
return 3; // FLOAT
+ } else if (type === 'bigint') {
+ GodotRuntime.setHeapValue(p_exchange, p_val, 'i64');
+ return 2; // INT
} else if (type === 'string') {
const c_str = GodotRuntime.allocString(p_val);
GodotRuntime.setHeapValue(p_exchange, c_str, '*');
diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp
index 04513f6d57..eb61644066 100644
--- a/platform/web/web_main.cpp
+++ b/platform/web/web_main.cpp
@@ -35,6 +35,7 @@
#include "core/config/engine.h"
#include "core/io/resource_loader.h"
#include "main/main.h"
+#include "scene/main/scene_tree.h"
#include <emscripten/emscripten.h>
#include <stdlib.h>
@@ -130,7 +131,7 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
PackedStringArray ps;
ps.push_back("/tmp/preload.zip");
- os->get_main_loop()->emit_signal(SNAME("files_dropped"), ps, -1);
+ SceneTree::get_singleton()->get_root()->emit_signal(SNAME("files_dropped"), ps);
}
#endif
emscripten_set_main_loop(main_loop_callback, -1, false);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 0ad84240e4..fee306a25c 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -142,8 +142,9 @@ def detect_build_env_arch():
if os.getenv("VCTOOLSINSTALLDIR"):
host_path_index = os.getenv("PATH").upper().find(os.getenv("VCTOOLSINSTALLDIR").upper() + "BIN\\HOST")
if host_path_index > -1:
- first_path_arch = os.getenv("PATH").split(";")[0].rsplit("\\", 1)[-1].lower()
- return msvc_target_aliases[first_path_arch]
+ first_path_arch = os.getenv("PATH")[host_path_index:].split(";")[0].rsplit("\\", 1)[-1].lower()
+ if first_path_arch in msvc_target_aliases.keys():
+ return msvc_target_aliases[first_path_arch]
msys_target_aliases = {
"mingw32": "x86_32",
@@ -499,6 +500,14 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["arch"] == "x86_64":
env.AppendUnique(CPPDEFINES=["_WIN64"])
+ # Sanitizers
+ prebuilt_lib_extra_suffix = ""
+ if env["use_asan"]:
+ env.extra_suffix += ".san"
+ prebuilt_lib_extra_suffix = ".san"
+ env.Append(CCFLAGS=["/fsanitize=address"])
+ env.Append(LINKFLAGS=["/INFERASANLIBS"])
+
## Libs
LIBS = [
@@ -566,7 +575,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
LIBS += ["WinPixEventRuntime"]
env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
- LIBS += ["libNIR.windows." + env["arch"]]
+ LIBS += ["libNIR.windows." + env["arch"] + prebuilt_lib_extra_suffix]
if env["opengl3"]:
env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
@@ -574,9 +583,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.AppendUnique(CPPDEFINES=["EGL_STATIC"])
env.Append(LIBPATH=[env["angle_libs"]])
LIBS += [
- "libANGLE.windows." + env["arch"],
- "libEGL.windows." + env["arch"],
- "libGLES.windows." + env["arch"],
+ "libANGLE.windows." + env["arch"] + prebuilt_lib_extra_suffix,
+ "libEGL.windows." + env["arch"] + prebuilt_lib_extra_suffix,
+ "libGLES.windows." + env["arch"] + prebuilt_lib_extra_suffix,
]
LIBS += ["dxgi", "d3d9", "d3d11"]
env.Prepend(CPPPATH=["#thirdparty/angle/include"])
@@ -612,12 +621,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")])
env.Append(LIBPATH=[p for p in str(os.getenv("LIB")).split(";")])
- # Sanitizers
- if env["use_asan"]:
- env.extra_suffix += ".san"
- env.Append(LINKFLAGS=["/INFERASANLIBS"])
- env.Append(CCFLAGS=["/fsanitize=address"])
-
# Incremental linking fix
env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"]
env["BUILDERS"]["Program"] = methods.precious_program
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 838beddac2..8d26a705a9 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -5873,7 +5873,14 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
#if defined(RD_ENABLED)
if (rendering_context) {
rendering_device = memnew(RenderingDevice);
- rendering_device->initialize(rendering_context, MAIN_WINDOW_ID);
+ if (rendering_device->initialize(rendering_context, MAIN_WINDOW_ID) != OK) {
+ memdelete(rendering_device);
+ rendering_device = nullptr;
+ memdelete(rendering_context);
+ rendering_context = nullptr;
+ r_error = ERR_UNAVAILABLE;
+ return;
+ }
rendering_device->screen_create(MAIN_WINDOW_ID);
RendererCompositorRD::make_current();
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 3506a0df2b..b3f735e044 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -267,7 +267,7 @@ void AnimatedSprite2D::_notification(int p_what) {
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- ofs = ofs.round();
+ ofs = (ofs + Point2(0.5, 0.5)).floor();
}
Rect2 dst_rect(ofs, s);
diff --git a/scene/2d/sprite_2d.cpp b/scene/2d/sprite_2d.cpp
index efb5029ac4..2b5c40f212 100644
--- a/scene/2d/sprite_2d.cpp
+++ b/scene/2d/sprite_2d.cpp
@@ -98,7 +98,7 @@ void Sprite2D::_get_rects(Rect2 &r_src_rect, Rect2 &r_dst_rect, bool &r_filter_c
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- dest_offset = dest_offset.round();
+ dest_offset = (dest_offset + Point2(0.5, 0.5)).floor();
}
r_dst_rect = Rect2(dest_offset, frame_size);
@@ -400,7 +400,7 @@ Rect2 Sprite2D::get_rect() const {
}
if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- ofs = ofs.round();
+ ofs = (ofs + Point2(0.5, 0.5)).floor();
}
if (s == Size2(0, 0)) {
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index b6d0a8d73a..437790bb99 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -1887,6 +1887,12 @@ void TileMapLayer::_update_self_texture_repeat(RS::CanvasItemTextureRepeat p_tex
emit_signal(CoreStringName(changed));
}
+#ifdef TOOLS_ENABLED
+bool TileMapLayer::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
+ return tile_set.is_valid() && get_cell_source_id(local_to_map(p_point)) != TileSet::INVALID_SOURCE;
+}
+#endif
+
void TileMapLayer::set_as_tile_map_internal_node(int p_index) {
// Compatibility with TileMap.
ERR_FAIL_NULL(get_parent());
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index b0aaaafe5d..c71f13d7be 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -393,6 +393,10 @@ protected:
virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) override;
public:
+#ifdef TOOLS_ENABLED
+ virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
+#endif
+
// TileMap node.
void set_as_tile_map_internal_node(int p_index);
int get_index_in_tile_map() const {
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 6aade24e4e..4889512037 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -239,9 +239,20 @@ int BoneAttachment3D::get_bone_idx() const {
}
void BoneAttachment3D::set_override_pose(bool p_override) {
+ if (override_pose == p_override) {
+ return;
+ }
+
override_pose = p_override;
set_notify_transform(override_pose);
set_process_internal(override_pose);
+ if (!override_pose && bone_idx >= 0) {
+ Skeleton3D *sk = _get_skeleton3d();
+ if (sk) {
+ sk->reset_bone_pose(bone_idx);
+ }
+ }
+
notify_property_list_changed();
}
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index c74348c2e7..e7626b3c2d 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -691,7 +691,9 @@ bool AnimationMixer::_update_caches() {
track = track_value;
- track_value->init_value = anim->track_get_key_value(i, 0);
+ bool is_value = track_src_type == Animation::TYPE_VALUE;
+
+ track_value->init_value = is_value ? anim->track_get_key_value(i, 0) : (anim->track_get_key_value(i, 0).operator Array())[0];
track_value->init_value.zero();
track_value->is_init = false;
@@ -703,7 +705,7 @@ bool AnimationMixer::_update_caches() {
if (has_reset_anim) {
int rt = reset_anim->find_track(path, track_src_type);
if (rt >= 0) {
- if (track_src_type == Animation::TYPE_VALUE) {
+ if (is_value) {
if (reset_anim->track_get_key_count(rt) > 0) {
track_value->init_value = reset_anim->track_get_key_value(rt, 0);
}
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index f270f32193..508b2c49fa 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -95,9 +95,9 @@ private:
}
bool operator<(const BlendKey &bk) const {
if (from == bk.from) {
- return to < bk.to;
+ return StringName::AlphCompare()(to, bk.to);
} else {
- return from < bk.from;
+ return StringName::AlphCompare()(from, bk.from);
}
}
};
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 4e738216de..dd344121e1 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -530,7 +530,7 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu
}
}
- return _get_largest_stylebox_size() + minsize;
+ return (theme_cache.align_to_largest_stylebox ? _get_largest_stylebox_size() : _get_current_stylebox()->get_minimum_size()) + minsize;
}
void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 33756dc1fd..429fb2e64f 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -782,7 +782,9 @@ Rect2 GraphEdit::_compute_shrinked_frame_rect(const GraphFrame *p_frame) {
return Rect2(p_frame->get_position_offset(), Size2());
}
- min_point -= Size2(autoshrink_margin, autoshrink_margin);
+ const Size2 titlebar_size = p_frame->get_titlebar_size();
+
+ min_point -= Size2(autoshrink_margin, MAX(autoshrink_margin, titlebar_size.y));
max_point += Size2(autoshrink_margin, autoshrink_margin);
return Rect2(min_point, max_point - min_point);
diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp
index 8cd7dbbeb5..e85d007262 100644
--- a/scene/gui/graph_frame.cpp
+++ b/scene/gui/graph_frame.cpp
@@ -262,6 +262,10 @@ HBoxContainer *GraphFrame::get_titlebar_hbox() {
return titlebar_hbox;
}
+Size2 GraphFrame::get_titlebar_size() const {
+ return titlebar_hbox->get_size() + theme_cache.titlebar->get_minimum_size();
+}
+
void GraphFrame::set_drag_margin(int p_margin) {
drag_margin = p_margin;
}
diff --git a/scene/gui/graph_frame.h b/scene/gui/graph_frame.h
index 21346586c8..2af09cf872 100644
--- a/scene/gui/graph_frame.h
+++ b/scene/gui/graph_frame.h
@@ -89,6 +89,7 @@ public:
int get_autoshrink_margin() const;
HBoxContainer *get_titlebar_hbox();
+ Size2 get_titlebar_size() const;
void set_drag_margin(int p_margin);
int get_drag_margin() const;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 416a7fafb6..8ffa0f8c63 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1249,7 +1249,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
- fx_offset = fx_offset.round();
+ fx_offset = (fx_offset + Point2(0.5, 0.5)).floor();
}
Vector2 char_off = char_xform.get_origin();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 233c7200ff..7b682daa83 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -472,7 +472,7 @@ String TextEdit::Text::get_custom_word_separators() const {
}
String TextEdit::Text::get_default_word_separators() const {
- String concat_separators = "´`~$^=+|<>";
+ String concat_separators = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~";
for (char32_t ch = 0x2000; ch <= 0x206F; ++ch) { // General punctuation block.
concat_separators += ch;
}
diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp
index c52f463905..972a832985 100644
--- a/scene/gui/texture_rect.cpp
+++ b/scene/gui/texture_rect.cpp
@@ -30,7 +30,6 @@
#include "texture_rect.h"
-#include "scene/resources/atlas_texture.h"
#include "servers/rendering_server.h"
void TextureRect::_notification(int p_what) {
@@ -92,15 +91,6 @@ void TextureRect::_notification(int p_what) {
} break;
}
- Ref<AtlasTexture> p_atlas = texture;
-
- if (p_atlas.is_valid() && !region.has_area()) {
- Size2 scale_size(size.width / texture->get_width(), size.height / texture->get_height());
-
- offset.width += hflip ? p_atlas->get_margin().get_position().width * scale_size.width * 2 : 0;
- offset.height += vflip ? p_atlas->get_margin().get_position().height * scale_size.height * 2 : 0;
- }
-
size.width *= hflip ? -1.0f : 1.0f;
size.height *= vflip ? -1.0f : 1.0f;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index ae8e2a493d..5bbf8ebff4 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -2907,9 +2907,8 @@ void Node::_duplicate_properties(const Node *p_root, const Node *p_original, Nod
arr[i] = p_copy->get_node_or_null(p_original->get_path_to(property_node));
}
}
- value = arr;
- p_copy->set(name, value);
}
+ p_copy->set(name, arr);
} else {
p_copy->set(name, value);
}
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 254bd38be7..c0ab636adc 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -3189,6 +3189,20 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
+Array Animation::make_default_bezier_key(float p_value) {
+ const double max_width = length / 2.0;
+ Array new_point;
+ new_point.resize(5);
+
+ new_point[0] = p_value;
+ new_point[1] = MAX(-0.25, -max_width);
+ new_point[2] = 0;
+ new_point[3] = MIN(0.25, max_width);
+ new_point[4] = 0;
+
+ return new_point;
+}
+
int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
Track *t = tracks[p_track];
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index e9bfc298a5..cb12b12c0e 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -455,6 +455,7 @@ public:
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
+ Array make_default_bezier_key(float p_value);
int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
void bezier_track_set_key_value(int p_track, int p_index, real_t p_value);
void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, real_t p_balanced_value_time_ratio = 1.0);
diff --git a/scene/resources/atlas_texture.cpp b/scene/resources/atlas_texture.cpp
index ef2f1eb135..28e4186048 100644
--- a/scene/resources/atlas_texture.cpp
+++ b/scene/resources/atlas_texture.cpp
@@ -164,20 +164,13 @@ void AtlasTexture::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile
return;
}
- Rect2 rc = region;
+ Rect2 src_rect = Rect2(0, 0, get_width(), get_height());
- if (rc.size.width == 0) {
- rc.size.width = atlas->get_width();
- }
-
- if (rc.size.height == 0) {
- rc.size.height = atlas->get_height();
+ Rect2 dr;
+ Rect2 src_c;
+ if (get_rect_region(p_rect, src_rect, dr, src_c)) {
+ atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip);
}
-
- Vector2 scale = p_rect.size / (region.size + margin.size);
- Rect2 dr(p_rect.position + margin.position * scale, rc.size * scale);
-
- atlas->draw_rect_region(p_canvas_item, dr, rc, p_modulate, p_transpose, filter_clip);
}
void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const {
@@ -188,9 +181,9 @@ void AtlasTexture::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, cons
Rect2 dr;
Rect2 src_c;
- get_rect_region(p_rect, p_src_rect, dr, src_c);
-
- atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip);
+ if (get_rect_region(p_rect, p_src_rect, dr, src_c)) {
+ atlas->draw_rect_region(p_canvas_item, dr, src_c, p_modulate, p_transpose, filter_clip);
+ }
}
bool AtlasTexture::get_rect_region(const Rect2 &p_rect, const Rect2 &p_src_rect, Rect2 &r_rect, Rect2 &r_src_rect) const {
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index b2567e431b..7d121c9d87 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -324,6 +324,21 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const {
is_uniform_type_compatible = E->get().type == cached.get_type();
}
+#ifndef DISABLE_DEPRECATED
+ // PackedFloat32Array -> PackedVector4Array conversion.
+ if (!is_uniform_type_compatible && E->get().type == Variant::PACKED_VECTOR4_ARRAY && cached.get_type() == Variant::PACKED_FLOAT32_ARRAY) {
+ PackedVector4Array varray;
+ PackedFloat32Array array = (PackedFloat32Array)cached;
+
+ for (int i = 0; i + 3 < array.size(); i += 4) {
+ varray.push_back(Vector4(array[i], array[i + 1], array[i + 2], array[i + 3]));
+ }
+
+ param_cache.insert(E->get().name, varray);
+ is_uniform_type_compatible = true;
+ }
+#endif
+
if (is_uniform_type_compatible && E->get().type == Variant::OBJECT && cached.get_type() == Variant::OBJECT) {
// Check if the Object class (hint string) changed, for example Texture2D sampler to Texture3D.
// Allow inheritance, Texture2D type sampler should also accept CompressedTexture2D.
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index b1742bd5a3..900629f5f8 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -2124,6 +2124,56 @@ void PackedScene::recreate_state() {
#endif
}
+#ifdef TOOLS_ENABLED
+HashSet<StringName> PackedScene::get_scene_groups(const String &p_path) {
+ {
+ Ref<PackedScene> packed_scene = ResourceCache::get_ref(p_path);
+ if (packed_scene.is_valid()) {
+ return packed_scene->get_state()->get_all_groups();
+ }
+ }
+
+ if (p_path.get_extension() == "tscn") {
+ Ref<FileAccess> scene_file = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V(scene_file.is_null(), HashSet<StringName>());
+
+ HashSet<StringName> ret;
+ while (!scene_file->eof_reached()) {
+ const String line = scene_file->get_line();
+ if (!line.begins_with("[node")) {
+ continue;
+ }
+
+ int i = line.find("groups=[");
+ if (i == -1) {
+ continue;
+ }
+
+ int j = line.find_char(']', i);
+ while (i < j) {
+ i = line.find_char('"', i);
+ if (i == -1) {
+ break;
+ }
+
+ int k = line.find_char('"', i + 1);
+ if (k == -1) {
+ break;
+ }
+
+ ret.insert(line.substr(i + 1, k - i - 1));
+ i = k + 1;
+ }
+ }
+ return ret;
+ } else {
+ Ref<PackedScene> packed_scene = ResourceLoader::load(p_path);
+ ERR_FAIL_COND_V(packed_scene.is_null(), HashSet<StringName>());
+ return packed_scene->get_state()->get_all_groups();
+ }
+}
+#endif
+
Ref<SceneState> PackedScene::get_state() const {
return state;
}
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index c46a4dd5fe..e26b9f7b90 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -270,6 +270,7 @@ public:
state->set_last_modified_time(p_time);
}
+ static HashSet<StringName> get_scene_groups(const String &p_path);
#endif
Ref<SceneState> get_state() const;
diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h
index 7e4a517c1d..60422c16cc 100644
--- a/servers/display_server_headless.h
+++ b/servers/display_server_headless.h
@@ -51,7 +51,18 @@ private:
return memnew(DisplayServerHeadless());
}
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event) {
+ static_cast<DisplayServerHeadless *>(get_singleton())->_dispatch_input_event(p_event);
+ }
+
+ void _dispatch_input_event(const Ref<InputEvent> &p_event) {
+ if (input_event_callback.is_valid()) {
+ input_event_callback.call(p_event);
+ }
+ }
+
NativeMenu *native_menu = nullptr;
+ Callable input_event_callback;
public:
bool has_feature(Feature p_feature) const override { return false; }
@@ -86,7 +97,11 @@ public:
void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {}
void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {}
- void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {}
+
+ void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {
+ input_event_callback = p_callable;
+ }
+
void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {}
void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {}
@@ -137,7 +152,9 @@ public:
int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override { return 0; }
- void process_events() override {}
+ void process_events() override {
+ Input::get_singleton()->flush_buffered_events();
+ }
void set_native_icon(const String &p_filename) override {}
void set_icon(const Ref<Image> &p_icon) override {}
@@ -179,7 +196,9 @@ public:
DisplayServerHeadless() {
native_menu = memnew(NativeMenu);
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
}
+
~DisplayServerHeadless() {
if (native_menu) {
memdelete(native_menu);
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index e92050a323..c4286dcc0c 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -284,8 +284,8 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
}
if (snapping_2d_transforms_to_pixel) {
- final_xform.columns[2] = final_xform.columns[2].round();
- parent_xform.columns[2] = parent_xform.columns[2].round();
+ final_xform.columns[2] = (final_xform.columns[2] + Point2(0.5, 0.5)).floor();
+ parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor();
}
final_xform = parent_xform * final_xform;
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index d1c8c71b7f..13b00be1c4 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -1165,8 +1165,8 @@ MaterialStorage::MaterialStorage() {
global_shader_uniforms.buffer_values = memnew_arr(GlobalShaderUniforms::Value, global_shader_uniforms.buffer_size);
memset(global_shader_uniforms.buffer_values, 0, sizeof(GlobalShaderUniforms::Value) * global_shader_uniforms.buffer_size);
global_shader_uniforms.buffer_usage = memnew_arr(GlobalShaderUniforms::ValueUsage, global_shader_uniforms.buffer_size);
- global_shader_uniforms.buffer_dirty_regions = memnew_arr(bool, global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
- memset(global_shader_uniforms.buffer_dirty_regions, 0, sizeof(bool) * global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
+ global_shader_uniforms.buffer_dirty_regions = memnew_arr(bool, 1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE));
+ memset(global_shader_uniforms.buffer_dirty_regions, 0, sizeof(bool) * (1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE)));
global_shader_uniforms.buffer = RD::get_singleton()->storage_buffer_create(sizeof(GlobalShaderUniforms::Value) * global_shader_uniforms.buffer_size);
}
@@ -1769,7 +1769,7 @@ void MaterialStorage::global_shader_parameters_instance_update(RID p_instance, i
void MaterialStorage::_update_global_shader_uniforms() {
MaterialStorage *material_storage = MaterialStorage::get_singleton();
if (global_shader_uniforms.buffer_dirty_region_count > 0) {
- uint32_t total_regions = global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE;
+ uint32_t total_regions = 1 + (global_shader_uniforms.buffer_size / GlobalShaderUniforms::BUFFER_DIRTY_REGION_SIZE);
if (total_regions / global_shader_uniforms.buffer_dirty_region_count <= 4) {
// 25% of regions dirty, just update all buffer
RD::get_singleton()->buffer_update(global_shader_uniforms.buffer, 0, sizeof(GlobalShaderUniforms::Value) * global_shader_uniforms.buffer_size, global_shader_uniforms.buffer_values);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 38f1fe57bd..59f7b3d9e1 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -5507,7 +5507,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
for (uint32_t i = 0; i < frames.size(); i++) {
// Staging was never used, create a block.
err = _insert_staging_block();
- ERR_CONTINUE(err != OK);
+ ERR_FAIL_COND_V(err, FAILED);
}
draw_list = nullptr;
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 1e9690a8ae..f5e0b811a2 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1547,7 +1547,7 @@ bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type
}
DataType na = p_op->arguments[0]->get_datatype();
- valid = na > TYPE_BOOL && na < TYPE_MAT2;
+ valid = na > TYPE_BVEC4 && na < TYPE_MAT2;
ret_type = na;
} break;
case OP_ADD:
@@ -1567,7 +1567,7 @@ bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type
}
if (na == nb) {
- valid = (na > TYPE_BOOL && na <= TYPE_MAT4);
+ valid = (na > TYPE_BVEC4 && na <= TYPE_MAT4);
ret_type = na;
} else if (na == TYPE_INT && nb == TYPE_IVEC2) {
valid = true;
@@ -1776,7 +1776,7 @@ bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type
DataType nb = p_op->arguments[1]->get_datatype();
if (na == nb) {
- valid = (na > TYPE_BOOL && na <= TYPE_MAT4);
+ valid = (na > TYPE_BVEC4 && na <= TYPE_MAT4);
ret_type = na;
} else if (na == TYPE_IVEC2 && nb == TYPE_INT) {
valid = true;
@@ -3982,12 +3982,9 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C
}
value = Variant(array);
} else {
- PackedFloat32Array array;
+ PackedVector4Array array;
for (int i = 0; i < array_size; i += 4) {
- array.push_back(p_value[i].real);
- array.push_back(p_value[i + 1].real);
- array.push_back(p_value[i + 2].real);
- array.push_back(p_value[i + 3].real);
+ array.push_back(Vector4(p_value[i].real, p_value[i + 1].real, p_value[i + 2].real, p_value[i + 3].real));
}
value = Variant(array);
}
@@ -4219,7 +4216,7 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform
if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) {
pi.type = Variant::PACKED_COLOR_ARRAY;
} else {
- pi.type = Variant::PACKED_FLOAT32_ARRAY;
+ pi.type = Variant::PACKED_VECTOR4_ARRAY;
}
} else {
if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) {
@@ -10840,4 +10837,5 @@ ShaderLanguage::ShaderLanguage() {
ShaderLanguage::~ShaderLanguage() {
clear();
+ global_func_set.clear();
}
diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h
index fc3fd6a87d..7bd494ec80 100644
--- a/tests/core/math/test_vector2.h
+++ b/tests/core/math/test_vector2.h
@@ -353,7 +353,6 @@ TEST_CASE("[Vector2] Plane methods") {
const Vector2 vector = Vector2(1.2, 3.4);
const Vector2 vector_y = Vector2(0, 1);
const Vector2 vector_normal = Vector2(0.95879811270838721622267, 0.2840883296913739899919);
- const Vector2 vector_non_normal = Vector2(5.4, 1.6);
const real_t p_d = 99.1;
CHECK_MESSAGE(
vector.bounce(vector_y) == Vector2(1.2, -3.4),
@@ -383,6 +382,8 @@ TEST_CASE("[Vector2] Plane methods") {
vector.slide(vector_normal).is_equal_approx(Vector2(-0.8292559899117276166456, 2.798738965952080706179)),
"Vector2 slide with normal should return expected value.");
// There's probably a better way to test these ones?
+#ifdef MATH_CHECKS
+ const Vector2 vector_non_normal = Vector2(5.4, 1.6);
ERR_PRINT_OFF;
CHECK_MESSAGE(
vector.bounce(vector_non_normal).is_equal_approx(Vector2()),
@@ -394,6 +395,7 @@ TEST_CASE("[Vector2] Plane methods") {
vector.slide(vector_non_normal).is_equal_approx(Vector2()),
"Vector2 slide should return empty Vector2 with non-normalized input.");
ERR_PRINT_ON;
+#endif // MATH_CHECKS
}
TEST_CASE("[Vector2] Rounding methods") {
diff --git a/tests/core/math/test_vector3.h b/tests/core/math/test_vector3.h
index ca0aa02882..4cab753d6f 100644
--- a/tests/core/math/test_vector3.h
+++ b/tests/core/math/test_vector3.h
@@ -368,7 +368,6 @@ TEST_CASE("[Vector3] Plane methods") {
const Vector3 vector = Vector3(1.2, 3.4, 5.6);
const Vector3 vector_y = Vector3(0, 1, 0);
const Vector3 vector_normal = Vector3(0.88763458893247992491, 0.26300284116517923701, 0.37806658417494515320);
- const Vector3 vector_non_normal = Vector3(5.4, 1.6, 2.3);
CHECK_MESSAGE(
vector.bounce(vector_y) == Vector3(1.2, -3.4, 5.6),
"Vector3 bounce on a plane with normal of the Y axis should.");
@@ -394,6 +393,8 @@ TEST_CASE("[Vector3] Plane methods") {
vector.slide(vector_normal).is_equal_approx(Vector3(-2.41848149148878681437, 2.32785733585517427722237, 4.0587949202918130235)),
"Vector3 slide with normal should return expected value.");
// There's probably a better way to test these ones?
+#ifdef MATH_CHECKS
+ const Vector3 vector_non_normal = Vector3(5.4, 1.6, 2.3);
ERR_PRINT_OFF;
CHECK_MESSAGE(
vector.bounce(vector_non_normal).is_equal_approx(Vector3()),
@@ -405,6 +406,7 @@ TEST_CASE("[Vector3] Plane methods") {
vector.slide(vector_non_normal).is_equal_approx(Vector3()),
"Vector3 slide should return empty Vector3 with non-normalized input.");
ERR_PRINT_ON;
+#endif // MATH_CHECKS
}
TEST_CASE("[Vector3] Rounding methods") {
diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h
index 381d759e5b..358bbc08a3 100644
--- a/tests/core/object/test_class_db.h
+++ b/tests/core/object/test_class_db.h
@@ -375,8 +375,10 @@ void validate_property(const Context &p_context, const ExposedClass &p_class, co
}
void validate_argument(const Context &p_context, const ExposedClass &p_class, const String &p_owner_name, const String &p_owner_type, const ArgumentData &p_arg) {
+#ifdef DEBUG_METHODS_ENABLED
TEST_COND((p_arg.name.is_empty() || p_arg.name.begins_with("_unnamed_arg")),
vformat("Unnamed argument in position %d of %s '%s.%s'.", p_arg.position, p_owner_type, p_class.name, p_owner_name));
+#endif // DEBUG_METHODS_ENABLED
const ExposedClass *arg_class = p_context.find_exposed_class(p_arg.type);
if (arg_class) {
diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h
index 6ee0ff82e7..1e2f5e222b 100644
--- a/tests/core/os/test_os.h
+++ b/tests/core/os/test_os.h
@@ -163,12 +163,14 @@ TEST_CASE("[OS] Processor count and memory information") {
CHECK_MESSAGE(
OS::get_singleton()->get_processor_count() >= 1,
"The returned processor count should be greater than zero.");
+#ifdef DEBUG_ENABLED
CHECK_MESSAGE(
OS::get_singleton()->get_static_memory_usage() >= 1,
"The returned static memory usage should be greater than zero.");
CHECK_MESSAGE(
OS::get_singleton()->get_static_memory_peak_usage() >= 1,
"The returned static memory peak usage should be greater than zero.");
+#endif // DEBUG_ENABLED
}
TEST_CASE("[OS] Execute") {
diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h
index e4946995a7..b44ff06b35 100644
--- a/tests/display_server_mock.h
+++ b/tests/display_server_mock.h
@@ -36,7 +36,7 @@
#include "servers/rendering/dummy/rasterizer_dummy.h"
// Specialized DisplayServer for unittests based on DisplayServerHeadless, that
-// additionally supports rudimentary InputEvent handling and mouse position.
+// additionally supports things like mouse enter/exit events and clipboard.
class DisplayServerMock : public DisplayServerHeadless {
private:
friend class DisplayServer;
@@ -45,7 +45,6 @@ private:
CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
bool window_over = false;
Callable event_callback;
- Callable input_event_callback;
String clipboard_text;
String primary_clipboard_text;
@@ -62,16 +61,6 @@ private:
return memnew(DisplayServerMock());
}
- static void _dispatch_input_events(const Ref<InputEvent> &p_event) {
- static_cast<DisplayServerMock *>(get_singleton())->_dispatch_input_event(p_event);
- }
-
- void _dispatch_input_event(const Ref<InputEvent> &p_event) {
- if (input_event_callback.is_valid()) {
- input_event_callback.call(p_event);
- }
- }
-
void _set_mouse_position(const Point2i &p_position) {
if (mouse_position == p_position) {
return;
@@ -153,18 +142,9 @@ public:
event_callback = p_callable;
}
- virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {
- input_event_callback = p_callable;
- }
-
static void register_mock_driver() {
register_create_function("mock", create_func, get_rendering_drivers_func);
}
-
- DisplayServerMock() {
- Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
- }
- ~DisplayServerMock() {}
};
#endif // DISPLAY_SERVER_MOCK_H
diff --git a/tests/scene/test_instance_placeholder.h b/tests/scene/test_instance_placeholder.h
index d915c5d961..17f2151d54 100644
--- a/tests/scene/test_instance_placeholder.h
+++ b/tests/scene/test_instance_placeholder.h
@@ -333,6 +333,7 @@ TEST_CASE("[SceneTree][InstancePlaceholder] Instantiate from placeholder with ov
}
}
+#ifdef TOOLS_ENABLED
TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an InstancePlaceholder with no overrides") {
GDREGISTER_CLASS(_TestInstancePlaceholderNode);
@@ -526,6 +527,7 @@ TEST_CASE("[SceneTree][InstancePlaceholder] Instance a PackedScene containing an
DirAccess::remove_file_or_error(internal_path);
DirAccess::remove_file_or_error(main_path);
}
+#endif // TOOLS_ENABLED
} //namespace TestInstancePlaceholder
diff --git a/tests/scene/test_node.h b/tests/scene/test_node.h
index 05764d8f29..e387c73f9f 100644
--- a/tests/scene/test_node.h
+++ b/tests/scene/test_node.h
@@ -529,6 +529,7 @@ TEST_CASE("[SceneTree][Node]Exported node checks") {
memdelete(dup);
}
+#ifdef TOOLS_ENABLED
SUBCASE("Saving instance with exported nodes should not store the unchanged property") {
Ref<PackedScene> ps;
ps.instantiate();
@@ -602,6 +603,7 @@ TEST_CASE("[SceneTree][Node]Exported node checks") {
}
CHECK_EQ(stored_properties, 2);
}
+#endif // TOOLS_ENABLED
memdelete(node);
}
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 93469f69bb..4572687be2 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
-- Version: 0.13.7 (d2c0428a99f7305c086caffe0c730add601ebd6e, 2024)
+- Version: 0.14.0 (ae4e9d003c93325f1eba64319fa9852a0d764b4c, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS
index da58481032..e00e91a696 100644
--- a/thirdparty/thorvg/AUTHORS
+++ b/thirdparty/thorvg/AUTHORS
@@ -1,10 +1,10 @@
-Hermet Park <hermet@lottiefiles.com>, <chuneon.park@samsung.com>
+Hermet Park <hermet@lottiefiles.com>
Prudhvi Raj Vasireddi <prudhvi.raj@samsung.com>
Junsu Choi <jsuya.choi@samsung.com>
Pranay Samanta <pranay.ks@samsung.com>
Mateusz Palkowski <m.palkowski@samsung.com>
Subhransu Mohanty <sub.mohanty@samsung.com>
-Mira Grudzinska <veleveta@gmail.com>, <m.grudzinska@samsung.com>
+Mira Grudzinska <mira@lottiefiles.com>
Michal Szczecinski <m.szczecinsk@partner.samsung.com>
Shinwoo Kim <cinoo.kim@samsung.com>
Piotr Kalota <p.kalota@samsung.com>
@@ -27,3 +27,7 @@ Jinny You <jinny@lottiefiles.com>
Nattu Adnan <nattu@reallynattu.com>
Gabor Kiss-Vamosi <kisvegabor@gmail.com>
Lorcán Mc Donagh <lorcan@lmdsp.com>
+Lucas Niu <hoiyu3twon9@gmail.com>
+Francisco Ramírez <franchuti688@gmail.com>
+Abdelrahman Ashraf <a.theashraf@gmail.com>
+Neo Xu <neo.xu1990@gmail.com>
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index fc7f3ac36d..6218c18e68 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.13.8"
+#define THORVG_VERSION_STRING "0.14.0"
#endif
diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h
index 8285aa1c4c..0b7e9771b9 100644
--- a/thirdparty/thorvg/inc/thorvg.h
+++ b/thirdparty/thorvg/inc/thorvg.h
@@ -72,6 +72,10 @@ class Animation;
/**
* @brief Enumeration specifying the result from the APIs.
+ *
+ * All ThorVG APIs could potentially return one of the values in the list.
+ * Please note that some APIs may additionally specify the reasons that trigger their return values.
+ *
*/
enum class Result
{
@@ -157,7 +161,7 @@ enum class CompositeMethod
AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value.
InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value.
LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9
- InvLumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the compositing target's pixels.
+ InvLumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the compositing target's pixels. @since 0.11
AddMask, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) (Experimental API)
SubtractMask, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) (Experimental API)
IntersectMask, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) (Experimental API)
@@ -277,8 +281,6 @@ public:
* The rotational axis passes through the point on the object with zero coordinates.
*
* @param[in] degree The value of the angle in degrees.
- *
- * @retval Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result rotate(float degree) noexcept;
@@ -286,8 +288,6 @@ public:
* @brief Sets the scale value of the object.
*
* @param[in] factor The value of the scaling factor. The default value is 1.
- *
- * @retval Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result scale(float factor) noexcept;
@@ -299,8 +299,6 @@ public:
*
* @param[in] x The value of the horizontal shift.
* @param[in] y The value of the vertical shift.
- *
- * @retval Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result translate(float x, float y) noexcept;
@@ -310,8 +308,6 @@ public:
* The augmented matrix of the transformation is expected to be given.
*
* @param[in] m The 3x3 augmented matrix.
- *
- * @retval Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result transform(const Matrix& m) noexcept;
@@ -332,8 +328,6 @@ public:
*
* @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque.
*
- * @retval Result::Success when succeed.
- *
* @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible.
* @note ClipPath won't use the opacity value. (see: enum class CompositeMethod::ClipPath)
*/
@@ -344,8 +338,6 @@ public:
*
* @param[in] target The paint of the target object.
* @param[in] method The method used to composite the source object with the target.
- *
- * @retval Result::Success when succeed, Result::InvalidArguments otherwise.
*/
Result composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept;
@@ -358,24 +350,11 @@ public:
*
* @param[in] method The blending method to be set.
*
- * @retval Result::Success when the blending method is successfully set.
- *
* @note Experimental API
*/
Result blend(BlendMethod method) const noexcept;
/**
- * @brief Gets the bounding box of the paint object before any transformation.
- *
- * @param[out] x The x coordinate of the upper left corner of the object.
- * @param[out] y The y coordinate of the upper left corner of the object.
- * @param[out] w The width of the object.
- * @param[out] h The height of the object.
- *
- * @return Result::Success when succeed, Result::InsufficientCondition otherwise.
- *
- * @note The bounding box doesn't indicate the final rendered region. It's the smallest rectangle that encloses the object.
- * @see Paint::bounds(float* x, float* y, float* w, float* h, bool transformed);
* @deprecated Use bounds(float* x, float* y, float* w, float* h, bool transformed) instead
*/
TVG_DEPRECATED Result bounds(float* x, float* y, float* w, float* h) const noexcept;
@@ -391,8 +370,6 @@ public:
* @param[out] h The height of the object.
* @param[in] transformed If @c true, the paint's transformations are taken into account, otherwise they aren't.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
- *
* @note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object.
*/
Result bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept;
@@ -479,8 +456,6 @@ public:
*
* @param[in] colorStops An array of ColorStop data structure.
* @param[in] cnt The count of the @p colorStops array equal to the colors number used in the gradient.
- *
- * @retval Result::Success when succeed.
*/
Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept;
@@ -488,8 +463,6 @@ public:
* @brief Sets the FillSpread value, which specifies how to fill the area outside the gradient bounds.
*
* @param[in] s The FillSpread value.
- *
- * @retval Result::Success when succeed.
*/
Result spread(FillSpread s) noexcept;
@@ -499,8 +472,6 @@ public:
* The augmented matrix of the transformation is expected to be given.
*
* @param[in] m The 3x3 augmented matrix.
- *
- * @retval Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result transform(const Matrix& m) noexcept;
@@ -567,16 +538,6 @@ public:
Canvas(RenderMethod*);
virtual ~Canvas();
- /**
- * @brief Sets the size of the container, where all the paints pushed into the Canvas are stored.
- *
- * If the number of objects pushed into the Canvas is known in advance, calling the function
- * prevents multiple memory reallocation, thus improving the performance.
- *
- * @param[in] n The number of objects for which the memory is to be reserved.
- *
- * @return Result::Success when succeed.
- */
TVG_DEPRECATED Result reserve(uint32_t n) noexcept;
/**
@@ -599,9 +560,7 @@ public:
*
* @param[in] paint A Paint object to be drawn.
*
- * @retval Result::Success When succeed.
* @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument.
- * @retval Result::InsufficientCondition An internal error.
*
* @note The rendering order of the paints is the same as the order as they were pushed into the canvas. Consider sorting the paints before pushing them if you intend to use layering.
* @see Canvas::paints()
@@ -618,7 +577,6 @@ public:
*
* @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
*
* @see Canvas::push()
* @see Canvas::paints()
@@ -633,8 +591,6 @@ public:
*
* @param[in] paint A pointer to the Paint object or @c nullptr.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
- *
* @note The Update behavior can be asynchronous if the assigned thread number is greater than zero.
*/
virtual Result update(Paint* paint = nullptr) noexcept;
@@ -642,8 +598,6 @@ public:
/**
* @brief Requests the canvas to draw the Paint objects.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
- *
* @note Drawing can be asynchronous if the assigned thread number is greater than zero. To guarantee the drawing is done, call sync() afterwards.
* @see Canvas::sync()
*/
@@ -660,8 +614,6 @@ public:
* @param[in] w The width of the rectangle.
* @param[in] h The height of the rectangle.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
- *
* @see SwCanvas::target()
* @see GlCanvas::target()
* @see WgCanvas::target()
@@ -679,7 +631,6 @@ public:
* The Canvas rendering can be performed asynchronously. To make sure that rendering is finished,
* the sync() must be called after the draw() regardless of threading.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
* @see Canvas::draw()
*/
virtual Result sync() noexcept;
@@ -713,8 +664,6 @@ public:
* @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds.
* @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds.
*
- * @retval Result::Success when succeed.
- *
* @note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered.
*/
Result linear(float x1, float y1, float x2, float y2) noexcept;
@@ -730,8 +679,6 @@ public:
* @param[out] y1 The vertical coordinate of the first point used to determine the gradient bounds.
* @param[out] x2 The horizontal coordinate of the second point used to determine the gradient bounds.
* @param[out] y2 The vertical coordinate of the second point used to determine the gradient bounds.
- *
- * @retval Result::Success when succeed.
*/
Result linear(float* x1, float* y1, float* x2, float* y2) const noexcept;
@@ -775,7 +722,7 @@ public:
* @param[in] cy The vertical coordinate of the center of the bounding circle.
* @param[in] radius The radius of the bounding circle.
*
- * @retval Result::Success when succeed, Result::InvalidArguments in case the @p radius value is zero or less.
+ * @retval Result::InvalidArguments in case the @p radius value is zero or less.
*/
Result radial(float cx, float cy, float radius) noexcept;
@@ -788,7 +735,6 @@ public:
* @param[out] cy The vertical coordinate of the center of the bounding circle.
* @param[out] radius The radius of the bounding circle.
*
- * @retval Result::Success when succeed.
*/
Result radial(float* cx, float* cy, float* radius) const noexcept;
@@ -834,8 +780,6 @@ public:
*
* The transformation matrix, the color, the fill and the stroke properties are retained.
*
- * @retval Result::Success when succeed.
- *
* @note The memory, where the path data is stored, is not deallocated at this stage for caching effect.
*/
Result reset() noexcept;
@@ -847,8 +791,6 @@ public:
*
* @param[in] x The horizontal coordinate of the initial point of the sub-path.
* @param[in] y The vertical coordinate of the initial point of the sub-path.
- *
- * @retval Result::Success when succeed.
*/
Result moveTo(float x, float y) noexcept;
@@ -860,8 +802,6 @@ public:
* @param[in] x The horizontal coordinate of the end-point of the line.
* @param[in] y The vertical coordinate of the end-point of the line.
*
- * @retval Result::Success when succeed.
- *
* @note In case this is the first command in the path, it corresponds to the moveTo() call.
*/
Result lineTo(float x, float y) noexcept;
@@ -879,8 +819,6 @@ public:
* @param[in] x The horizontal coordinate of the end-point of the curve.
* @param[in] y The vertical coordinate of the end-point of the curve.
*
- * @retval Result::Success when succeed.
- *
* @note In case this is the first command in the path, no data from the path are rendered.
*/
Result cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept;
@@ -890,8 +828,6 @@ public:
*
* The value of the current point is set to the initial point of the closed sub-path.
*
- * @retval Result::Success when succeed.
- *
* @note In case the sub-path does not contain any points, this function has no effect.
*/
Result close() noexcept;
@@ -916,8 +852,6 @@ public:
* @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle.
* @param[in] ry The y-axis radius of the ellipse defining the rounded corners of the rectangle.
*
- * @retval Result::Success when succeed.
- *
* @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse.
*/
Result appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0) noexcept;
@@ -936,7 +870,6 @@ public:
* @param[in] rx The x-axis radius of the ellipse.
* @param[in] ry The y-axis radius of the ellipse.
*
- * @retval Result::Success when succeed.
*/
Result appendCircle(float cx, float cy, float rx, float ry) noexcept;
@@ -953,8 +886,6 @@ public:
* @param[in] sweep The central angle of the arc given in degrees, measured counter-clockwise from @p startAngle.
* @param[in] pie Specifies whether to draw radii from the arc's center to both of its end-point - drawn if @c true.
*
- * @retval Result::Success when succeed.
- *
* @note Setting @p sweep value greater than 360 degrees, is equivalent to calling appendCircle(cx, cy, radius, radius).
*/
Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept;
@@ -971,8 +902,6 @@ public:
* @param[in] pts The array of the two-dimensional points.
* @param[in] ptsCnt The number of the points in the @p pts array.
*
- * @retval Result::Success when succeed, Result::InvalidArguments otherwise.
- *
* @note The interface is designed for optimal path setting if the caller has a completed path commands already.
*/
Result appendPath(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept;
@@ -982,7 +911,6 @@ public:
*
* @param[in] width The width of the stroke. The default value is 0.
*
- * @retval Result::Success when succeed.
*/
Result stroke(float width) noexcept;
@@ -994,7 +922,6 @@ public:
* @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0.
* @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0.
*
- * @retval Result::Success when succeed.
*/
Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept;
@@ -1003,8 +930,7 @@ public:
*
* @param[in] f The gradient fill.
*
- * @retval Result::Success When succeed.
- * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument or an error with accessing it.
+ * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument.
*/
Result stroke(std::unique_ptr<Fill> f) noexcept;
@@ -1014,8 +940,6 @@ public:
* @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length.
* @param[in] cnt The length of the @p dashPattern array.
*
- * @retval Result::Success When succeed.
- * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be dashed.
* @retval Result::InvalidArguments In case @p dashPattern is @c nullptr and @p cnt > 0, @p cnt is zero, any of the dash pattern values is zero or less.
*
* @note To reset the stroke dash pattern, pass @c nullptr to @p dashPattern and zero to @p cnt.
@@ -1028,7 +952,6 @@ public:
*
* @param[in] cap The cap style value. The default value is @c StrokeCap::Square.
*
- * @retval Result::Success when succeed.
*/
Result stroke(StrokeCap cap) noexcept;
@@ -1039,7 +962,6 @@ public:
*
* @param[in] join The join style value. The default value is @c StrokeJoin::Bevel.
*
- * @retval Result::Success when succeed.
*/
Result stroke(StrokeJoin join) noexcept;
@@ -1048,7 +970,7 @@ public:
*
* @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4.
*
- * @retval Result::Success when succeed or Result::InvalidArgument for @p miterlimit values less than zero.
+ * @retval Result::InvalidArgument for @p miterlimit values less than zero.
*
* @since 0.11
*/
@@ -1064,8 +986,6 @@ public:
* @param[in] simultaneous Determines how to trim multiple paths within a single shape. If set to @c true (default), trimming is applied simultaneously to all paths;
* Otherwise, all paths are treated as a single entity with a combined length equal to the sum of their individual lengths and are trimmed as such.
*
- * @retval Result::Success when succeed.
- *
* @note Experimental API
*/
Result strokeTrim(float begin, float end, bool simultaneous = true) noexcept;
@@ -1080,8 +1000,6 @@ public:
* @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0.
* @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0.
*
- * @retval Result::Success when succeed.
- *
* @note Either a solid color or a gradient fill is applied, depending on what was set as last.
* @note ClipPath won't use the fill values. (see: enum class CompositeMethod::ClipPath)
*/
@@ -1094,8 +1012,6 @@ public:
*
* @param[in] f The unique pointer to the gradient fill.
*
- * @retval Result::Success when succeed, Result::MemoryCorruption otherwise.
- *
* @note Either a solid color or a gradient fill is applied, depending on what was set as last.
*/
Result fill(std::unique_ptr<Fill> f) noexcept;
@@ -1104,8 +1020,6 @@ public:
* @brief Sets the fill rule for the Shape object.
*
* @param[in] r The fill rule value. The default value is @c FillRule::Winding.
- *
- * @retval Result::Success when succeed.
*/
Result fill(FillRule r) noexcept;
@@ -1114,8 +1028,6 @@ public:
*
* @param[in] strokeFirst If @c true the stroke is rendered before the fill, otherwise the stroke is rendered as the second one (the default option).
*
- * @retval Result::Success when succeed.
- *
* @since 0.10
*/
Result order(bool strokeFirst) noexcept;
@@ -1179,7 +1091,6 @@ public:
* @param[out] b The blue color channel value in the range [0 ~ 255].
* @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
*/
Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept;
@@ -1228,7 +1139,7 @@ public:
* @param[out] begin The starting point of the segment to display along the path.
* @param[out] end Specifies the end of the segment to display along the path.
*
- * @retval @c true if trimming is applied simultaneously to all paths of the shape, @c false otherwise.
+ * @return @c true if trimming is applied simultaneously to all paths of the shape, @c false otherwise.
*
* @note Experimental API
*/
@@ -1277,10 +1188,8 @@ public:
*
* @param[in] path A path to the picture file.
*
- * @retval Result::Success When succeed.
* @retval Result::InvalidArguments In case the @p path is invalid.
* @retval Result::NonSupport When trying to load a file with an unknown extension.
- * @retval Result::Unknown If an error occurs at a later stage.
*
* @note The Load behavior can be asynchronous if the assigned thread number is greater than zero.
* @see Initializer::init()
@@ -1288,41 +1197,26 @@ public:
Result load(const std::string& path) noexcept;
/**
- * @brief Loads a picture data from a memory block of a given size.
- *
- * ThorVG efficiently caches the loaded data using the specified @p data address as a key
- * when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations
- * for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data.
- *
- * @param[in] data A pointer to a memory location where the content of the picture file is stored.
- * @param[in] size The size in bytes of the memory occupied by the @p data.
- * @param[in] copy Decides whether the data should be copied into the engine local buffer.
- *
- * @retval Result::Success When succeed.
- * @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less.
- * @retval Result::NonSupport When trying to load a file with an unknown extension.
- * @retval Result::Unknown If an error occurs at a later stage.
- *
- * @warning: you have responsibility to release the @p data memory if the @p copy is true
* @deprecated Use load(const char* data, uint32_t size, const std::string& mimeType, bool copy) instead.
- * @see Result load(const char* data, uint32_t size, const std::string& mimeType, bool copy = false) noexcept
*/
TVG_DEPRECATED Result load(const char* data, uint32_t size, bool copy = false) noexcept;
/**
* @brief Loads a picture data from a memory block of a given size.
*
+ * ThorVG efficiently caches the loaded data using the specified @p data address as a key
+ * when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations
+ * for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data.
+ *
* @param[in] data A pointer to a memory location where the content of the picture file is stored.
* @param[in] size The size in bytes of the memory occupied by the @p data.
* @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "lottie", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one.
* @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not.
*
- * @retval Result::Success When succeed.
* @retval Result::InvalidArguments In case no data are provided or the @p size is zero or less.
* @retval Result::NonSupport When trying to load a file with an unknown extension.
- * @retval Result::Unknown If an error occurs at a later stage.
*
- * @warning: It's the user responsibility to release the @p data memory if the @p copy is @c true.
+ * @warning: It's the user responsibility to release the @p data memory.
*
* @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out.
* @since 0.5
@@ -1338,7 +1232,6 @@ public:
* @param[in] w A new width of the image in pixels.
* @param[in] h A new height of the image in pixels.
*
- * @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
*/
Result size(float w, float h) noexcept;
@@ -1348,7 +1241,6 @@ public:
* @param[out] w The width of the image in pixels.
* @param[out] h The height of the image in pixels.
*
- * @retval Result::Success when succeed.
*/
Result size(float* w, float* h) const noexcept;
@@ -1359,16 +1251,12 @@ public:
* when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations
* for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data.
*
- * @param[in] paint A Tvg_Paint pointer to the picture object.
* @param[in] data A pointer to a memory location where the content of the picture raw data is stored.
* @param[in] w The width of the image @p data in pixels.
* @param[in] h The height of the image @p data in pixels.
* @param[in] premultiplied If @c true, the given image data is alpha-premultiplied.
* @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not.
*
- * @retval Result::Success When succeed, Result::InsufficientCondition otherwise.
- * @retval Result::FailedAllocation An internal error possibly with memory allocation.
- *
* @since 0.9
*/
Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept;
@@ -1387,9 +1275,6 @@ public:
* @param[in] triangles An array of Polygons(triangles) that make up the mesh, or null to remove the mesh.
* @param[in] triangleCnt The number of Polygons(triangles) provided, or 0 to remove the mesh.
*
- * @retval Result::Success When succeed.
- * @retval Result::Unknown If fails
- *
* @note The Polygons are copied internally, so modifying them after calling Mesh::mesh has no affect.
* @warning Please do not use it, this API is not official one. It could be modified in the next version.
*
@@ -1456,24 +1341,12 @@ public:
*
* @param[in] paint A Paint object to be drawn.
*
- * @retval Result::Success when succeed, Result::MemoryCorruption otherwise.
- *
* @note The rendering order of the paints is the same as the order as they were pushed. Consider sorting the paints before pushing them if you intend to use layering.
* @see Scene::paints()
* @see Scene::clear()
*/
Result push(std::unique_ptr<Paint> paint) noexcept;
- /**
- * @brief Sets the size of the container, where all the paints pushed into the Scene are stored.
- *
- * If the number of objects pushed into the scene is known in advance, calling the function
- * prevents multiple memory reallocation, thus improving the performance.
- *
- * @param[in] size The number of objects for which the memory is to be reserved.
- *
- * @return Result::Success when succeed, Result::FailedAllocation otherwise.
- */
TVG_DEPRECATED Result reserve(uint32_t size) noexcept;
/**
@@ -1496,8 +1369,6 @@ public:
*
* @param[in] free If @c true, the memory occupied by paints is deallocated, otherwise it is not.
*
- * @retval Result::Success when succeed
- *
* @warning If you don't free the paints they become dangled. They are supposed to be reused, otherwise you are responsible for their lives. Thus please use the @p free argument only when you know how it works, otherwise it's not recommended.
*
* @since 0.2
@@ -1547,7 +1418,6 @@ public:
* @param[in] style The style of the font. It can be used to set the font to 'italic'.
* If not specified, the default style is used. Only 'italic' style is supported currently.
*
- * @retval Result::Success when the font properties are set successfully.
* @retval Result::InsufficientCondition when the specified @p name cannot be found.
*
* @note Experimental API
@@ -1562,8 +1432,6 @@ public:
*
* @param[in] text The multi-byte text encoded with utf8 string to be rendered.
*
- * @retval Result::Success when succeed.
- *
* @note Experimental API
*/
Result text(const char* text) noexcept;
@@ -1575,7 +1443,6 @@ public:
* @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0.
* @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0.
*
- * @retval Result::Success when succeed.
* @retval Result::InsufficientCondition when the font has not been set up prior to this operation.
*
* @see Text::font()
@@ -1591,7 +1458,6 @@ public:
*
* @param[in] f The unique pointer to the gradient fill.
*
- * @retval Result::Success when succeed, Result::MemoryCorruption otherwise.
* @retval Result::InsufficientCondition when the font has not been set up prior to this operation.
*
* @note Either a solid color or a gradient fill is applied, depending on what was set as last.
@@ -1602,7 +1468,7 @@ public:
Result fill(std::unique_ptr<Fill> f) noexcept;
/**
- * @brief Loads a scalable font data(ttf) from a file.
+ * @brief Loads a scalable font data (ttf) from a file.
*
* ThorVG efficiently caches the loaded data using the specified @p path as a key.
* This means that loading the same file again will not result in duplicate operations;
@@ -1610,10 +1476,8 @@ public:
*
* @param[in] path The path to the font file.
*
- * @retval Result::Success When succeed.
* @retval Result::InvalidArguments In case the @p path is invalid.
* @retval Result::NonSupport When trying to load a file with an unknown extension.
- * @retval Result::Unknown If an error occurs at a later stage.
*
* @note Experimental API
*
@@ -1622,13 +1486,39 @@ public:
static Result load(const std::string& path) noexcept;
/**
+ * @brief Loads a scalable font data (ttf) from a memory block of a given size.
+ *
+ * ThorVG efficiently caches the loaded font data using the specified @p name as a key.
+ * This means that loading the same fonts again will not result in duplicate operations.
+ * Instead, ThorVG will reuse the previously loaded font data.
+ *
+ * @param[in] name The name under which the font will be stored and accessible (e.x. in a @p font() API).
+ * @param[in] data A pointer to a memory location where the content of the font data is stored.
+ * @param[in] size The size in bytes of the memory occupied by the @p data.
+ * @param[in] mimeType Mimetype or extension of font data. In case an empty string is provided the loader will be determined automatically.
+ * @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not (default).
+ *
+ * @retval Result::InvalidArguments If no name is provided or if @p size is zero while @p data points to a valid memory location.
+ * @retval Result::NonSupport When trying to load a file with an unsupported extension.
+ * @retval Result::InsufficientCondition If attempting to unload the font data that has not been previously loaded.
+ *
+ * @warning: It's the user responsibility to release the @p data memory.
+ *
+ * @note To unload the font data loaded using this API, pass the proper @p name and @c nullptr as @p data.
+ * @note If you are unsure about the MIME type, you can provide an empty value like @c "", and thorvg will attempt to figure it out.
+ * @note Experimental API
+ *
+ * @see Text::font(const char* name, float size, const char* style)
+ */
+ static Result load(const char* name, const char* data, uint32_t size, const std::string& mimeType = "ttf", bool copy = false) noexcept;
+
+ /**
* @brief Unloads the specified scalable font data (TTF) that was previously loaded.
*
* This function is used to release resources associated with a font file that has been loaded into memory.
*
* @param[in] path The file path of the loaded font.
*
- * @retval Result::Success Successfully unloads the font data.
* @retval Result::InsufficientCondition Fails if the loader is not initialized.
*
* @note If the font data is currently in use, it will not be immediately unloaded.
@@ -1703,14 +1593,14 @@ public:
* @param[in] h The height of the raster image.
* @param[in] cs The value specifying the way the 32-bits colors should be read/written.
*
- * @retval Result::Success When succeed.
- * @retval Result::MemoryCorruption When casting in the internal function implementation failed.
* @retval Result::InvalidArguments In case no valid pointer is provided or the width, or the height or the stride is zero.
+ * @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced.
* @retval Result::NonSupport In case the software engine is not supported.
*
* @warning Do not access @p buffer during Canvas::push() - Canvas::sync(). It should not be accessed while the engine is writing on it.
*
* @see Canvas::viewport()
+ * @see Canvas::sync()
*/
Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept;
@@ -1726,7 +1616,6 @@ public:
*
* @param[in] policy The method specifying the Memory Pool behavior. The default value is @c MempoolPolicy::Default.
*
- * @retval Result::Success When succeed.
* @retval Result::InsufficientCondition If the canvas contains some paints already.
* @retval Result::NonSupport In case the software engine is not supported.
*
@@ -1756,7 +1645,7 @@ public:
*
* @warning Please do not use it. This class is not fully supported yet.
*
- * @note Experimental API
+ * @since 0.14
*/
class TVG_API GlCanvas final : public Canvas
{
@@ -1773,10 +1662,11 @@ public:
* @param[in] w The width (in pixels) of the raster image.
* @param[in] h The height (in pixels) of the raster image.
*
- * @warning This API is experimental and not officially supported. It may be modified or removed in future versions.
- * @warning Drawing on the main surface is currently not permitted. If the identifier (@p id) is set to @c 0, the operation will be aborted.
+ * @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced.
+ * @retval Result::NonSupport In case the gl engine is not supported.
*
* @see Canvas::viewport()
+ * @see Canvas::sync()
*
* @note Currently, this only allows the GL_RGBA8 color space format.
* @note Experimental API
@@ -1788,7 +1678,7 @@ public:
*
* @return A new GlCanvas object.
*
- * @note Experimental API
+ * @since 0.14
*/
static std::unique_ptr<GlCanvas> gen() noexcept;
@@ -1811,14 +1701,22 @@ public:
~WgCanvas();
/**
- * @brief Sets the target window for the rasterization.
+ * @brief Sets the drawing target for the rasterization.
*
- * @warning Please do not use it, this API is not official one. It could be modified in the next version.
+ * @param[in] instance WGPUInstance, context for all other wgpu objects.
+ * @param[in] surface WGPUSurface, handle to a presentable surface.
+ * @param[in] w The width of the surface.
+ * @param[in] h The height of the surface.
+ *
+ * @retval Result::InsufficientCondition if the canvas is performing rendering. Please ensure the canvas is synced.
+ * @retval Result::NonSupport In case the wg engine is not supported.
*
* @note Experimental API
+ *
* @see Canvas::viewport()
+ * @see Canvas::sync()
*/
- Result target(void* window, uint32_t w, uint32_t h) noexcept;
+ Result target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept;
/**
* @brief Creates a new WgCanvas object.
@@ -1852,11 +1750,7 @@ public:
* @param[in] engine The engine types to initialize. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed.
* @param[in] threads The number of additional threads. Zero indicates only the main thread is to be used.
*
- * @retval Result::Success When succeed.
- * @retval Result::FailedAllocation An internal error possibly with memory allocation.
- * @retval Result::InvalidArguments If unknown engine type chosen.
* @retval Result::NonSupport In case the engine type is not supported on the system.
- * @retval Result::Unknown Others.
*
* @note The Initializer keeps track of the number of times it was called. Threads count is fixed at the first init() call.
* @see Initializer::term()
@@ -1868,11 +1762,8 @@ public:
*
* @param[in] engine The engine types to terminate. This is relative to the Canvas types, in which it will be used. For multiple backends bitwise operation is allowed
*
- * @retval Result::Success When succeed.
* @retval Result::InsufficientCondition In case there is nothing to be terminated.
- * @retval Result::InvalidArguments If unknown engine type chosen.
* @retval Result::NonSupport In case the engine type is not supported on the system.
- * @retval Result::Unknown Others.
*
* @note Initializer does own reference counting for multiple calls.
* @see Initializer::init()
@@ -1903,7 +1794,6 @@ public:
*
* @param[in] no The index of the animation frame to be displayed. The index should be less than the totalFrame().
*
- * @retval Result::Success Successfully set the frame.
* @retval Result::InsufficientCondition if the given @p no is the same as the current frame value.
* @retval Result::NonSupport The current Picture data does not support animations.
*
@@ -1975,9 +1865,7 @@ public:
* @param[in] begin segment start.
* @param[in] end segment end.
*
- * @retval Result::Success When succeed.
* @retval Result::InsufficientCondition In case the animation is not loaded.
- * @retval Result::InvalidArguments When the given parameter is invalid.
* @retval Result::NonSupport When it's not animatable.
*
* @note Range from 0.0~1.0
@@ -1993,9 +1881,7 @@ public:
* @param[out] begin segment start.
* @param[out] end segment end.
*
- * @retval Result::Success When succeed.
* @retval Result::InsufficientCondition In case the animation is not loaded.
- * @retval Result::InvalidArguments When the given parameter is invalid.
* @retval Result::NonSupport When it's not animatable.
*
* @note Experimental API
@@ -2056,10 +1942,8 @@ public:
* @param[in] path A path to the file, in which the paint data is to be saved.
* @param[in] compress If @c true then compress data if possible.
*
- * @retval Result::Success When succeed.
* @retval Result::InsufficientCondition If currently saving other resources.
* @retval Result::NonSupport When trying to save a file with an unknown extension or in an unsupported format.
- * @retval Result::MemoryCorruption An internal error.
* @retval Result::Unknown In case an empty paint is to be saved.
*
* @note Saving can be asynchronous if the assigned thread number is greater than zero. To guarantee the saving is done, call sync() afterwards.
@@ -2079,10 +1963,8 @@ public:
* @param[in] quality The encoded quality level. @c 0 is the minimum, @c 100 is the maximum value(recommended).
* @param[in] fps The desired frames per second (FPS). For example, to encode data at 60 FPS, pass 60. Pass 0 to keep the original frame data.
*
- * @retval Result::Success if the export succeeds.
* @retval Result::InsufficientCondition if there are ongoing resource-saving operations.
* @retval Result::NonSupport if an attempt is made to save the file with an unknown extension or in an unsupported format.
- * @retval Result::MemoryCorruption in case of an internal error.
* @retval Result::Unknown if attempting to save an empty paint.
*
* @note A higher frames per second (FPS) would result in a larger file size. It is recommended to use the default value.
@@ -2101,9 +1983,6 @@ public:
* Thus, if you wish to have a benefit of it, you must call sync() after the save() in the proper delayed time.
* Otherwise, you can call sync() immediately.
*
- * @retval Result::Success when succeed.
- * @retval Result::InsufficientCondition otherwise.
- *
* @note The asynchronous tasking is dependent on the Saver module implementation.
* @see Saver::save()
*
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
index e6b5d47050..e99ec46681 100644
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
@@ -111,3 +111,11 @@ void operator*=(Point& pt, const Matrix& m)
pt.x = tx;
pt.y = ty;
}
+
+
+Point operator*(const Point& pt, const Matrix& m)
+{
+ auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13;
+ auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
+ return {tx, ty};
+}
diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h
index 0f877d919e..3555885c8e 100644
--- a/thirdparty/thorvg/src/common/tvgMath.h
+++ b/thirdparty/thorvg/src/common/tvgMath.h
@@ -152,9 +152,9 @@ static inline void operator*=(Matrix& lhs, const Matrix& rhs)
}
-static inline void mathLog(Matrix* m)
+static inline void mathLog(const Matrix& m)
{
- TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33);
+ TVGLOG("COMMON", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m.e11, m.e12, m.e13, m.e21, m.e22, m.e23, m.e31, m.e32, m.e33);
}
@@ -163,6 +163,7 @@ static inline void mathLog(Matrix* m)
/************************************************************************/
void operator*=(Point& pt, const Matrix& m);
+Point operator*(const Point& pt, const Matrix& m);
static inline bool mathZero(const Point& p)
@@ -231,6 +232,11 @@ static inline Point operator/(const Point& lhs, const float rhs)
}
+static inline void mathLog(const Point& pt)
+{
+ TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y);
+}
+
/************************************************************************/
/* Interpolation functions */
/************************************************************************/
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index f29bc09b77..ae784ccfd7 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -647,9 +647,9 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
}
}
- *red = static_cast<uint8_t>(roundf(_red * 255.0f));
- *green = static_cast<uint8_t>(roundf(_green * 255.0f));
- *blue = static_cast<uint8_t>(roundf(_blue * 255.0f));
+ *red = static_cast<uint8_t>(ceil(_red * 255.0f));
+ *green = static_cast<uint8_t>(ceil(_green * 255.0f));
+ *blue = static_cast<uint8_t>(ceil(_blue * 255.0f));
return true;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h
index cbaec28fa3..a072a88819 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterAvx.h
@@ -101,47 +101,57 @@ static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32
static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
- if (surface->channelSize != sizeof(uint32_t)) {
- TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
- return false;
- }
-
- auto color = surface->join(r, g, b, a);
- auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
- uint32_t ialpha = 255 - a;
+ //32bits channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto color = surface->join(r, g, b, a);
+ auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
- auto avxColor = _mm_set1_epi32(color);
- auto avxIalpha = _mm_set1_epi8(ialpha);
+ uint32_t ialpha = 255 - a;
- for (uint32_t y = 0; y < h; ++y) {
- auto dst = &buffer[y * surface->stride];
+ auto avxColor = _mm_set1_epi32(color);
+ auto avxIalpha = _mm_set1_epi8(ialpha);
- //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
- auto notAligned = ((uintptr_t)dst & 0xf) / 4;
- if (notAligned) {
- notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned);
- for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
- *dst = color + ALPHA_BLEND(*dst, ialpha);
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+
+ //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
+ auto notAligned = ((uintptr_t)dst & 0xf) / 4;
+ if (notAligned) {
+ notAligned = (N_32BITS_IN_128REG - notAligned > w ? w : N_32BITS_IN_128REG - notAligned);
+ for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ }
}
- }
- //2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once
- uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG;
- uint32_t avxFilled = iterations * N_32BITS_IN_128REG;
- auto avxDst = (__m128i*)dst;
- for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
- *avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha));
- }
+ //2. fill the aligned memory - N_32BITS_IN_128REG pixels processed at once
+ uint32_t iterations = (w - notAligned) / N_32BITS_IN_128REG;
+ uint32_t avxFilled = iterations * N_32BITS_IN_128REG;
+ auto avxDst = (__m128i*)dst;
+ for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
+ *avxDst = _mm_add_epi32(avxColor, ALPHA_BLEND(*avxDst, avxIalpha));
+ }
- //3. fill the remaining pixels
- int32_t leftovers = w - notAligned - avxFilled;
- dst += avxFilled;
- while (leftovers--) {
- *dst = color + ALPHA_BLEND(*dst, ialpha);
- dst++;
+ //3. fill the remaining pixels
+ int32_t leftovers = w - notAligned - avxFilled;
+ dst += avxFilled;
+ while (leftovers--) {
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ dst++;
+ }
+ }
+ //8bit grayscale
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
+ auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
+ auto ialpha = ~a;
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+ for (uint32_t x = 0; x < w; ++x, ++dst) {
+ *dst = a + MULTIPLY(*dst, ialpha);
+ }
}
}
return true;
@@ -150,56 +160,68 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, u
static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
- if (surface->channelSize != sizeof(uint32_t)) {
- TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
- return false;
- }
-
- auto color = surface->join(r, g, b, a);
auto span = rle->spans;
- uint32_t src;
- for (uint32_t i = 0; i < rle->size; ++i) {
- auto dst = &surface->buf32[span->y * surface->stride + span->x];
+ //32bit channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto color = surface->join(r, g, b, a);
+ uint32_t src;
- if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
- else src = color;
+ for (uint32_t i = 0; i < rle->size; ++i) {
+ auto dst = &surface->buf32[span->y * surface->stride + span->x];
- auto ialpha = IA(src);
+ if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
+ else src = color;
- //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
- auto notAligned = ((uintptr_t)dst & 0xf) / 4;
- if (notAligned) {
- notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
- for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
- *dst = src + ALPHA_BLEND(*dst, ialpha);
+ auto ialpha = IA(src);
+
+ //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
+ auto notAligned = ((uintptr_t)dst & 0xf) / 4;
+ if (notAligned) {
+ notAligned = (N_32BITS_IN_128REG - notAligned > span->len ? span->len : N_32BITS_IN_128REG - notAligned);
+ for (uint32_t x = 0; x < notAligned; ++x, ++dst) {
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ }
}
- }
- //2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
- //In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all
- uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
- uint32_t avxFilled = 0;
- if (iterations > 0) {
- auto avxSrc = _mm_set1_epi32(src);
- auto avxIalpha = _mm_set1_epi8(ialpha);
+ //2. fill the aligned memory using avx - N_32BITS_IN_128REG pixels processed at once
+ //In order to avoid unneccessary avx variables declarations a check is made whether there are any iterations at all
+ uint32_t iterations = (span->len - notAligned) / N_32BITS_IN_128REG;
+ uint32_t avxFilled = 0;
+ if (iterations > 0) {
+ auto avxSrc = _mm_set1_epi32(src);
+ auto avxIalpha = _mm_set1_epi8(ialpha);
+
+ avxFilled = iterations * N_32BITS_IN_128REG;
+ auto avxDst = (__m128i*)dst;
+ for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
+ *avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
+ }
+ }
- avxFilled = iterations * N_32BITS_IN_128REG;
- auto avxDst = (__m128i*)dst;
- for (uint32_t x = 0; x < iterations; ++x, ++avxDst) {
- *avxDst = _mm_add_epi32(avxSrc, ALPHA_BLEND(*avxDst, avxIalpha));
+ //3. fill the remaining pixels
+ int32_t leftovers = span->len - notAligned - avxFilled;
+ dst += avxFilled;
+ while (leftovers--) {
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ dst++;
}
- }
- //3. fill the remaining pixels
- int32_t leftovers = span->len - notAligned - avxFilled;
- dst += avxFilled;
- while (leftovers--) {
- *dst = src + ALPHA_BLEND(*dst, ialpha);
- dst++;
+ ++span;
+ }
+ //8bit grayscale
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ TVGLOG("SW_ENGINE", "Require AVX Optimization, Channel Size = %d", surface->channelSize);
+ uint8_t src;
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buf8[span->y * surface->stride + span->x];
+ if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
+ else src = a;
+ auto ialpha = ~a;
+ for (uint32_t x = 0; x < span->len; ++x, ++dst) {
+ *dst = src + MULTIPLY(*dst, ialpha);
+ }
}
-
- ++span;
}
return true;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h
index 1ea6cd96cf..91cf7743c1 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterNeon.h
@@ -91,44 +91,56 @@ static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int3
static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
- if (surface->channelSize != sizeof(uint32_t)) {
- TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
- return false;
- }
-
- auto color = surface->join(r, g, b, a);
auto span = rle->spans;
- uint32_t src;
- uint8x8_t *vDst = nullptr;
- uint16_t align;
-
- for (uint32_t i = 0; i < rle->size; ++i) {
- if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
- else src = color;
-
- auto dst = &surface->buf32[span->y * surface->stride + span->x];
- auto ialpha = IA(src);
-
- if ((((uintptr_t) dst) & 0x7) != 0) {
- //fill not aligned byte
- *dst = src + ALPHA_BLEND(*dst, ialpha);
- vDst = (uint8x8_t*)(dst + 1);
- align = 1;
- } else {
- vDst = (uint8x8_t*) dst;
- align = 0;
- }
- uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src);
- uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha);
+ //32bit channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto color = surface->join(r, g, b, a);
+ uint32_t src;
+ uint8x8_t *vDst = nullptr;
+ uint16_t align;
+
+ for (uint32_t i = 0; i < rle->size; ++i) {
+ if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
+ else src = color;
- for (uint32_t x = 0; x < (span->len - align) / 2; ++x)
- vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha));
+ auto dst = &surface->buf32[span->y * surface->stride + span->x];
+ auto ialpha = IA(src);
- auto leftovers = (span->len - align) % 2;
- if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha);
+ if ((((uintptr_t) dst) & 0x7) != 0) {
+ //fill not aligned byte
+ *dst = src + ALPHA_BLEND(*dst, ialpha);
+ vDst = (uint8x8_t*)(dst + 1);
+ align = 1;
+ } else {
+ vDst = (uint8x8_t*) dst;
+ align = 0;
+ }
- ++span;
+ uint8x8_t vSrc = (uint8x8_t) vdup_n_u32(src);
+ uint8x8_t vIalpha = vdup_n_u8((uint8_t) ialpha);
+
+ for (uint32_t x = 0; x < (span->len - align) / 2; ++x)
+ vDst[x] = vadd_u8(vSrc, ALPHA_BLEND(vDst[x], vIalpha));
+
+ auto leftovers = (span->len - align) % 2;
+ if (leftovers > 0) dst[span->len - 1] = src + ALPHA_BLEND(dst[span->len - 1], ialpha);
+
+ ++span;
+ }
+ //8bit grayscale
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
+ uint8_t src;
+ for (uint32_t i = 0; i < rle->size; ++i, ++span) {
+ auto dst = &surface->buf8[span->y * surface->stride + span->x];
+ if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
+ else src = a;
+ auto ialpha = ~a;
+ for (uint32_t x = 0; x < span->len; ++x, ++dst) {
+ *dst = src + MULTIPLY(*dst, ialpha);
+ }
+ }
}
return true;
}
@@ -136,41 +148,51 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
- if (surface->channelSize != sizeof(uint32_t)) {
- TVGERR("SW_ENGINE", "Unsupported Channel Size = %d", surface->channelSize);
- return false;
- }
-
- auto color = surface->join(r, g, b, a);
- auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
- auto ialpha = 255 - a;
- auto vColor = vdup_n_u32(color);
- auto vIalpha = vdup_n_u8((uint8_t) ialpha);
+ //32bits channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto color = surface->join(r, g, b, a);
+ auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
+ auto ialpha = 255 - a;
- uint8x8_t* vDst = nullptr;
- uint32_t align;
+ auto vColor = vdup_n_u32(color);
+ auto vIalpha = vdup_n_u8((uint8_t) ialpha);
- for (uint32_t y = 0; y < h; ++y) {
- auto dst = &buffer[y * surface->stride];
+ uint8x8_t* vDst = nullptr;
+ uint32_t align;
- if ((((uintptr_t) dst) & 0x7) != 0) {
- //fill not aligned byte
- *dst = color + ALPHA_BLEND(*dst, ialpha);
- vDst = (uint8x8_t*) (dst + 1);
- align = 1;
- } else {
- vDst = (uint8x8_t*) dst;
- align = 0;
- }
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
- for (uint32_t x = 0; x < (w - align) / 2; ++x)
- vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
+ if ((((uintptr_t) dst) & 0x7) != 0) {
+ //fill not aligned byte
+ *dst = color + ALPHA_BLEND(*dst, ialpha);
+ vDst = (uint8x8_t*) (dst + 1);
+ align = 1;
+ } else {
+ vDst = (uint8x8_t*) dst;
+ align = 0;
+ }
- auto leftovers = (w - align) % 2;
- if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
+ for (uint32_t x = 0; x < (w - align) / 2; ++x)
+ vDst[x] = vadd_u8((uint8x8_t)vColor, ALPHA_BLEND(vDst[x], vIalpha));
+
+ auto leftovers = (w - align) % 2;
+ if (leftovers > 0) dst[w - 1] = color + ALPHA_BLEND(dst[w - 1], ialpha);
+ }
+ //8bit grayscale
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ TVGLOG("SW_ENGINE", "Require Neon Optimization, Channel Size = %d", surface->channelSize);
+ auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
+ auto ialpha = ~a;
+ for (uint32_t y = 0; y < h; ++y) {
+ auto dst = &buffer[y * surface->stride];
+ for (uint32_t x = 0; x < w; ++x, ++dst) {
+ *dst = a + MULTIPLY(*dst, ialpha);
+ }
+ }
}
return true;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
index bab534bba2..cfce7785c7 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
@@ -824,7 +824,7 @@ static AASpans* _AASpans(float ymin, float ymax, const SwImage* image, const SwB
//Initialize X range
auto height = yEnd - yStart;
- aaSpans->lines = static_cast<AALine*>(calloc(height, sizeof(AALine)));
+ aaSpans->lines = static_cast<AALine*>(malloc(height * sizeof(AALine)));
for (int32_t i = 0; i < height; i++) {
aaSpans->lines[i].x[0] = INT32_MAX;
@@ -878,7 +878,7 @@ static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x
/*
* This Anti-Aliasing mechanism is originated from Hermet Park's idea.
* To understand this AA logic, you can refer this page:
- * www.hermet.pe.kr/122 (hermetpark@gmail.com)
+ * https://uigraphics.tistory.com/1
*/
static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
{
@@ -924,6 +924,9 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
//Calculates AA Edges
for (y++; y < yEnd; y++) {
+
+ if (lines[y].x[0] == INT32_MAX) continue;
+
//Ready tx
if (eidx == 0) {
tx[0] = pEdge.x;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
index 78108af095..0a3f5ef7e7 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
@@ -147,7 +147,7 @@ struct SwShapeTask : SwTask
}
}
//Fill
- if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
+ if (flags & (RenderUpdateFlag::Path |RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (visibleFill || clipper) {
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
}
@@ -160,7 +160,7 @@ struct SwShapeTask : SwTask
}
}
//Stroke
- if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
+ if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
if (strokeWidth > 0.0f) {
shapeResetStroke(&shape, rshape, transform);
if (!shapeGenStrokeRle(&shape, rshape, transform, clipRegion, bbox, mpool, tid)) goto err;
@@ -718,9 +718,6 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
- //Finish previous task if it has duplicated request.
- task->done();
-
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
@@ -769,8 +766,11 @@ RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
//prepare task
auto task = static_cast<SwImageTask*>(data);
if (!task) task = new SwImageTask;
+ else task->done();
+
task->source = surface;
task->mesh = mesh;
+
return prepareCommon(task, transform, clips, opacity, flags);
}
@@ -780,6 +780,8 @@ RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data,
//prepare task
auto task = static_cast<SwSceneTask*>(data);
if (!task) task = new SwSceneTask;
+ else task->done();
+
task->scene = scene;
//TODO: Failed threading them. It would be better if it's possible.
@@ -788,6 +790,7 @@ RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data,
for (auto task = scene.begin(); task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
+
return prepareCommon(task, transform, clips, opacity, flags);
}
@@ -796,10 +799,10 @@ RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
- if (!task) {
- task = new SwShapeTask;
- task->rshape = &rshape;
- }
+ if (!task) task = new SwShapeTask;
+ else task->done();
+
+ task->rshape = &rshape;
task->clipper = clipper;
return prepareCommon(task, transform, clips, opacity, flags);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
index 9ec4bd78a5..18f5f3eca8 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
@@ -238,7 +238,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
} else {
//this is a mitered (pointed) or beveled (truncated) corner
auto rotate = SIDE_TO_ROTATE(side);
- auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false;
+ auto bevel = stroke.join == StrokeJoin::Bevel;
SwFixed phi = 0;
SwFixed thcos = 0;
@@ -816,7 +816,7 @@ void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* tran
stroke->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap();
- stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit()) << 16;
+ stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
//Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin();
diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h
index a4b866eacb..9d216e2f30 100644
--- a/thirdparty/thorvg/src/renderer/tvgCanvas.h
+++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h
@@ -26,17 +26,15 @@
#include "tvgPaint.h"
+enum Status : uint8_t {Synced = 0, Updating, Drawing, Damanged};
+
struct Canvas::Impl
{
- enum Status : uint8_t {Synced = 0, Updating, Drawing};
-
list<Paint*> paints;
RenderMethod* renderer;
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
Status status = Status::Synced;
- bool refresh = false; //if all paints should be updated by force.
-
Impl(RenderMethod* pRenderer) : renderer(pRenderer)
{
renderer->ref();
@@ -87,18 +85,13 @@ struct Canvas::Impl
return Result::Success;
}
- void needRefresh()
- {
- refresh = true;
- }
-
Result update(Paint* paint, bool force)
{
if (paints.empty() || status == Status::Drawing) return Result::InsufficientCondition;
Array<RenderData> clips;
auto flag = RenderUpdateFlag::None;
- if (refresh || force) flag = RenderUpdateFlag::All;
+ if (status == Status::Damanged || force) flag = RenderUpdateFlag::All;
if (paint) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
@@ -106,7 +99,6 @@ struct Canvas::Impl
for (auto paint : paints) {
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
}
- refresh = false;
}
status = Status::Updating;
return Result::Success;
@@ -114,6 +106,7 @@ struct Canvas::Impl
Result draw()
{
+ if (status == Status::Damanged) update(nullptr, false);
if (status == Status::Drawing || paints.empty() || !renderer->preRender()) return Result::InsufficientCondition;
bool rendered = false;
@@ -129,7 +122,7 @@ struct Canvas::Impl
Result sync()
{
- if (status == Status::Synced) return Result::InsufficientCondition;
+ if (status == Status::Synced || status == Status::Damanged) return Result::InsufficientCondition;
if (renderer->sync()) {
status = Status::Synced;
@@ -141,7 +134,8 @@ struct Canvas::Impl
Result viewport(int32_t x, int32_t y, int32_t w, int32_t h)
{
- if (status != Status::Synced) return Result::InsufficientCondition;
+ if (status != Status::Damanged && status != Status::Synced) return Result::InsufficientCondition;
+
RenderRegion val = {x, y, w, h};
//intersect if the target buffer is already set.
auto surface = renderer->mainSurface();
@@ -151,7 +145,7 @@ struct Canvas::Impl
if (vport == val) return Result::Success;
renderer->viewport(val);
vport = val;
- needRefresh();
+ status = Status::Damanged;
return Result::Success;
}
};
diff --git a/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp
index 211dbb589c..82666b7ae3 100644
--- a/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgGlCanvas.cpp
@@ -45,15 +45,14 @@ struct GlCanvas::Impl
/************************************************************************/
#ifdef THORVG_GL_RASTER_SUPPORT
-GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(new Impl)
+GlCanvas::GlCanvas() : Canvas(GlRenderer::gen()), pImpl(nullptr)
#else
-GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(new Impl)
+GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(nullptr)
#endif
{
}
-
GlCanvas::~GlCanvas()
{
delete(pImpl);
@@ -63,6 +62,10 @@ GlCanvas::~GlCanvas()
Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
{
#ifdef THORVG_GL_RASTER_SUPPORT
+ if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) {
+ return Result::InsufficientCondition;
+ }
+
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<GlRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
@@ -72,7 +75,7 @@ Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
renderer->viewport(Canvas::pImpl->vport);
//Paints must be updated again with this new target.
- Canvas::pImpl->needRefresh();
+ Canvas::pImpl->status = Status::Damanged;
return Result::Success;
#endif
diff --git a/thirdparty/thorvg/src/renderer/tvgLoader.cpp b/thirdparty/thorvg/src/renderer/tvgLoader.cpp
index 4b8d3256a9..6a81ddcdbb 100644
--- a/thirdparty/thorvg/src/renderer/tvgLoader.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgLoader.cpp
@@ -433,3 +433,27 @@ LoadModule* LoaderMgr::loader(const uint32_t *data, uint32_t w, uint32_t h, bool
delete(loader);
return nullptr;
}
+
+
+//loads fonts from memory - loader is cached (regardless of copy value) in order to access it while setting font
+LoadModule* LoaderMgr::loader(const char* name, const char* data, uint32_t size, TVG_UNUSED const string& mimeType, bool copy)
+{
+#ifdef THORVG_TTF_LOADER_SUPPORT
+ //TODO: add check for mimetype ?
+ if (auto loader = _findFromCache(name)) return loader;
+
+ //function is dedicated for ttf loader (the only supported font loader)
+ auto loader = new TtfLoader;
+ if (loader->open(data, size, copy)) {
+ loader->hashpath = strdup(name);
+ loader->pathcache = true;
+ ScopedLock lock(key);
+ _activeLoaders.back(loader);
+ return loader;
+ }
+
+ TVGLOG("LOADER", "The font data \"%s\" could not be loaded.", name);
+ delete(loader);
+#endif
+ return nullptr;
+}
diff --git a/thirdparty/thorvg/src/renderer/tvgLoader.h b/thirdparty/thorvg/src/renderer/tvgLoader.h
index b15032df27..74c4f43964 100644
--- a/thirdparty/thorvg/src/renderer/tvgLoader.h
+++ b/thirdparty/thorvg/src/renderer/tvgLoader.h
@@ -32,6 +32,7 @@ struct LoaderMgr
static LoadModule* loader(const string& path, bool* invalid);
static LoadModule* loader(const char* data, uint32_t size, const string& mimeType, bool copy);
static LoadModule* loader(const uint32_t* data, uint32_t w, uint32_t h, bool copy);
+ static LoadModule* loader(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy);
static LoadModule* loader(const char* key);
static bool retrieve(const string& path);
static bool retrieve(LoadModule* loader);
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.cpp b/thirdparty/thorvg/src/renderer/tvgShape.cpp
index c010aa7bbf..3b9293a00e 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgShape.cpp
@@ -88,6 +88,8 @@ Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point*
pImpl->grow(cmdCnt, ptsCnt);
pImpl->append(cmds, cmdCnt, pts, ptsCnt);
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
@@ -104,6 +106,8 @@ Result Shape::lineTo(float x, float y) noexcept
{
pImpl->lineTo(x, y);
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
@@ -112,6 +116,8 @@ Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float
{
pImpl->cubicTo(cx1, cy1, cx2, cy2, x, y);
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
@@ -120,6 +126,8 @@ Result Shape::close() noexcept
{
pImpl->close();
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
@@ -137,9 +145,12 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept
pImpl->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy);
pImpl->close();
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
+
Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept
{
//just circle
@@ -196,6 +207,8 @@ Result Shape::appendArc(float cx, float cy, float radius, float startAngle, floa
if (pie) pImpl->close();
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
@@ -234,6 +247,8 @@ Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry)
pImpl->close();
}
+ pImpl->flag |= RenderUpdateFlag::Path;
+
return Result::Success;
}
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h
index 4e85db37d0..c45995a64d 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.h
+++ b/thirdparty/thorvg/src/renderer/tvgShape.h
@@ -167,24 +167,18 @@ struct Shape::Impl
memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt);
rs.path.cmds.count += cmdCnt;
rs.path.pts.count += ptsCnt;
-
- flag |= RenderUpdateFlag::Path;
}
void moveTo(float x, float y)
{
rs.path.cmds.push(PathCommand::MoveTo);
rs.path.pts.push({x, y});
-
- flag |= RenderUpdateFlag::Path;
}
void lineTo(float x, float y)
{
rs.path.cmds.push(PathCommand::LineTo);
rs.path.pts.push({x, y});
-
- flag |= RenderUpdateFlag::Path;
}
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
@@ -193,8 +187,6 @@ struct Shape::Impl
rs.path.pts.push({cx1, cy1});
rs.path.pts.push({cx2, cy2});
rs.path.pts.push({x, y});
-
- flag |= RenderUpdateFlag::Path;
}
void close()
@@ -203,8 +195,6 @@ struct Shape::Impl
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
rs.path.cmds.push(PathCommand::Close);
-
- flag |= RenderUpdateFlag::Path;
}
void strokeWidth(float width)
diff --git a/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp
index 52d85d8320..d762492f22 100644
--- a/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgSwCanvas.cpp
@@ -46,9 +46,9 @@ struct SwCanvas::Impl
/************************************************************************/
#ifdef THORVG_SW_RASTER_SUPPORT
-SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(new Impl)
+SwCanvas::SwCanvas() : Canvas(SwRenderer::gen()), pImpl(nullptr)
#else
-SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(new Impl)
+SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(nullptr)
#endif
{
}
@@ -82,6 +82,10 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept
Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept
{
#ifdef THORVG_SW_RASTER_SUPPORT
+ if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) {
+ return Result::InsufficientCondition;
+ }
+
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<SwRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
@@ -90,12 +94,12 @@ Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
renderer->viewport(Canvas::pImpl->vport);
- //Paints must be updated again with this new target.
- Canvas::pImpl->needRefresh();
-
//FIXME: The value must be associated with an individual canvas instance.
ImageLoader::cs = static_cast<ColorSpace>(cs);
+ //Paints must be updated again with this new target.
+ Canvas::pImpl->status = Status::Damanged;
+
return Result::Success;
#endif
return Result::NonSupport;
diff --git a/thirdparty/thorvg/src/renderer/tvgText.cpp b/thirdparty/thorvg/src/renderer/tvgText.cpp
index 1fe244c11d..4b5eb35ce5 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgText.cpp
@@ -71,6 +71,21 @@ Result Text::load(const std::string& path) noexcept
}
+Result Text::load(const char* name, const char* data, uint32_t size, const string& mimeType, bool copy) noexcept
+{
+ if (!name || (size == 0 && data)) return Result::InvalidArguments;
+
+ //unload font
+ if (!data) {
+ if (LoaderMgr::retrieve(name)) return Result::Success;
+ return Result::InsufficientCondition;
+ }
+
+ if (!LoaderMgr::loader(name, data, size, mimeType, copy)) return Result::NonSupport;
+ return Result::Success;
+}
+
+
Result Text::unload(const std::string& path) noexcept
{
if (LoaderMgr::retrieve(path)) return Result::Success;
diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
index f4fb12259a..c56ce8b878 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.h
+++ b/thirdparty/thorvg/src/renderer/tvgText.h
@@ -100,7 +100,7 @@ struct Text::Impl
bool render(RenderMethod* renderer)
{
if (paint) return PP(paint)->render(renderer);
- return false;
+ return true;
}
bool load()
diff --git a/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp b/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp
index 7db77f6d0c..067e35b1f0 100644
--- a/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgWgCanvas.cpp
@@ -47,33 +47,39 @@ WgCanvas::WgCanvas() : Canvas(nullptr), pImpl(nullptr)
{
}
+
WgCanvas::~WgCanvas()
{
delete pImpl;
}
-Result WgCanvas::target(void* window, uint32_t w, uint32_t h) noexcept
+
+Result WgCanvas::target(void* instance, void* surface, uint32_t w, uint32_t h) noexcept
{
#ifdef THORVG_WG_RASTER_SUPPORT
- if (!window) return Result::InvalidArguments;
- if ((w == 0) || (h == 0)) return Result::InvalidArguments;
+ if (Canvas::pImpl->status != Status::Damanged && Canvas::pImpl->status != Status::Synced) {
+ return Result::InsufficientCondition;
+ }
+
+ if (!instance || !surface || (w == 0) || (h == 0)) return Result::InvalidArguments;
//We know renderer type, avoid dynamic_cast for performance.
auto renderer = static_cast<WgRenderer*>(Canvas::pImpl->renderer);
if (!renderer) return Result::MemoryCorruption;
- if (!renderer->target(window, w, h)) return Result::Unknown;
+ if (!renderer->target((WGPUInstance)instance, (WGPUSurface)surface, w, h)) return Result::Unknown;
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
renderer->viewport(Canvas::pImpl->vport);
//Paints must be updated again with this new target.
- Canvas::pImpl->needRefresh();
+ Canvas::pImpl->status = Status::Damanged;
return Result::Success;
#endif
return Result::NonSupport;
}
+
unique_ptr<WgCanvas> WgCanvas::gen() noexcept
{
#ifdef THORVG_WG_RASTER_SUPPORT
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index 663d685d01..d08158bfe7 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,6 +1,6 @@
#!/bin/bash -e
-VERSION=0.13.8
+VERSION=0.14.0
cd thirdparty/thorvg/ || true
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/