diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp index c1724496db..83d000df35 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp @@ -272,6 +272,7 @@ void OpusDecoder::Main(std::stop_token stop_token) { void OpusDecoder::NotifyShutdown() { init_thread.request_stop(); main_thread.request_stop(); + Send(Direction::DSP, Message::Shutdown); } } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 3b7ede9b2a..c84cdc99c9 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -1242,8 +1242,7 @@ namespace Kernel { if (m_termination_requested.compare_exchange_strong(expected, true)) { // If the thread is in initialized state, just change state to terminated. if (this->GetState() == ThreadState::Initialized) { - m_thread_state = ThreadState::Terminated; - return ThreadState::Terminated; + return m_thread_state = ThreadState::Terminated; } // Register the terminating dpc. @@ -1277,7 +1276,6 @@ namespace Kernel { m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true); } } - return this->GetState(); } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 57668ad02d..580aae8510 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -80,7 +80,10 @@ struct KernelCore::Impl { // so it will be statically given a TLS slot anyways. static inline thread_local ThreadLocalData tls_data = {}; - explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} { + explicit Impl(Core::System& system_, KernelCore& kernel_) + : worker_task_manager{kernel_} + , system{system_} + { tls_data.lock = true; } @@ -1257,7 +1260,6 @@ void KernelCore::ShutdownCores() { for (auto& sm : impl->server_managers) { sm->NotifyShutdown(); } - impl->TerminateAllProcesses(); KScopedSchedulerLock lk{*this}; for (auto* thread : impl->shutdown_threads) diff --git a/src/core/hle/service/am/event_observer.cpp b/src/core/hle/service/am/event_observer.cpp index 520000b64f..dcec3c9771 100644 --- a/src/core/hle/service/am/event_observer.cpp +++ b/src/core/hle/service/am/event_observer.cpp @@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system) m_window_system.SetEventObserver(this); m_wakeup_holder.SetUserData(static_cast(UserDataTag::WakeupEvent)); m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait)); - m_thread = std::jthread([this](std::stop_token stop_token) { - Common::SetCurrentThreadName("am:EventObserver"); + system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() { + auto const stop_token = m_stop_source.get_token(); while (!stop_token.stop_requested()) { auto* signaled_holder = this->WaitSignaled(stop_token); - if (!signaled_holder) + if (stop_token.stop_requested() || !signaled_holder) break; this->Process(signaled_holder); } + m_process_cv.notify_one(); }); } EventObserver::~EventObserver() { // Signal thread and wait for processing to finish. - if (m_thread.joinable()) { - // Signal thread and wait for processing to finish. - m_thread.request_stop(); - m_wakeup_event.Signal(m_system.Kernel()); - m_thread.join(); + m_stop_source.request_stop(); + m_wakeup_event.Signal(m_system.Kernel()); + { + std::unique_lock lk{m_process_mutex}; + m_process_cv.wait(lk); } // Free remaining owned sessions. diff --git a/src/core/hle/service/am/event_observer.h b/src/core/hle/service/am/event_observer.h index c66eb711c8..d57558e946 100644 --- a/src/core/hle/service/am/event_observer.h +++ b/src/core/hle/service/am/event_observer.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include "common/polyfill_thread.h" #include "common/thread.h" #include "core/hle/service/kernel_helpers.h" @@ -62,7 +64,9 @@ private: MultiWait m_deferred_wait_list; // Processing thread. - std::jthread m_thread{}; + std::stop_source m_stop_source{}; + std::mutex m_process_mutex; + std::condition_variable m_process_cv{}; }; } // namespace Service::AM diff --git a/src/core/hle/service/glue/time/worker.cpp b/src/core/hle/service/glue/time/worker.cpp index 2abac82772..b7f2c3daac 100644 --- a/src/core/hle/service/glue/time/worker.cpp +++ b/src/core/hle/service/glue/time/worker.cpp @@ -49,14 +49,15 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady } TimeWorker::~TimeWorker() { + // Wait for processing to stop + m_stop_source.request_stop(); m_local_clock_event->Signal(m_system.Kernel()); m_network_clock_event->Signal(m_system.Kernel()); m_ephemeral_clock_event->Signal(m_system.Kernel()); - - if (m_thread.joinable()) { - m_thread.request_stop(); - m_event->Signal(m_system.Kernel()); - m_thread.join(); + m_event->Signal(m_system.Kernel()); + { + std::unique_lock lk{m_process_mutex}; + m_process_cv.wait(lk); } m_ctx.CloseEvent(m_event); @@ -125,9 +126,8 @@ void TimeWorker::Initialize(std::shared_ptr t } void TimeWorker::StartThread() { - m_thread = m_system.Kernel().RunOnHostCoreThread("TimeWorker", [this]() { - auto const stop_token = m_thread.get_stop_token(); - Common::SetCurrentThreadPriority(Common::ThreadPriority::Low); + m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() { + auto const stop_token = m_stop_source.get_token(); while (!stop_token.stop_requested()) { enum class EventType : s32 { Exit = 0, @@ -140,9 +140,7 @@ void TimeWorker::StartThread() { UpdateFileTimestamp = 7, AutoCorrect = 8, }; - s32 index{}; - if (m_pm_state_change_handler.m_priority != 0) { // TODO: gIPmModuleService::GetEvent() 1 index = WaitAny( @@ -282,6 +280,7 @@ void TimeWorker::StartThread() { UNREACHABLE(); } } + m_process_cv.notify_one(); }); } diff --git a/src/core/hle/service/glue/time/worker.h b/src/core/hle/service/glue/time/worker.h index e19cbf370f..be84b802a7 100644 --- a/src/core/hle/service/glue/time/worker.h +++ b/src/core/hle/service/glue/time/worker.h @@ -6,6 +6,7 @@ #pragma once +#include #include "common/common_types.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/glue/time/alarm_worker.h" @@ -39,13 +40,14 @@ public: template T GetSettingsItemValue(const std::string& category, const std::string& name); - void ThreadFunc(std::stop_token stop_token); - Core::System& m_system; KernelHelpers::ServiceContext m_ctx; std::shared_ptr m_set_sys; - std::jthread m_thread; + std::mutex m_process_mutex; + std::condition_variable m_process_cv; + std::stop_source m_stop_source; + Kernel::KEvent* m_event{}; std::shared_ptr m_time_m; std::shared_ptr m_time_sm; diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index de18fedd19..ee6a385291 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -94,9 +94,6 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"ldn", &LDN::LoopProcess}, {"nvservices", &Nvidia::LoopProcess}, {"bsdsocket", &Sockets::LoopProcess}, - // TODO: glue is only required here as otherwise the dummythread doesn't close - // TODO: rework sched so this isn't needed - {"glue", &Glue::LoopProcess}, }) kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach(); kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach(); @@ -155,6 +152,7 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"usb", &USB::LoopProcess}, {"i2c", &I2C::LoopProcess}, {"gpio", &GPIO::LoopProcess}, + {"glue", &Glue::LoopProcess}, }) kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); } diff --git a/src/core/hle/service/vi/conductor.cpp b/src/core/hle/service/vi/conductor.cpp index a2f5058e19..f67650a8fd 100644 --- a/src/core/hle/service/vi/conductor.cpp +++ b/src/core/hle/service/vi/conductor.cpp @@ -30,13 +30,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di }); if (system.IsMulticore()) { - m_event = Core::Timing::CreateEvent( - "ScreenComposition", - [this](s64 time, - std::chrono::nanoseconds ns_late) -> std::optional { - m_signal.Set(); - return std::chrono::nanoseconds(this->GetNextTicks()); - }); + m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { + m_signal.Set(); + return std::chrono::nanoseconds(this->GetNextTicks()); + }); system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event); m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() { @@ -52,13 +49,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di } }); } else { - m_event = Core::Timing::CreateEvent( - "ScreenComposition", - [this](s64 time, - std::chrono::nanoseconds ns_late) -> std::optional { - this->ProcessVsync(); - return std::chrono::nanoseconds(this->GetNextTicks()); - }); + m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { + this->ProcessVsync(); + return std::chrono::nanoseconds(this->GetNextTicks()); + }); system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event); } diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp index be48226c60..1480e7871b 100644 --- a/src/core/memory/dmnt_cheat_vm.cpp +++ b/src/core/memory/dmnt_cheat_vm.cpp @@ -232,10 +232,7 @@ DmntCheatVm::Callbacks::~Callbacks() = default; bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { // If we've ever seen a decode failure, return false. bool valid = decode_success; - CheatVmOpcode opcode = { - .begin_conditional_block = false, - .opcode = {} - }; + CheatVmOpcode opcode = {}; SCOPE_EXIT { decode_success &= valid; if (valid) { diff --git a/src/yuzu_headless/main.cpp b/src/yuzu_headless/main.cpp new file mode 100644 index 0000000000..44f50fb122 --- /dev/null +++ b/src/yuzu_headless/main.cpp @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: 2014 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common/logging.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/cpu_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs/vfs_real.h" +#include "core/hle/service/am/applet_manager.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "frontend_common/config.h" +#include "input_common/main.h" +#include "video_core/gpu.h" +#include "video_core/renderer_base.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h" + +class EmuWindow_Headless : public Core::Frontend::EmuWindow { +public: + explicit EmuWindow_Headless() = default; + ~EmuWindow_Headless() = default; + + bool IsShown() const override { + return true; + } + void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override { + + } + std::unique_ptr CreateSharedContext() const override { + return std::make_unique(); + } +}; + +int main(int argc, char *argv[]) { + struct { + Core::System system{}; + EmuWindow_Headless emu_window{}; + } state = {}; + + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); + + if (argc < 2) { + LOG_CRITICAL(Frontend, "Usage: {} [ms-to-run] [file]", argv[0]); + return EXIT_FAILURE; + } + + std::chrono::milliseconds time_quanta = std::chrono::milliseconds{atoi(argv[1])}; + auto const time_end = std::chrono::steady_clock::now() + time_quanta; + std::string filepath = argv[2]; + + // apply the log_filter setting + // the logger was initialized before and doesn't pick up the filter on its own + Common::Log::Filter filter; + filter.ParseFilterString("*:Info"); + Common::Log::SetGlobalFilter(filter); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); + return EXIT_FAILURE; + } + + state.system.Initialize(); + + InputCommon::InputSubsystem input_subsystem{}; + + // Apply the command line arguments + state.system.ApplySettings(); + Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Null); + + state.system.SetContentProvider(std::make_unique()); + state.system.SetFilesystem(std::make_shared()); + state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem()); + state.system.GetUserChannel().clear(); + + Service::AM::FrontendAppletParameters load_parameters{ + .applet_id = Service::AM::AppletId::Application, + }; + if (auto const load_result = state.system.Load(state.emu_window, filepath, load_parameters); load_result != Core::SystemResultStatus::Success) { + LOG_CRITICAL(Frontend, "load result = {}", u32(load_result)); + // shutdown + void(state.system.Pause()); + state.system.DetachDebugger(); + state.system.ShutdownMainProcess(); + return EXIT_FAILURE; + } + // Core is loaded, start the GPU (makes the GPU contexts current to this thread) + state.system.GPU().Start(); + state.system.GetCpuManager().OnGpuReady(); + // don't do anything, SDL3 already exists for us :D + state.system.RegisterExitCallback([] {}); + void(state.system.Run()); + if (state.system.DebuggerEnabled()) + state.system.InitializeDebugger(); + while (state.system.IsPoweredOn() && !state.system.GetExitRequested()) { + auto const time_now = std::chrono::steady_clock::now(); + if (time_now > time_end) + break; + } + // shutdown + void(state.system.Pause()); + state.system.DetachDebugger(); + state.system.ShutdownMainProcess(); + return EXIT_SUCCESS; +} + +#define VMA_IMPLEMENTATION +#include "video_core/vulkan_common/vma.h"