|
|
@ -27,135 +27,299 @@ namespace Core { |
|
|
|
|
|
|
|
|
/*static*/ System System::s_instance; |
|
|
/*static*/ System System::s_instance; |
|
|
|
|
|
|
|
|
System::System() = default; |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
System::~System() = default; |
|
|
|
|
|
|
|
|
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
|
|
|
/// Runs a CPU core while the system is powered on
|
|
|
static void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { |
|
|
|
|
|
|
|
|
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { |
|
|
while (Core::System::GetInstance().IsPoweredOn()) { |
|
|
while (Core::System::GetInstance().IsPoweredOn()) { |
|
|
cpu_state->RunLoop(true); |
|
|
cpu_state->RunLoop(true); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} // Anonymous namespace
|
|
|
|
|
|
|
|
|
Cpu& System::CurrentCpuCore() { |
|
|
|
|
|
// If multicore is enabled, use host thread to figure out the current CPU core
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Otherwise, use single-threaded mode active_core variable
|
|
|
|
|
|
return *cpu_cores[active_core]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
ResultStatus RunLoop(bool tight_loop) { |
|
|
|
|
|
status = ResultStatus::Success; |
|
|
|
|
|
|
|
|
System::ResultStatus System::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]; |
|
|
|
|
|
|
|
|
// 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 (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; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) { |
|
|
|
|
|
cpu_cores[active_core]->RunLoop(tight_loop); |
|
|
|
|
|
|
|
|
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) { |
|
|
if (Settings::values.use_multi_core) { |
|
|
// Cores 1-3 are run on other threads in this mode
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
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]; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (GDBStub::IsServerEnabled()) { |
|
|
|
|
|
GDBStub::SetCpuStepFlag(false); |
|
|
|
|
|
|
|
|
LOG_DEBUG(Core, "Initialized OK"); |
|
|
|
|
|
|
|
|
|
|
|
// Reset counters and set time origin to current frame
|
|
|
|
|
|
GetAndResetPerfStats(); |
|
|
|
|
|
perf_stats.BeginSystemFrame(); |
|
|
|
|
|
|
|
|
|
|
|
return ResultStatus::Success; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return status; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { |
|
|
|
|
|
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); |
|
|
|
|
|
|
|
|
System::ResultStatus System::SingleStep() { |
|
|
|
|
|
return RunLoop(false); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
static 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 (system_mode.second != Loader::ResultStatus::Success) { |
|
|
|
|
|
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!", |
|
|
|
|
|
static_cast<int>(system_mode.second)); |
|
|
|
|
|
|
|
|
|
|
|
return ResultStatus::ErrorSystemMode; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (concat.empty()) |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return FileSys::ConcatenateFiles(concat, dir->GetName()); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return vfs->OpenFile(path, FileSys::Mode::Read); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { |
|
|
|
|
|
app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); |
|
|
|
|
|
|
|
|
// Shutdown kernel and core timing
|
|
|
|
|
|
kernel.Shutdown(); |
|
|
|
|
|
CoreTiming::Shutdown(); |
|
|
|
|
|
|
|
|
|
|
|
// Close app loader
|
|
|
|
|
|
app_loader.reset(); |
|
|
|
|
|
|
|
|
if (!app_loader) { |
|
|
|
|
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); |
|
|
|
|
|
return ResultStatus::ErrorGetLoader; |
|
|
|
|
|
|
|
|
LOG_DEBUG(Core, "Shutdown OK"); |
|
|
} |
|
|
} |
|
|
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)); |
|
|
|
|
|
|
|
|
Loader::ResultStatus GetGameName(std::string& out) const { |
|
|
|
|
|
if (app_loader == nullptr) |
|
|
|
|
|
return Loader::ResultStatus::ErrorNotInitialized; |
|
|
|
|
|
return app_loader->ReadTitle(out); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return ResultStatus::ErrorSystemMode; |
|
|
|
|
|
|
|
|
void SetStatus(ResultStatus new_status, const char* details = nullptr) { |
|
|
|
|
|
status = new_status; |
|
|
|
|
|
if (details) { |
|
|
|
|
|
status_details = details; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
ResultStatus init_result{Init(emu_window)}; |
|
|
|
|
|
if (init_result != ResultStatus::Success) { |
|
|
|
|
|
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", |
|
|
|
|
|
static_cast<int>(init_result)); |
|
|
|
|
|
System::Shutdown(); |
|
|
|
|
|
return init_result; |
|
|
|
|
|
|
|
|
PerfStats::Results GetAndResetPerfStats() { |
|
|
|
|
|
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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)); |
|
|
|
|
|
System::Shutdown(); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + |
|
|
|
|
|
static_cast<u32>(load_result)); |
|
|
|
|
|
|
|
|
void System::InvalidateCpuInstructionCaches() { |
|
|
|
|
|
for (auto& cpu : impl->cpu_cores) { |
|
|
|
|
|
cpu->ArmInterface().ClearInstructionCache(); |
|
|
} |
|
|
} |
|
|
status = ResultStatus::Success; |
|
|
|
|
|
return status; |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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() { |
|
|
void System::PrepareReschedule() { |
|
|
@ -163,131 +327,134 @@ void System::PrepareReschedule() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
PerfStats::Results System::GetAndResetPerfStats() { |
|
|
PerfStats::Results System::GetAndResetPerfStats() { |
|
|
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs()); |
|
|
|
|
|
|
|
|
return impl->GetAndResetPerfStats(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) { |
|
|
|
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
|
|
|
return cpu_cores[core_index]->Scheduler(); |
|
|
|
|
|
|
|
|
Core::TelemetrySession& System::TelemetrySession() const { |
|
|
|
|
|
return *impl->telemetry_session; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Kernel::KernelCore& System::Kernel() { |
|
|
|
|
|
return kernel; |
|
|
|
|
|
|
|
|
ARM_Interface& System::CurrentArmInterface() { |
|
|
|
|
|
return CurrentCpuCore().ArmInterface(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const Kernel::KernelCore& System::Kernel() const { |
|
|
|
|
|
return kernel; |
|
|
|
|
|
|
|
|
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) { |
|
|
ARM_Interface& System::ArmInterface(size_t core_index) { |
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
return cpu_cores[core_index]->ArmInterface(); |
|
|
|
|
|
|
|
|
return impl->cpu_cores[core_index]->ArmInterface(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Cpu& System::CpuCore(size_t core_index) { |
|
|
Cpu& System::CpuCore(size_t core_index) { |
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
ASSERT(core_index < NUM_CPU_CORES); |
|
|
return *cpu_cores[core_index]; |
|
|
|
|
|
|
|
|
return *impl->cpu_cores[core_index]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { |
|
|
|
|
|
LOG_DEBUG(HW_Memory, "initialized OK"); |
|
|
|
|
|
|
|
|
ExclusiveMonitor& System::Monitor() { |
|
|
|
|
|
return *impl->cpu_exclusive_monitor; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
CoreTiming::Init(); |
|
|
|
|
|
kernel.Initialize(); |
|
|
|
|
|
|
|
|
Tegra::GPU& System::GPU() { |
|
|
|
|
|
return *impl->gpu_core; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Create a default fs if one doesn't already exist.
|
|
|
|
|
|
if (virtual_filesystem == nullptr) |
|
|
|
|
|
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); |
|
|
|
|
|
|
|
|
const Tegra::GPU& System::GPU() const { |
|
|
|
|
|
return *impl->gpu_core; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
current_process = Kernel::Process::Create(kernel, "main"); |
|
|
|
|
|
|
|
|
VideoCore::RendererBase& System::Renderer() { |
|
|
|
|
|
return *impl->renderer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const VideoCore::RendererBase& System::Renderer() const { |
|
|
|
|
|
return *impl->renderer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
telemetry_session = std::make_unique<Core::TelemetrySession>(); |
|
|
|
|
|
service_manager = std::make_shared<Service::SM::ServiceManager>(); |
|
|
|
|
|
|
|
|
Kernel::KernelCore& System::Kernel() { |
|
|
|
|
|
return impl->kernel; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Service::Init(service_manager, virtual_filesystem); |
|
|
|
|
|
GDBStub::Init(); |
|
|
|
|
|
|
|
|
const Kernel::KernelCore& System::Kernel() const { |
|
|
|
|
|
return impl->kernel; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
renderer = VideoCore::CreateRenderer(emu_window); |
|
|
|
|
|
if (!renderer->Init()) { |
|
|
|
|
|
return ResultStatus::ErrorVideoCore; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Core::PerfStats& System::GetPerfStats() { |
|
|
|
|
|
return impl->perf_stats; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
gpu_core = std::make_unique<Tegra::GPU>(renderer->Rasterizer()); |
|
|
|
|
|
|
|
|
const Core::PerfStats& System::GetPerfStats() const { |
|
|
|
|
|
return impl->perf_stats; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 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]; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Core::FrameLimiter& System::FrameLimiter() { |
|
|
|
|
|
return impl->frame_limiter; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
LOG_DEBUG(Core, "Initialized OK"); |
|
|
|
|
|
|
|
|
const Core::FrameLimiter& System::FrameLimiter() const { |
|
|
|
|
|
return impl->frame_limiter; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Reset counters and set time origin to current frame
|
|
|
|
|
|
GetAndResetPerfStats(); |
|
|
|
|
|
perf_stats.BeginSystemFrame(); |
|
|
|
|
|
|
|
|
Loader::ResultStatus System::GetGameName(std::string& out) const { |
|
|
|
|
|
return impl->GetGameName(out); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return ResultStatus::Success; |
|
|
|
|
|
|
|
|
void System::SetStatus(ResultStatus new_status, const char* details) { |
|
|
|
|
|
impl->SetStatus(new_status, details); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void System::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(); |
|
|
|
|
|
|
|
|
const std::string& System::GetStatusDetails() const { |
|
|
|
|
|
return impl->status_details; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Shutdown kernel and core timing
|
|
|
|
|
|
kernel.Shutdown(); |
|
|
|
|
|
CoreTiming::Shutdown(); |
|
|
|
|
|
|
|
|
Loader::AppLoader& System::GetAppLoader() const { |
|
|
|
|
|
return *impl->app_loader; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Close app loader
|
|
|
|
|
|
app_loader.reset(); |
|
|
|
|
|
|
|
|
void System::SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { |
|
|
|
|
|
impl->debug_context = std::move(context); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
LOG_DEBUG(Core, "Shutdown OK"); |
|
|
|
|
|
|
|
|
std::shared_ptr<Tegra::DebugContext> System::GetGPUDebugContext() const { |
|
|
|
|
|
return impl->debug_context; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void System::SetFilesystem(FileSys::VirtualFilesystem vfs) { |
|
|
|
|
|
impl->virtual_filesystem = std::move(vfs); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
FileSys::VirtualFilesystem 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() { |
|
|
Service::SM::ServiceManager& System::ServiceManager() { |
|
|
return *service_manager; |
|
|
|
|
|
|
|
|
return *impl->service_manager; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const Service::SM::ServiceManager& System::ServiceManager() const { |
|
|
const Service::SM::ServiceManager& System::ServiceManager() const { |
|
|
return *service_manager; |
|
|
|
|
|
|
|
|
return *impl->service_manager; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} // namespace Core
|
|
|
} // namespace Core
|