Files
eden/src/core/cpu_manager.cpp
T
CamilleLaVey 3f52bf4b4b [core, gpu, threads] Multithreading refactor (#4254)
This insufferable work tries to cover some holes on previous threading implementation from yuzu's team, starting with Windows and Linux reordering of priorities (NICE), reworks previous Android's threading and cpu affinity with adpf, adjust emulated clocks/gpu for better "accuracy" with their work, bumps android minSDK for all flavors, legacy will now work with AP 29 to cover A10 - A12, standard will reach A13 as base and finally the optimized build will come with API 35, mostly targeted on devices with A15 support and newer, NDK and AGP wasn't upgraded yet. The performance cost efficiency have been improved based on device power configuration; preventing overheating if certain devices tended to fall into NICE0 (not allocated threads priority, all task ran with higher priority, 11 tasks running within the limited 2 - 7 threads available on the most common configuration 1x3x4 or 1x4x3).

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4254
2026-08-08 04:39:47 +02:00

204 lines
6.7 KiB
C++

// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fiber.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/hle/kernel/k_interrupt_manager.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "video_core/gpu.h"
namespace Core {
CpuManager::CpuManager(System& system_) : system{system_} {}
CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier.emplace(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++)
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) {
RunThread(token, core);
});
}
void CpuManager::Shutdown() {
for (std::size_t core = 0; core < num_cores; core++) {
if (core_data[core].host_thread.joinable()) {
core_data[core].host_thread.request_stop();
core_data[core].host_thread.join();
}
}
}
void CpuManager::GuestThreadFunction(Kernel::KernelCore& kernel) {
if (is_multicore) {
MultiCoreRunGuestThread(kernel);
} else {
SingleCoreRunGuestThread(kernel);
}
}
void CpuManager::IdleThreadFunction(Kernel::KernelCore& kernel) {
if (is_multicore) {
MultiCoreRunIdleThread(kernel);
} else {
SingleCoreRunIdleThread(kernel);
}
}
void CpuManager::ShutdownThreadFunction(Kernel::KernelCore& kernel) {
ShutdownThread(kernel);
}
void CpuManager::HandleInterrupt(Kernel::KernelCore& kernel) {
auto core_index = kernel.CurrentPhysicalCoreIndex();
Kernel::KInterruptManager::HandleInterrupt(kernel, s32(core_index));
}
///////////////////////////////////////////////////////////////////////////////
/// MultiCore ///
///////////////////////////////////////////////////////////////////////////////
void CpuManager::MultiCoreRunGuestThread(Kernel::KernelCore& kernel) {
// Similar to UserModeThreadStarter in HOS
auto* thread = Kernel::GetCurrentThreadPointer(kernel);
kernel.CurrentScheduler()->OnThreadStart(kernel);
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
while (!physical_core->IsInterrupted()) {
physical_core->RunThread(kernel, thread);
physical_core = &kernel.CurrentPhysicalCore();
}
HandleInterrupt(kernel);
}
}
void CpuManager::MultiCoreRunIdleThread(Kernel::KernelCore& kernel) {
// Not accurate to HOS. Remove this entire method when singlecore is removed.
// See notes in KScheduler::ScheduleImpl for more information about why this
// is inaccurate.
kernel.CurrentScheduler()->OnThreadStart(kernel);
while (true) {
auto& physical_core = kernel.CurrentPhysicalCore();
if (!physical_core.IsInterrupted()) {
physical_core.Idle();
}
HandleInterrupt(kernel);
}
}
///////////////////////////////////////////////////////////////////////////////
/// SingleCore ///
///////////////////////////////////////////////////////////////////////////////
void CpuManager::SingleCoreRunGuestThread(Kernel::KernelCore& kernel) {
auto* thread = Kernel::GetCurrentThreadPointer(kernel);
kernel.CurrentScheduler()->OnThreadStart(kernel);
while (true) {
auto* physical_core = &kernel.CurrentPhysicalCore();
if (!physical_core->IsInterrupted()) {
physical_core->RunThread(kernel, thread);
physical_core = &kernel.CurrentPhysicalCore();
}
kernel.SetIsPhantomModeForSingleCore(true);
system.CoreTiming().Advance();
kernel.SetIsPhantomModeForSingleCore(false);
PreemptSingleCore(kernel);
HandleInterrupt(kernel);
}
}
void CpuManager::SingleCoreRunIdleThread(Kernel::KernelCore& kernel) {
kernel.CurrentScheduler()->OnThreadStart(kernel);
while (true) {
PreemptSingleCore(kernel, false);
system.CoreTiming().AddTicks(1000U);
idle_count++;
HandleInterrupt(kernel);
}
}
void CpuManager::PreemptSingleCore(Kernel::KernelCore& kernel, bool from_running_environment) {
if (idle_count >= 4 || from_running_environment) {
if (!from_running_environment) {
system.CoreTiming().Idle();
idle_count = 0;
}
kernel.SetIsPhantomModeForSingleCore(true);
system.CoreTiming().Advance();
kernel.SetIsPhantomModeForSingleCore(false);
}
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
system.CoreTiming().ResetTicks();
kernel.Scheduler(current_core).PreemptSingleCore(kernel);
// We've now been scheduled again, and we may have exchanged schedulers.
// Reload the scheduler in case it's different.
if (!kernel.Scheduler(current_core).IsIdle()) {
idle_count = 0;
}
}
void CpuManager::GuestActivate(Kernel::KernelCore& kernel) {
// Similar to the HorizonKernelMain callback in HOS
auto* scheduler = kernel.CurrentScheduler();
scheduler->Activate(kernel);
UNREACHABLE();
}
void CpuManager::ShutdownThread(Kernel::KernelCore& kernel) {
auto* thread = kernel.GetCurrentEmuThread();
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context);
UNREACHABLE();
}
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadToPerformanceCores();
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();
// Cleanup
SCOPE_EXIT {
data.host_context->Exit();
};
// Running
if (!gpu_barrier->Sync(token)) {
return;
}
if (!is_async_gpu && !is_multicore) {
system.GPU().ObtainContext();
}
auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread();
Kernel::SetCurrentThread(kernel, thread);
Common::Fiber::YieldTo(data.host_context, *thread->GetHostContext());
}
} // namespace Core