nxemu/src/nxemu-os/core/core.cpp

824 lines
20 KiB
C++

// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <atomic>
#include <cstdlib>
#include <optional>
#include "yuzu_audio_core/audio_core.h"
#include "yuzu_common/hardware_properties.h"
#include "core/core.h"
#include "os_settings.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/debugger/debugger.h"
#include "core/gpu_dirty_memory_manager.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/os/event.h"
#include "core/hle/service/services.h"
#include "core/internal_network/network.h"
#include "core/perf_stats.h"
#include "core/reporter.h"
#include "yuzu_hid_core/hid_core.h"
#include "yuzu_input_common/main.h"
#include "network/network.h"
#include <nxemu-module-spec/system_loader.h>
#include <nxemu-cpu/cpu_settings_identifiers.h>
#include <nxemu-video/video_settings_identifiers.h>
#ifdef _WIN32
#include "yuzu_common/windows/timer_resolution.h"
#endif
extern IModuleSettings * g_settings;
namespace Core {
struct System::Impl {
explicit Impl(System & system_, ISystemModules & modules_) :
kernel{system_},
hid_core{},
cpu_manager{system_},
room_network{},
applet_manager{system_},
frontend_applets{system_},
modules(modules_),
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
reporter{system_}
{
device_memory = std::make_unique<Core::DeviceMemory>();
}
~Impl()
{
hid_core.UnloadInputDevices();
input_subsystem->Shutdown();
}
void Initialize(System& system)
{
ConfigureRuntime(system);
// These objects outlive individual games and runtime setting changes.
frontend_applets.SetDefaultAppletsIfMissing();
input_subsystem->Initialize();
}
void ConfigureRuntime(System& system)
{
is_multicore = osSettings.use_multi_core;
cpu_hle_requested = osSettings.cpu_hle_synchronization;
cpu_hle_initialized_supported = CpuHleSupported();
cpu_hle_initialized_native_clock = CpuHleUsesNativeClock();
#ifdef _WIN32
const std::chrono::nanoseconds timer_resolution = Common::Windows::SetCurrentTimerResolutionToMaximum();
core_timing.SetTimerResolutionNs(timer_resolution);
LOG_INFO(Core, "Host Timer Resolution: {:.4f} ms", std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(timer_resolution).count());
#endif
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); },
cpu_hle_initialized_supported, cpu_hle_initialized_native_clock);
is_async_gpu = g_settings->GetBool(NXVideoSetting::UseAsynchronousGPUEmulation);
kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore);
cpu_manager.SetAsyncGpu(is_async_gpu);
}
void ReconfigureIfNecessary(System & system)
{
const bool must_reconfigure = is_multicore != osSettings.use_multi_core ||
cpu_hle_requested != osSettings.cpu_hle_synchronization ||
(cpu_hle_requested && cpu_hle_initialized_supported != CpuHleSupported()) ||
(cpu_hle_requested && cpu_hle_initialized_native_clock != CpuHleUsesNativeClock()) ||
core_timing.CpuHleSynchronizationFailed();
if (!must_reconfigure)
{
return;
}
LOG_DEBUG(Kernel, "Reconfiguring runtime timing and CPU settings");
ConfigureRuntime(system);
}
bool CpuHleSupported() const
{
return cpu_hle_guest64;
}
bool CpuHleUsesNativeClock() const
{
#if (defined(_M_ARM64) || defined(ARCHITECTURE_arm64) || defined(__aarch64__)) && (defined(__linux__) || defined(__ANDROID__))
return cpu_hle_guest64 && g_settings->GetBool(NXCpuSetting::NceEnabled);
#else
return false;
#endif
}
void Run()
{
std::unique_lock<std::mutex> lk(suspend_guard);
core_timing.SyncPause(false);
kernel.SuspendEmulation(false);
is_paused.store(false, std::memory_order_relaxed);
}
void Pause()
{
std::unique_lock<std::mutex> lk(suspend_guard);
kernel.SuspendEmulation(true);
core_timing.SyncPause(true);
is_paused.store(true, std::memory_order_relaxed);
}
bool IsPaused() const
{
return is_paused.load(std::memory_order_relaxed);
}
std::unique_lock<std::mutex> StallApplication()
{
std::unique_lock<std::mutex> lk(suspend_guard);
kernel.SuspendEmulation(true);
core_timing.SyncPause(true);
return lk;
}
void UnstallApplication()
{
if (!IsPaused())
{
core_timing.SyncPause(false);
kernel.SuspendEmulation(false);
}
}
void SetNVDECActive(bool is_nvdec_active)
{
nvdec_active.store(is_nvdec_active, std::memory_order_relaxed);
}
bool GetNVDECActive()
{
return nvdec_active.load(std::memory_order_relaxed);
}
void InitializeKernel(System & system, uint64_t titleID, bool is_64_bit)
{
LOG_DEBUG(Core, "initialized OK");
cpu_hle_guest64 = is_64_bit;
// Reconfigure timing before creating guest threads. Input factories and
// frontend applets remain the instances registered during OS startup.
ReconfigureIfNecessary(system);
kernel.Initialize();
cpu_manager.Initialize();
arp_manager.ResetAll();
audio_core = std::make_unique<AudioCore::AudioCore>(system);
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
services =
std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
is_powered_on = true;
exit_locked = false;
exit_requested = false;
perf_stats = std::make_unique<PerfStats>(titleID);
// Reset counters and set time origin to current frame
GetAndResetPerfStats();
perf_stats->BeginSystemFrame();
LOG_DEBUG(Core, "Initialized OK");
}
void ShutdownMainProcess()
{
IVideo & video = modules.Video();
SetShuttingDown(true);
is_powered_on = false;
exit_locked = false;
exit_requested = false;
stop_event.request_stop();
core_timing.SyncPause(false);
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
applet_manager.StopEventObserver();
kernel.ShutdownCores();
applet_manager.Reset();
{
std::scoped_lock lk{general_channel_mutex};
general_channel.clear();
general_channel_event.reset();
general_channel_context.reset();
}
services.reset();
service_manager.reset();
core_timing.ClearPendingEvents();
audio_core.reset();
perf_stats.reset();
cpu_manager.Shutdown();
kernel.Shutdown();
stop_event = {};
Network::RestartSocketOperations();
if (auto room_member = room_network.GetRoomMember().lock())
{
Network::GameInfo game_info{};
room_member->SendGameInfo(game_info);
}
LOG_DEBUG(Core, "Shutdown OK");
}
bool IsShuttingDown() const {
return is_shutting_down;
}
void SetShuttingDown(bool shutting_down) {
is_shutting_down = shutting_down;
}
PerfStatsResults GetAndResetPerfStats() {
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
}
mutable std::mutex suspend_guard;
std::atomic_bool is_paused{};
std::atomic<bool> is_shutting_down{};
Timing::CoreTiming core_timing;
Kernel::KernelCore kernel;
std::unique_ptr<Core::DeviceMemory> device_memory;
std::unique_ptr<AudioCore::AudioCore> audio_core;
Core::HID::HIDCore hid_core;
Network::RoomNetwork room_network;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
bool exit_locked = false;
bool exit_requested = false;
std::atomic<bool> nvdec_active{};
Reporter reporter;
std::array<u8, 0x20> build_id{};
ISystemModules & modules;
/// Applets
Service::AM::AppletManager applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
/// APM (Performance) services
Service::APM::Controller apm_controller{core_timing};
/// Service State
Service::Glue::ARPManager arp_manager;
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// Services
std::unique_ptr<Service::Services> services;
/// Network instance
Network::NetworkInstance network_instance;
std::unique_ptr<Core::PerfStats> perf_stats;
Core::SpeedLimiter speed_limiter;
bool is_multicore{};
bool cpu_hle_requested{};
bool cpu_hle_guest64{true}, cpu_hle_initialized_supported{true};
bool cpu_hle_initialized_native_clock{};
bool is_async_gpu{};
::ExecuteProgramCallback execute_program_callback{};
void * execute_program_user_data{};
::ExitCallback exit_callback{};
void * exit_user_data{};
std::stop_source stop_event;
std::array<Core::GPUDirtyMemoryManager, Hardware::NUM_CPU_CORES>
gpu_dirty_memory_managers;
std::deque<std::vector<u8>> user_channel;
std::vector<std::vector<u8>> general_channel;
std::mutex general_channel_mutex;
std::optional<Service::KernelHelpers::ServiceContext> general_channel_context;
std::optional<Service::Event> general_channel_event;
void EnsureGeneralChannelInitialized(System& system) {
if (!general_channel_event) {
general_channel_context.emplace(system, "GeneralChannel");
general_channel_event.emplace(*general_channel_context);
}
}
Kernel::KProcess * current_process{nullptr};
};
System::System(ISystemModules & modules) :
impl{std::make_unique<Impl>(*this, modules)}
{
}
System::~System() = default;
CpuManager & System::GetCpuManager()
{
return impl->cpu_manager;
}
const CpuManager & System::GetCpuManager() const
{
return impl->cpu_manager;
}
void System::InitializeKernel(uint64_t titleID, bool is_64_bit)
{
impl->InitializeKernel(*this, titleID, is_64_bit);
}
void System::Initialize() {
impl->Initialize(*this);
}
void System::Run()
{
impl->Run();
}
void System::Pause()
{
impl->Pause();
}
bool System::IsPaused() const
{
return impl->IsPaused();
}
bool System::IsShuttingDown() const
{
return impl->IsShuttingDown();
}
void System::SetShuttingDown(bool shutting_down)
{
impl->SetShuttingDown(shutting_down);
}
void System::ShutdownMainProcess()
{
impl->ShutdownMainProcess();
}
std::unique_lock<std::mutex> System::StallApplication()
{
return impl->StallApplication();
}
void System::UnstallApplication()
{
impl->UnstallApplication();
}
void System::SetNVDECActive(bool is_nvdec_active)
{
impl->SetNVDECActive(is_nvdec_active);
}
bool System::GetNVDECActive()
{
return impl->GetNVDECActive();
}
bool System::IsPoweredOn() const
{
return impl->is_powered_on.load(std::memory_order::relaxed);
}
size_t System::GetCurrentHostThreadID() const
{
return impl->kernel.GetCurrentHostThreadID();
}
std::span<GPUDirtyMemoryManager> System::GetGPUDirtyMemoryManager()
{
return impl->gpu_dirty_memory_managers;
}
void System::GatherGPUDirtyMemory(ICacheInvalidator * invalidator)
{
for (auto & manager : impl->gpu_dirty_memory_managers)
{
manager.Gather(invalidator);
}
}
PerfStatsResults System::GetAndResetPerfStats()
{
return impl->GetAndResetPerfStats();
}
ISystemModules & System::GetSystemModules()
{
return impl->modules;
}
IVideo & System::GetVideo()
{
return impl->modules.Video();
}
ISystemloader & System::GetSystemloader()
{
return impl->modules.Systemloader();
}
Kernel::PhysicalCore & System::CurrentPhysicalCore()
{
return impl->kernel.CurrentPhysicalCore();
}
const Kernel::PhysicalCore & System::CurrentPhysicalCore() const
{
return impl->kernel.CurrentPhysicalCore();
}
/// Gets the global scheduler
Kernel::GlobalSchedulerContext & System::GlobalSchedulerContext()
{
return impl->kernel.GlobalSchedulerContext();
}
/// Gets the global scheduler
const Kernel::GlobalSchedulerContext & System::GlobalSchedulerContext() const
{
return impl->kernel.GlobalSchedulerContext();
}
Kernel::KProcess * System::ApplicationProcess()
{
return impl->kernel.ApplicationProcess();
}
void System::SetCurrentProcess(Kernel::KProcess * process)
{
impl->current_process = process;
}
Kernel::KProcess * System::CurrentProcess() const
{
return impl->current_process;
}
Core::DeviceMemory & System::DeviceMemory()
{
return *impl->device_memory;
}
const Core::DeviceMemory & System::DeviceMemory() const
{
return *impl->device_memory;
}
Memory::Memory & System::ApplicationMemory()
{
return impl->kernel.ApplicationProcess()->GetCoreMemory();
}
const Core::Memory::Memory & System::ApplicationMemory() const
{
return impl->kernel.ApplicationProcess()->GetCoreMemory();
}
Kernel::KernelCore & System::Kernel()
{
return impl->kernel;
}
const Kernel::KernelCore & System::Kernel() const
{
return impl->kernel;
}
HID::HIDCore & System::HIDCore()
{
return impl->hid_core;
}
const HID::HIDCore & System::HIDCore() const
{
return impl->hid_core;
}
AudioCore::AudioCore & System::AudioCore()
{
return *impl->audio_core;
}
const AudioCore::AudioCore & System::AudioCore() const
{
return *impl->audio_core;
}
ICoreTiming & System::Timing()
{
return impl->core_timing;
}
Timing::CoreTiming & System::CoreTiming()
{
return impl->core_timing;
}
const Timing::CoreTiming & System::CoreTiming() const
{
return impl->core_timing;
}
Core::PerfStats & System::GetPerfStats()
{
return *impl->perf_stats;
}
const Core::PerfStats & System::GetPerfStats() const
{
return *impl->perf_stats;
}
Core::SpeedLimiter & System::SpeedLimiter()
{
return impl->speed_limiter;
}
const Core::SpeedLimiter & System::SpeedLimiter() const
{
return impl->speed_limiter;
}
std::shared_ptr<InputCommon::InputSubsystem> & System::InputSubsystem() const
{
return impl->input_subsystem;
}
u64 System::GetApplicationProcessProgramID() const {
return impl->kernel.ApplicationProcess()->GetProgramId();
}
void System::AddGlueRegistrationForProcess(Kernel::KProcess & process, uint32_t version, StorageId baseGameStorageId, StorageId updateStorageId, uint8_t * nacpData, uint32_t nacpDataLen)
{
std::vector<u8> nacp_data;
if (nacpDataLen > 0)
{
nacp_data.resize(nacpDataLen);
memcpy(nacp_data.data(), nacpData, nacpDataLen);
}
Service::Glue::ApplicationLaunchProperty launch{};
launch.title_id = process.GetProgramId();
launch.version = version;
launch.base_game_storage_id = baseGameStorageId;
launch.update_storage_id = updateStorageId;
impl->arp_manager.Register(launch.title_id, launch, std::move(nacp_data));
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet && set)
{
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
impl->frontend_applets.SetDefaultAppletsIfMissing();
}
Service::AM::Frontend::FrontendAppletHolder & System::GetFrontendAppletHolder()
{
return impl->frontend_applets;
}
const Service::AM::Frontend::FrontendAppletHolder & System::GetFrontendAppletHolder() const
{
return impl->frontend_applets;
}
Service::AM::AppletManager & System::GetAppletManager()
{
return impl->applet_manager;
}
const Reporter & System::GetReporter() const
{
return impl->reporter;
}
Service::Glue::ARPManager & System::GetARPManager()
{
return impl->arp_manager;
}
const Service::Glue::ARPManager & System::GetARPManager() const
{
return impl->arp_manager;
}
Service::APM::Controller & System::GetAPMController()
{
return impl->apm_controller;
}
const Service::APM::Controller & System::GetAPMController() const
{
return impl->apm_controller;
}
void System::SetExitLocked(bool locked)
{
impl->exit_locked = locked;
}
bool System::GetExitLocked() const
{
return impl->exit_locked;
}
void System::SetExitRequested(bool requested)
{
impl->exit_requested = requested;
}
bool System::GetExitRequested() const
{
return impl->exit_requested;
}
void System::SetApplicationProcessBuildID(const CurrentBuildProcessID & id)
{
impl->build_id = id;
}
const System::CurrentBuildProcessID & System::GetApplicationProcessBuildID() const
{
return impl->build_id;
}
Service::SM::ServiceManager & System::ServiceManager()
{
return *impl->service_manager;
}
const Service::SM::ServiceManager & System::ServiceManager() const
{
return *impl->service_manager;
}
void System::RegisterCoreThread(std::size_t id)
{
impl->kernel.RegisterCoreThread(id);
}
void System::RegisterHostThread()
{
impl->kernel.RegisterHostThread();
}
bool System::IsMulticore() const
{
return impl->is_multicore;
}
bool System::DebuggerEnabled() const
{
//return Settings::values.use_gdbstub.GetValue();
return false;
}
Core::Debugger & System::GetDebugger()
{
UNIMPLEMENTED();
static Core::Debugger * ptr = nullptr;
return *ptr;
}
const Core::Debugger & System::GetDebugger() const
{
UNIMPLEMENTED();
static Core::Debugger * ptr = nullptr;
return *ptr;
}
Network::RoomNetwork & System::GetRoomNetwork()
{
return impl->room_network;
}
const Network::RoomNetwork & System::GetRoomNetwork() const
{
return impl->room_network;
}
void System::RunServer(std::unique_ptr<Service::ServerManager> && server_manager)
{
return impl->kernel.RunServer(std::move(server_manager));
}
void System::RegisterExecuteProgramCallback(::ExecuteProgramCallback callback, void * userData)
{
impl->execute_program_callback = callback;
impl->execute_program_user_data = userData;
}
void System::ExecuteProgram(std::size_t program_index)
{
if (impl->execute_program_callback)
{
impl->execute_program_callback((uint32_t)program_index, impl->execute_program_user_data);
}
else
{
LOG_CRITICAL(Core, "execute_program_callback must be initialized by the frontend");
}
}
std::deque<std::vector<u8>> & System::GetUserChannel()
{
return impl->user_channel;
}
void System::PushGeneralChannelData(std::vector<u8>&& data) {
std::scoped_lock lk{impl->general_channel_mutex};
impl->EnsureGeneralChannelInitialized(*this);
const bool was_empty = impl->general_channel.empty();
impl->general_channel.push_back(std::move(data));
if (was_empty) {
impl->general_channel_event->Signal();
}
}
bool System::TryPopGeneralChannel(std::vector<u8>& out_data) {
std::scoped_lock lk{impl->general_channel_mutex};
if (!impl->general_channel_event || impl->general_channel.empty()) {
return false;
}
out_data = std::move(impl->general_channel.back());
impl->general_channel.pop_back();
if (impl->general_channel.empty()) {
impl->general_channel_event->Clear();
}
return true;
}
Service::Event& System::GetGeneralChannelEvent() {
std::scoped_lock lk{impl->general_channel_mutex};
impl->EnsureGeneralChannelInitialized(*this);
return *impl->general_channel_event;
}
void System::RegisterExitCallback(::ExitCallback callback, void * userData)
{
impl->exit_callback = callback;
impl->exit_user_data = userData;
}
void System::Exit()
{
if (impl->exit_callback)
{
impl->exit_callback(impl->exit_user_data);
}
else
{
LOG_CRITICAL(Core, "exit_callback must be initialized by the frontend");
}
}
} // namespace Core