4a587b81b2
The follow-up to e2457418da
, which
replaces most of the includes in the core header with forward declarations.
This makes it so that if any of the headers the core header was
previously including change, then no one will need to rebuild the bulk
of the core, due to core.h being quite a prevalent inclusion.
This should make turnaround for changes much faster for developers.
472 lines
14 KiB
C++
472 lines
14 KiB
C++
// Copyright 2014 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <array>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <utility>
|
|
|
|
#include "common/logging/log.h"
|
|
#include "common/string_util.h"
|
|
#include "core/arm/exclusive_monitor.h"
|
|
#include "core/core.h"
|
|
#include "core/core_cpu.h"
|
|
#include "core/core_timing.h"
|
|
#include "core/gdbstub/gdbstub.h"
|
|
#include "core/hle/kernel/client_port.h"
|
|
#include "core/hle/kernel/kernel.h"
|
|
#include "core/hle/kernel/process.h"
|
|
#include "core/hle/kernel/scheduler.h"
|
|
#include "core/hle/kernel/thread.h"
|
|
#include "core/hle/service/service.h"
|
|
#include "core/hle/service/sm/controller.h"
|
|
#include "core/hle/service/sm/sm.h"
|
|
#include "core/loader/loader.h"
|
|
#include "core/perf_stats.h"
|
|
#include "core/settings.h"
|
|
#include "core/telemetry_session.h"
|
|
#include "file_sys/vfs_concat.h"
|
|
#include "file_sys/vfs_real.h"
|
|
#include "video_core/debug_utils/debug_utils.h"
|
|
#include "video_core/gpu.h"
|
|
#include "video_core/renderer_base.h"
|
|
#include "video_core/video_core.h"
|
|
|
|
namespace Core {
|
|
|
|
/*static*/ System System::s_instance;
|
|
|
|
namespace {
|
|
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
|
const std::string& path) {
|
|
// To account for split 00+01+etc files.
|
|
std::string dir_name;
|
|
std::string filename;
|
|
Common::SplitPath(path, &dir_name, &filename, nullptr);
|
|
if (filename == "00") {
|
|
const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
|
|
std::vector<FileSys::VirtualFile> concat;
|
|
for (u8 i = 0; i < 0x10; ++i) {
|
|
auto next = dir->GetFile(fmt::format("{:02X}", i));
|
|
if (next != nullptr)
|
|
concat.push_back(std::move(next));
|
|
else {
|
|
next = dir->GetFile(fmt::format("{:02x}", i));
|
|
if (next != nullptr)
|
|
concat.push_back(std::move(next));
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (concat.empty())
|
|
return nullptr;
|
|
|
|
return FileSys::ConcatenateFiles(concat, dir->GetName());
|
|
}
|
|
|
|
return vfs->OpenFile(path, FileSys::Mode::Read);
|
|
}
|
|
|
|
/// Runs a CPU core while the system is powered on
|
|
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
|
while (Core::System::GetInstance().IsPoweredOn()) {
|
|
cpu_state->RunLoop(true);
|
|
}
|
|
}
|
|
} // Anonymous namespace
|
|
|
|
struct System::Impl {
|
|
Cpu& CurrentCpuCore() {
|
|
if (Settings::values.use_multi_core) {
|
|
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
|
|
ASSERT(search != thread_to_cpu.end());
|
|
ASSERT(search->second);
|
|
return *search->second;
|
|
}
|
|
|
|
// Otherwise, use single-threaded mode active_core variable
|
|
return *cpu_cores[active_core];
|
|
}
|
|
|
|
ResultStatus RunLoop(bool tight_loop) {
|
|
status = ResultStatus::Success;
|
|
|
|
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
|
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
|
|
|
if (GDBStub::IsServerEnabled()) {
|
|
GDBStub::HandlePacket();
|
|
|
|
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
|
|
// execute. Otherwise, get out of the loop function.
|
|
if (GDBStub::GetCpuHaltFlag()) {
|
|
if (GDBStub::GetCpuStepFlag()) {
|
|
tight_loop = false;
|
|
} else {
|
|
return ResultStatus::Success;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
|
|
cpu_cores[active_core]->RunLoop(tight_loop);
|
|
if (Settings::values.use_multi_core) {
|
|
// Cores 1-3 are run on other threads in this mode
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GDBStub::IsServerEnabled()) {
|
|
GDBStub::SetCpuStepFlag(false);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
ResultStatus Init(Frontend::EmuWindow& emu_window) {
|
|
LOG_DEBUG(HW_Memory, "initialized OK");
|
|
|
|
CoreTiming::Init();
|
|
kernel.Initialize();
|
|
|
|
// Create a default fs if one doesn't already exist.
|
|
if (virtual_filesystem == nullptr)
|
|
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
|
|
|
current_process = Kernel::Process::Create(kernel, "main");
|
|
|
|
cpu_barrier = std::make_shared<CpuBarrier>();
|
|
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
|
for (size_t index = 0; index < cpu_cores.size(); ++index) {
|
|
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
|
|
}
|
|
|
|
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
|
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
|
|
|
Service::Init(service_manager, virtual_filesystem);
|
|
GDBStub::Init();
|
|
|
|
renderer = VideoCore::CreateRenderer(emu_window);
|
|
if (!renderer->Init()) {
|
|
return ResultStatus::ErrorVideoCore;
|
|
}
|
|
|
|
gpu_core = std::make_unique<Tegra::GPU>(renderer->Rasterizer());
|
|
|
|
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
|
// CPU core 0 is run on the main thread
|
|
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
|
if (Settings::values.use_multi_core) {
|
|
for (size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
|
cpu_core_threads[index] =
|
|
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
|
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG(Core, "Initialized OK");
|
|
|
|
// Reset counters and set time origin to current frame
|
|
GetAndResetPerfStats();
|
|
perf_stats.BeginSystemFrame();
|
|
|
|
return ResultStatus::Success;
|
|
}
|
|
|
|
ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
|
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
|
|
|
|
if (!app_loader) {
|
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
|
return ResultStatus::ErrorGetLoader;
|
|
}
|
|
std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
|
|
app_loader->LoadKernelSystemMode();
|
|
|
|
if (system_mode.second != Loader::ResultStatus::Success) {
|
|
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
|
static_cast<int>(system_mode.second));
|
|
|
|
return ResultStatus::ErrorSystemMode;
|
|
}
|
|
|
|
ResultStatus init_result{Init(emu_window)};
|
|
if (init_result != ResultStatus::Success) {
|
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
|
|
static_cast<int>(init_result));
|
|
Shutdown();
|
|
return init_result;
|
|
}
|
|
|
|
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
|
if (load_result != Loader::ResultStatus::Success) {
|
|
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
|
Shutdown();
|
|
|
|
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
|
|
static_cast<u32>(load_result));
|
|
}
|
|
status = ResultStatus::Success;
|
|
return status;
|
|
}
|
|
|
|
void Shutdown() {
|
|
// Log last frame performance stats
|
|
auto perf_results = GetAndResetPerfStats();
|
|
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
|
|
perf_results.emulation_speed * 100.0);
|
|
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
|
|
perf_results.game_fps);
|
|
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
|
perf_results.frametime * 1000.0);
|
|
|
|
// Shutdown emulation session
|
|
renderer.reset();
|
|
GDBStub::Shutdown();
|
|
Service::Shutdown();
|
|
service_manager.reset();
|
|
telemetry_session.reset();
|
|
gpu_core.reset();
|
|
|
|
// Close all CPU/threading state
|
|
cpu_barrier->NotifyEnd();
|
|
if (Settings::values.use_multi_core) {
|
|
for (auto& thread : cpu_core_threads) {
|
|
thread->join();
|
|
thread.reset();
|
|
}
|
|
}
|
|
thread_to_cpu.clear();
|
|
for (auto& cpu_core : cpu_cores) {
|
|
cpu_core.reset();
|
|
}
|
|
cpu_barrier.reset();
|
|
|
|
// Shutdown kernel and core timing
|
|
kernel.Shutdown();
|
|
CoreTiming::Shutdown();
|
|
|
|
// Close app loader
|
|
app_loader.reset();
|
|
|
|
LOG_DEBUG(Core, "Shutdown OK");
|
|
}
|
|
|
|
Loader::ResultStatus GetGameName(std::string& out) const {
|
|
if (app_loader == nullptr)
|
|
return Loader::ResultStatus::ErrorNotInitialized;
|
|
return app_loader->ReadTitle(out);
|
|
}
|
|
|
|
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
|
|
status = new_status;
|
|
if (details) {
|
|
status_details = details;
|
|
}
|
|
}
|
|
|
|
PerfStatsResults GetAndResetPerfStats() {
|
|
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
Kernel::KernelCore kernel;
|
|
/// RealVfsFilesystem instance
|
|
FileSys::VirtualFilesystem virtual_filesystem;
|
|
/// AppLoader used to load the current executing application
|
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
|
std::unique_ptr<VideoCore::RendererBase> renderer;
|
|
std::unique_ptr<Tegra::GPU> gpu_core;
|
|
std::shared_ptr<Tegra::DebugContext> debug_context;
|
|
Kernel::SharedPtr<Kernel::Process> current_process;
|
|
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
|
std::shared_ptr<CpuBarrier> cpu_barrier;
|
|
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
|
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
|
size_t active_core{}; ///< Active core, only used in single thread mode
|
|
|
|
/// Service manager
|
|
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
|
|
|
/// Telemetry session for this emulation session
|
|
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
|
|
|
ResultStatus status = ResultStatus::Success;
|
|
std::string status_details = "";
|
|
|
|
/// Map of guest threads to CPU cores
|
|
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
|
|
|
Core::PerfStats perf_stats;
|
|
Core::FrameLimiter frame_limiter;
|
|
};
|
|
|
|
System::System() : impl{std::make_unique<Impl>()} {}
|
|
System::~System() = default;
|
|
|
|
Cpu& System::CurrentCpuCore() {
|
|
return impl->CurrentCpuCore();
|
|
}
|
|
|
|
System::ResultStatus System::RunLoop(bool tight_loop) {
|
|
return impl->RunLoop(tight_loop);
|
|
}
|
|
|
|
System::ResultStatus System::SingleStep() {
|
|
return RunLoop(false);
|
|
}
|
|
|
|
void System::InvalidateCpuInstructionCaches() {
|
|
for (auto& cpu : impl->cpu_cores) {
|
|
cpu->ArmInterface().ClearInstructionCache();
|
|
}
|
|
}
|
|
|
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
|
|
return impl->Load(emu_window, filepath);
|
|
}
|
|
|
|
bool System::IsPoweredOn() const {
|
|
return impl->cpu_barrier && impl->cpu_barrier->IsAlive();
|
|
}
|
|
|
|
void System::PrepareReschedule() {
|
|
CurrentCpuCore().PrepareReschedule();
|
|
}
|
|
|
|
PerfStatsResults System::GetAndResetPerfStats() {
|
|
return impl->GetAndResetPerfStats();
|
|
}
|
|
|
|
Core::TelemetrySession& System::TelemetrySession() const {
|
|
return *impl->telemetry_session;
|
|
}
|
|
|
|
ARM_Interface& System::CurrentArmInterface() {
|
|
return CurrentCpuCore().ArmInterface();
|
|
}
|
|
|
|
size_t System::CurrentCoreIndex() {
|
|
return CurrentCpuCore().CoreIndex();
|
|
}
|
|
|
|
Kernel::Scheduler& System::CurrentScheduler() {
|
|
return *CurrentCpuCore().Scheduler();
|
|
}
|
|
|
|
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
|
ASSERT(core_index < NUM_CPU_CORES);
|
|
return impl->cpu_cores[core_index]->Scheduler();
|
|
}
|
|
|
|
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
|
return impl->current_process;
|
|
}
|
|
|
|
ARM_Interface& System::ArmInterface(size_t core_index) {
|
|
ASSERT(core_index < NUM_CPU_CORES);
|
|
return impl->cpu_cores[core_index]->ArmInterface();
|
|
}
|
|
|
|
Cpu& System::CpuCore(size_t core_index) {
|
|
ASSERT(core_index < NUM_CPU_CORES);
|
|
return *impl->cpu_cores[core_index];
|
|
}
|
|
|
|
ExclusiveMonitor& System::Monitor() {
|
|
return *impl->cpu_exclusive_monitor;
|
|
}
|
|
|
|
Tegra::GPU& System::GPU() {
|
|
return *impl->gpu_core;
|
|
}
|
|
|
|
const Tegra::GPU& System::GPU() const {
|
|
return *impl->gpu_core;
|
|
}
|
|
|
|
VideoCore::RendererBase& System::Renderer() {
|
|
return *impl->renderer;
|
|
}
|
|
|
|
const VideoCore::RendererBase& System::Renderer() const {
|
|
return *impl->renderer;
|
|
}
|
|
|
|
Kernel::KernelCore& System::Kernel() {
|
|
return impl->kernel;
|
|
}
|
|
|
|
const Kernel::KernelCore& System::Kernel() const {
|
|
return impl->kernel;
|
|
}
|
|
|
|
Core::PerfStats& System::GetPerfStats() {
|
|
return impl->perf_stats;
|
|
}
|
|
|
|
const Core::PerfStats& System::GetPerfStats() const {
|
|
return impl->perf_stats;
|
|
}
|
|
|
|
Core::FrameLimiter& System::FrameLimiter() {
|
|
return impl->frame_limiter;
|
|
}
|
|
|
|
const Core::FrameLimiter& System::FrameLimiter() const {
|
|
return impl->frame_limiter;
|
|
}
|
|
|
|
Loader::ResultStatus System::GetGameName(std::string& out) const {
|
|
return impl->GetGameName(out);
|
|
}
|
|
|
|
void System::SetStatus(ResultStatus new_status, const char* details) {
|
|
impl->SetStatus(new_status, details);
|
|
}
|
|
|
|
const std::string& System::GetStatusDetails() const {
|
|
return impl->status_details;
|
|
}
|
|
|
|
Loader::AppLoader& System::GetAppLoader() const {
|
|
return *impl->app_loader;
|
|
}
|
|
|
|
void System::SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
|
impl->debug_context = std::move(context);
|
|
}
|
|
|
|
std::shared_ptr<Tegra::DebugContext> System::GetGPUDebugContext() const {
|
|
return impl->debug_context;
|
|
}
|
|
|
|
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
|
impl->virtual_filesystem = std::move(vfs);
|
|
}
|
|
|
|
std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
|
|
return impl->virtual_filesystem;
|
|
}
|
|
|
|
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
|
return impl->Init(emu_window);
|
|
}
|
|
|
|
void System::Shutdown() {
|
|
impl->Shutdown();
|
|
}
|
|
|
|
Service::SM::ServiceManager& System::ServiceManager() {
|
|
return *impl->service_manager;
|
|
}
|
|
|
|
const Service::SM::ServiceManager& System::ServiceManager() const {
|
|
return *impl->service_manager;
|
|
}
|
|
|
|
} // namespace Core
|