Skip to content

Commit 44ce9f4

Browse files
Fix stretched/misplaced logo on startup & race conditions
* **Bug Fixes** * Improved boot splash sizing and centering across display scales and window configurations. * Boot splash screens now adapt more reliably when window dimensions change during startup. * Improved Wayland startup sizing and synchronization for more accurate initial window rendering. * Reduced potential hangs when closing the editor or stopping background previews and documentation generation. * Improved X11 window resize event handling during startup.
1 parent aab4134 commit 44ce9f4

19 files changed

Lines changed: 443 additions & 122 deletions

core/object/worker_thread_pool.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,9 +580,11 @@ void WorkerThreadPool::_switch_runlevel(Runlevel p_runlevel) {
580580
runlevel = p_runlevel;
581581
memset(&runlevel_data, 0, sizeof(runlevel_data));
582582
for (uint32_t i = 0; i < threads.size(); i++) {
583-
threads[i].cond_var.notify_one();
583+
threads[i].cond_var.notify_one(); // Wakes the worker loop so it rechecks the new runlevel
584584
threads[i].signaled = true;
585585
}
586+
// Notify the main thread even if counts already match,
587+
// in case it's currently blocked in wait_for_usec / wait.
586588
control_cond_var.notify_all();
587589
}
588590

core/os/condition_variable.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
#define THREADING_NAMESPACE std
5353
#endif
5454

55+
#include <chrono>
56+
5557
/// An object one or multiple threads can wait on a be notified by some other.
5658
/// Normally, you want to use a semaphore for such scenarios, but when the
5759
/// condition is something different than a count being greater than zero
@@ -71,6 +73,17 @@ class ConditionVariable {
7173
condition.wait(p_lock.mutex._get_lock());
7274
}
7375

76+
/// @return `true` if notified before timeout, false if timed out.
77+
template <typename BinaryMutexT>
78+
_ALWAYS_INLINE_ bool wait_for_usec(const MutexLock<BinaryMutexT> &p_lock, uint64_t p_usec) const {
79+
return condition.wait_for(p_lock._get_lock(), std::chrono::microseconds(p_usec)) == THREADING_NAMESPACE::cv_status::no_timeout;
80+
}
81+
82+
template <int Tag>
83+
_ALWAYS_INLINE_ bool wait_for_usec(const MutexLock<SafeBinaryMutex<Tag>> &p_lock, uint64_t p_usec) const {
84+
return condition.wait_for(p_lock.mutex._get_lock(), std::chrono::microseconds(p_usec)) == THREADING_NAMESPACE::cv_status::no_timeout;
85+
}
86+
7487
_ALWAYS_INLINE_ void notify_one() const {
7588
condition.notify_one();
7689
}
@@ -81,13 +94,19 @@ class ConditionVariable {
8194
};
8295

8396
#else // No threads.
84-
8597
class ConditionVariable {
8698
public:
8799
template <typename BinaryMutexT>
88100
void wait(const MutexLock<BinaryMutexT> &p_lock) const {}
101+
template <int Tag>
102+
void wait(const MutexLock<SafeBinaryMutex<Tag>> &p_lock) const {}
89103
void notify_one() const {}
90104
void notify_all() const {}
105+
106+
template <typename BinaryMutexT>
107+
bool wait_for_usec(const MutexLock<BinaryMutexT> &p_lock, uint64_t p_usec) const { return true; }
108+
template <int Tag>
109+
bool wait_for_usec(const MutexLock<SafeBinaryMutex<Tag>> &p_lock, uint64_t p_usec) const { return true; }
91110
};
92111

93112
#endif // THREADS_ENABLED

drivers/gles3/rasterizer_gles3.cpp

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -489,23 +489,13 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c
489489
texture_storage->texture_2d_initialize(texture, p_image);
490490

491491
Rect2 imgrect(0, 0, p_image->get_width(), p_image->get_height());
492+
// Size2 win_size_f = Size2(win_size); // This is needed for the .floor() function below because Size2i does not have a floor() function (but Size2 does)
492493
Rect2 screenrect;
493-
if (p_scale) {
494-
if (win_size.width > win_size.height) {
495-
//scale horizontally
496-
screenrect.size.y = win_size.height;
497-
screenrect.size.x = imgrect.size.x * win_size.height / imgrect.size.y;
498-
screenrect.position.x = (win_size.width - screenrect.size.x) / 2;
499494

500-
} else {
501-
//scale vertically
502-
screenrect.size.x = win_size.width;
503-
screenrect.size.y = imgrect.size.y * win_size.width / imgrect.size.x;
504-
screenrect.position.y = (win_size.height - screenrect.size.y) / 2;
505-
}
495+
if (p_scale) {
496+
screenrect = OS::get_singleton()->calculate_boot_screen_rect(win_size, imgrect.size);
506497
} else {
507-
screenrect = imgrect;
508-
screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor();
498+
screenrect = DisplayServer::calculate_boot_image_rect(win_size, imgrect);
509499
}
510500

511501
#ifdef WINDOWS_ENABLED
@@ -530,6 +520,43 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c
530520

531521
gl_end_frame(true);
532522

523+
// After the first commit, a tiling compositor (e.g. Hyprland via XWayland)
524+
// may have sent a ConfigureNotify with the actual window size.
525+
// Pump events and re-render if the size changed.
526+
DisplayServer::get_singleton()->pump_resize_events();
527+
Size2i new_win_size = DisplayServer::get_singleton()->window_get_size();
528+
529+
if (new_win_size != win_size && new_win_size.width > 0 && new_win_size.height > 0) {
530+
win_size = new_win_size;
531+
glViewport(0, 0, win_size.width, win_size.height);
532+
glClearColor(p_color.r, p_color.g, p_color.b,
533+
OS::get_singleton()->is_layered_allowed() ? p_color.a : 1.0);
534+
glClear(GL_COLOR_BUFFER_BIT);
535+
536+
Rect2 screenrect2;
537+
if (p_scale) {
538+
screenrect2 = OS::get_singleton()->calculate_boot_screen_rect(win_size, imgrect.size);
539+
} else {
540+
screenrect2 = DisplayServer::calculate_boot_image_rect(win_size, imgrect);
541+
}
542+
#ifdef WINDOWS_ENABLED
543+
if (!screen_flipped_y)
544+
#endif
545+
{
546+
screenrect2.position.y = win_size.y - screenrect2.position.y;
547+
screenrect2.size.y = -screenrect2.size.y;
548+
}
549+
screenrect2.position /= win_size;
550+
screenrect2.size /= win_size;
551+
552+
GLES3::Texture *t2 = texture_storage->get_texture(texture);
553+
glActiveTexture(GL_TEXTURE0);
554+
glBindTexture(GL_TEXTURE_2D, t2->tex_id);
555+
copy_effects->copy_to_rect(screenrect2);
556+
glBindTexture(GL_TEXTURE_2D, 0);
557+
gl_end_frame(true);
558+
}
559+
533560
texture_storage->texture_free(texture);
534561
}
535562

editor/doc/editor_help.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3061,6 +3061,8 @@ void EditorHelp::_load_doc_thread(void *p_udata) {
30613061
}
30623062

30633063
OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
3064+
3065+
_worker_thread_done.set();
30643066
}
30653067

30663068
void EditorHelp::_gen_doc_thread(void *p_udata) {
@@ -3094,6 +3096,8 @@ void EditorHelp::_gen_doc_thread(void *p_udata) {
30943096
}
30953097

30963098
OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
3099+
3100+
_worker_thread_done.set();
30973101
}
30983102

30993103
void EditorHelp::_gen_extensions_docs() {
@@ -3109,6 +3113,10 @@ static void _load_script_doc_cache(bool p_changes) {
31093113
}
31103114

31113115
void EditorHelp::load_script_doc_cache() {
3116+
if (_cleanup_in_progress.is_set()) {
3117+
return;
3118+
}
3119+
31123120
if (!ProjectSettings::get_singleton()->is_project_loaded()) {
31133121
print_verbose("Skipping loading script doc cache since no project is open.");
31143122
return;
@@ -3132,6 +3140,7 @@ void EditorHelp::load_script_doc_cache() {
31323140
return;
31333141
}
31343142

3143+
_worker_thread_done.set_to(false);
31353144
worker_thread.start(_load_script_doc_cache_thread, nullptr);
31363145
}
31373146

@@ -3152,13 +3161,18 @@ void EditorHelp::_process_postponed_docs() {
31523161

31533162
void EditorHelp::_load_script_doc_cache_thread(void *p_udata) {
31543163
ERR_FAIL_COND_MSG(!ProjectSettings::get_singleton()->is_project_loaded(), "Error: cannot load script doc cache without a project.");
3164+
_worker_thread_done.set();
3165+
return;
31553166
ERR_FAIL_COND_MSG(!ResourceLoader::exists(get_script_doc_cache_full_path()), "Error: cannot load script doc cache from inexistent file.");
3167+
_worker_thread_done.set();
3168+
return;
31563169

31573170
Ref<Resource> script_doc_cache_res = ResourceLoader::load(get_script_doc_cache_full_path(), "", ResourceFormatLoader::CACHE_MODE_IGNORE);
31583171
if (script_doc_cache_res.is_null()) {
31593172
print_verbose("Script doc cache is corrupted. Regenerating it instead.");
31603173
_delete_script_doc_cache();
31613174
callable_mp_static(EditorHelp::regenerate_script_doc_cache).call_deferred();
3175+
_worker_thread_done.set();
31623176
return;
31633177
}
31643178

@@ -3175,6 +3189,8 @@ void EditorHelp::_load_script_doc_cache_thread(void *p_udata) {
31753189

31763190
// Always delete the doc cache after successful load since most uses of editor will change a script, invalidating cache.
31773191
_delete_script_doc_cache();
3192+
3193+
_worker_thread_done.set();
31783194
}
31793195

31803196
/// Helper method to deal with "sources_changed" signal having a parameter.
@@ -3183,6 +3199,10 @@ static void _regenerate_script_doc_cache(bool p_changes) {
31833199
}
31843200

31853201
void EditorHelp::regenerate_script_doc_cache() {
3202+
if (_cleanup_in_progress.is_set()) {
3203+
return;
3204+
}
3205+
31863206
if (EditorFileSystem::get_singleton()->is_scanning()) {
31873207
// Wait until EditorFileSystem scanning is complete to use updated filesystem structure.
31883208
EditorFileSystem::get_singleton()->connect(SNAME("sources_changed"), callable_mp_static(_regenerate_script_doc_cache), CONNECT_ONE_SHOT);
@@ -3191,6 +3211,7 @@ void EditorHelp::regenerate_script_doc_cache() {
31913211

31923212
_wait_for_thread(worker_thread);
31933213
_wait_for_thread(loader_thread);
3214+
_loader_thread_done.set_to(false);
31943215
loader_thread.start(_regen_script_doc_thread, EditorFileSystem::get_singleton()->get_filesystem());
31953216
}
31963217

@@ -3200,6 +3221,8 @@ void EditorHelp::_finish_regen_script_doc_thread(void *p_udata) {
32003221
_script_docs_loaded.set();
32013222

32023223
OS::get_singleton()->benchmark_end_measure("EditorHelp", "Generate Script Documentation");
3224+
3225+
_worker_thread_done.set();
32033226
}
32043227

32053228
void EditorHelp::_regen_script_doc_thread(void *p_udata) {
@@ -3215,6 +3238,9 @@ void EditorHelp::_regen_script_doc_thread(void *p_udata) {
32153238

32163239
_reload_scripts_documentation(dir);
32173240

3241+
_loader_thread_done.set();
3242+
_worker_thread_done.set_to(false); // worker_thread is about to start
3243+
32183244
// All ResourceLoader::load() calls are done, so we can no longer deadlock with main thread.
32193245
// Switch to back to worker_thread from loader_thread to resynchronize access to DocData.
32203246
worker_thread.start(_finish_regen_script_doc_thread, nullptr);
@@ -3265,6 +3291,10 @@ void EditorHelp::save_script_doc_cache() {
32653291
}
32663292

32673293
void EditorHelp::generate_doc(bool p_use_cache, bool p_use_script_cache) {
3294+
if (_cleanup_in_progress.is_set()) {
3295+
return;
3296+
}
3297+
32683298
doc_generation_count++;
32693299
OS::get_singleton()->benchmark_begin_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
32703300

@@ -3280,10 +3310,12 @@ void EditorHelp::generate_doc(bool p_use_cache, bool p_use_script_cache) {
32803310
}
32813311

32823312
if (p_use_cache && FileAccess::exists(get_cache_full_path())) {
3313+
_worker_thread_done.set_to(false);
32833314
worker_thread.start(_load_doc_thread, (void *)p_use_script_cache);
32843315
} else {
32853316
print_verbose("Regenerating editor help cache");
32863317
doc->generate();
3318+
_worker_thread_done.set_to(false);
32873319
worker_thread.start(_gen_doc_thread, (void *)p_use_script_cache);
32883320
}
32893321
}
@@ -3376,7 +3408,28 @@ void EditorHelp::update_doc() {
33763408
}
33773409

33783410
void EditorHelp::cleanup_doc() {
3379-
_wait_for_thread();
3411+
_cleanup_in_progress.set();
3412+
3413+
// loader_thread runs _regen_script_doc_thread which calls ResourceLoader::load().
3414+
// Flush the message queue so load tasks can dispatch to the main thread.
3415+
if (loader_thread.is_started()) {
3416+
while (!_loader_thread_done.is_set()) {
3417+
MessageQueue::get_singleton()->flush();
3418+
OS::get_singleton()->delay_usec(1000);
3419+
}
3420+
loader_thread.wait_to_finish();
3421+
}
3422+
3423+
// worker_thread runs _load_doc_thread, _gen_doc_thread, _load_script_doc_cache_thread,
3424+
// or _finish_regen_script_doc_thread. All may post deferred calls needing the main thread.
3425+
if (worker_thread.is_started()) {
3426+
while (!_worker_thread_done.is_set()) {
3427+
MessageQueue::get_singleton()->flush();
3428+
OS::get_singleton()->delay_usec(1000);
3429+
}
3430+
worker_thread.wait_to_finish();
3431+
}
3432+
33803433
memdelete(doc);
33813434
doc = nullptr;
33823435
}

editor/doc/editor_help.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ class EditorHelp : public VBoxContainer {
204204
inline static Thread loader_thread; ///< Only load scripts here to avoid deadlocking with main thread.
205205

206206
inline static SafeFlag _script_docs_loaded = SafeFlag(false);
207+
inline static SafeFlag _worker_thread_done = SafeFlag(false);
208+
inline static SafeFlag _loader_thread_done = SafeFlag(false);
209+
inline static SafeFlag _cleanup_in_progress = SafeFlag(false);
210+
207211
inline static LocalVector<DocData::ClassDoc> _docs_to_add;
208212
inline static LocalVector<String> _docs_to_remove;
209213
inline static LocalVector<String> _docs_to_remove_by_path;

editor/file_system/editor_file_system.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1730,8 +1730,9 @@ void EditorFileSystem::_notification(int p_what) {
17301730
case NOTIFICATION_EXIT_TREE: {
17311731
Thread &active_thread = thread.is_started() ? thread : thread_sources;
17321732
if (use_threads && active_thread.is_started()) {
1733+
const uint64_t TIMEOUT_USEC = 1000;
17331734
while (scanning) {
1734-
OS::get_singleton()->delay_usec(1000);
1735+
OS::get_singleton()->delay_usec(TIMEOUT_USEC);
17351736
}
17361737
active_thread.wait_to_finish();
17371738
WARN_PRINT("Scan thread aborted...");

editor/inspector/editor_resource_preview.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,14 @@ void EditorResourcePreview::stop() {
600600
preview_generators.write[i]->abort();
601601
}
602602

603+
const uint64_t TIMEOUT_USEC = 3000000; // 3 seconds
604+
uint64_t start = OS::get_singleton()->get_ticks_usec();
605+
603606
while (!exited.is_set()) {
604-
// Sync pending work.
607+
if (OS::get_singleton()->get_ticks_usec() - start > TIMEOUT_USEC) {
608+
WARN_PRINT("EditorResourcePreview: Timed out waiting for preview thread.");
609+
break;
610+
}
605611
OS::get_singleton()->delay_usec(10000);
606612
RenderingServer::get_singleton()->sync();
607613
MessageQueue::get_singleton()->flush();

0 commit comments

Comments
 (0)