diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 74d779b0a9..58495ae0cd 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -452,6 +452,7 @@ void Config::ReadCoreValues() {
if (global) {
ReadBasicSetting(Settings::values.use_cpu_jit);
+ ReadBasicSetting(Settings::values.delay_start_for_lle_modules);
}
qt_config->endGroup();
@@ -979,6 +980,7 @@ void Config::SaveCoreValues() {
if (global) {
WriteBasicSetting(Settings::values.use_cpu_jit);
+ WriteBasicSetting(Settings::values.delay_start_for_lle_modules);
}
qt_config->endGroup();
diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp
index 98d95c9e03..9143959e3b 100644
--- a/src/citra_qt/configuration/configure_debug.cpp
+++ b/src/citra_qt/configuration/configure_debug.cpp
@@ -94,6 +94,8 @@ void ConfigureDebug::SetConfiguration() {
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue());
+ ui->delay_start_for_lle_modules->setChecked(
+ Settings::values.delay_start_for_lle_modules.GetValue());
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
@@ -125,6 +127,7 @@ void ConfigureDebug::ApplyConfiguration() {
filter.ParseFilterString(Settings::values.log_filter.GetValue());
Common::Log::SetGlobalFilter(filter);
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
+ Settings::values.delay_start_for_lle_modules = ui->delay_start_for_lle_modules->isChecked();
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui
index 20b84441cb..faf8b9ee0e 100644
--- a/src/citra_qt/configuration/configure_debug.ui
+++ b/src/citra_qt/configuration/configure_debug.ui
@@ -219,6 +219,25 @@
+ -
+
+
+ Miscellaneus
+
+
+
-
+
+
+ <html><head/><body><p>Introduces a delay to the first ever launched app thread if LLE modules are enabled, to allow them to initialize.</p></body></html>
+
+
+ Delay app start for LLE module initialization
+
+
+
+
+
+
-
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 486340a3fe..e7e3d59f15 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -141,6 +141,7 @@ void LogSettings() {
log_setting("System_RegionValue", values.region_value.GetValue());
log_setting("System_PluginLoader", values.plugin_loader_enabled.GetValue());
log_setting("System_PluginLoaderAllowed", values.allow_plugin_loader.GetValue());
+ log_setting("Debugging_DelayStartForLLEModules", values.delay_start_for_lle_modules.GetValue());
log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue());
log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue());
}
diff --git a/src/common/settings.h b/src/common/settings.h
index 3b51e5dd45..8fc0f36c42 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -529,6 +529,7 @@ struct Values {
// Debugging
bool record_frame_times;
std::unordered_map lle_modules;
+ Setting delay_start_for_lle_modules{true, "delay_start_for_lle_modules"};
Setting use_gdbstub{false, "use_gdbstub"};
Setting gdbstub_port{24689, "gdbstub_port"};
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index db0caf59c5..9df6e75ddb 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -183,6 +183,7 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
ar& next_thread_id;
ar& memory_mode;
ar& n3ds_hw_caps;
+ ar& main_thread_extended_sleep;
// Deliberately don't include debugger info to allow debugging through loads
if (Archive::is_loading::value) {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index e9f77be6c2..8c3f2b35c8 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -185,12 +185,14 @@ public:
* @param processor_id The ID(s) of the processors on which the thread is desired to be run
* @param stack_top The address of the thread's stack top
* @param owner_process The parent process for the thread
+ * @param make_ready If the thread should be put in the ready queue
* @return A shared pointer to the newly created thread
*/
ResultVal> CreateThread(std::string name, VAddr entry_point,
u32 priority, u32 arg, s32 processor_id,
VAddr stack_top,
- std::shared_ptr owner_process);
+ std::shared_ptr owner_process,
+ bool make_ready = true);
/**
* Creates a semaphore.
@@ -338,6 +340,15 @@ public:
Core::Timing& timing;
+ /// Sleep main thread of the first ever launched non-sysmodule process.
+ void SetAppMainThreadExtendedSleep(bool requires_sleep) {
+ main_thread_extended_sleep = requires_sleep;
+ }
+
+ bool GetAppMainThreadExtendedSleep() const {
+ return main_thread_extended_sleep;
+ }
+
private:
void MemoryInit(MemoryMode memory_mode, New3dsMemoryMode n3ds_mode, u64 override_init_time);
@@ -386,6 +397,14 @@ private:
*/
std::recursive_mutex hle_lock;
+ /*
+ * Flags non system module main threads to wait a bit before running. On real hardware,
+ * system modules have plenty of time to load before the game is loaded, but on citra they
+ * start at the same time as the game. The artificial wait gives system modules some time
+ * to load and setup themselves before the game starts.
+ */
+ bool main_thread_extended_sleep = false;
+
friend class boost::serialization::access;
template
void serialize(Archive& ar, const unsigned int file_version);
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 83a2ba8f76..0bcca61772 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -10,8 +10,10 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/serialization/boost_flat_set.h"
+#include "common/settings.h"
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
+#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
@@ -324,7 +326,7 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, u32
ResultVal> KernelSystem::CreateThread(
std::string name, VAddr entry_point, u32 priority, u32 arg, s32 processor_id, VAddr stack_top,
- std::shared_ptr owner_process) {
+ std::shared_ptr owner_process, bool make_ready) {
// Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > ThreadPrioLowest) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
@@ -367,8 +369,11 @@ ResultVal> KernelSystem::CreateThread(
// to initialize the context
ResetThreadContext(thread->context, stack_top, entry_point, arg);
- thread_managers[processor_id]->ready_queue.push_back(thread->current_priority, thread.get());
- thread->status = ThreadStatus::Ready;
+ if (make_ready) {
+ thread_managers[processor_id]->ready_queue.push_back(thread->current_priority,
+ thread.get());
+ thread->status = ThreadStatus::Ready;
+ }
return thread;
}
@@ -405,16 +410,36 @@ void Thread::BoostPriority(u32 priority) {
std::shared_ptr SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
std::shared_ptr owner_process) {
+
+ constexpr s64 sleep_app_thread_ns = 2'600'000'000LL;
+ constexpr u32 system_module_tid_high = 0x00040130;
+
+ const bool is_lle_service =
+ static_cast(owner_process->codeset->program_id >> 32) == system_module_tid_high;
+
+ s64 sleep_time_ns = 0;
+ if (!is_lle_service && kernel.GetAppMainThreadExtendedSleep()) {
+ if (Settings::values.delay_start_for_lle_modules) {
+ sleep_time_ns = sleep_app_thread_ns;
+ }
+ kernel.SetAppMainThreadExtendedSleep(false);
+ }
+
// Initialize new "main" thread
auto thread_res =
kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor,
- Memory::HEAP_VADDR_END, owner_process);
+ Memory::HEAP_VADDR_END, owner_process, sleep_time_ns == 0);
std::shared_ptr thread = std::move(thread_res).Unwrap();
thread->context.fpscr =
FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
+ if (sleep_time_ns != 0) {
+ thread->status = ThreadStatus::WaitSleep;
+ thread->WakeAfterDelay(sleep_time_ns);
+ }
+
// Note: The newly created thread will be run when the scheduler fires.
return thread;
}
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 118ea32dd1..133bfce209 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -214,10 +214,20 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) {
/// Initialize ServiceManager
void Init(Core::System& core) {
SM::ServiceManager::InstallInterfaces(core);
+ core.Kernel().SetAppMainThreadExtendedSleep(false);
+ bool lle_module_present = false;
for (const auto& service_module : service_module_map) {
- if (!AttemptLLE(service_module) && service_module.init_function != nullptr)
+ const bool has_lle = AttemptLLE(service_module);
+ if (!has_lle && service_module.init_function != nullptr) {
service_module.init_function(core);
+ }
+ lle_module_present |= has_lle;
+ }
+ if (lle_module_present) {
+ // If there is at least one LLE module, tell the kernel to
+ // add a extended sleep to the app main thread (if option enabled).
+ core.Kernel().SetAppMainThreadExtendedSleep(true);
}
LOG_DEBUG(Service, "initialized OK");
}