diff --git a/src/core/system.cpp b/src/core/system.cpp index b5962ba31..5c2665df9 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -94,6 +94,8 @@ SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std SystemBootParameters::~SystemBootParameters() = default; namespace System { +static void CheckCacheLineSize(); + static std::optional InternalGetExtendedSaveStateInfo(ByteStream* stream); static void LoadInputBindings(SettingsInterface& si, std::unique_lock& lock); @@ -255,6 +257,44 @@ static TinyString GetTimestampStringForFileName() return TinyString::from_format("{:%Y-%m-%d-%H-%M-%S}", fmt::localtime(std::time(nullptr))); } +bool System::Internal::PerformEarlyHardwareChecks(Error* error) +{ + // Check page size. If it doesn't match, it is a fatal error. + const size_t runtime_host_page_size = PlatformMisc::GetRuntimePageSize(); + if (runtime_host_page_size == 0) + { + Error::SetStringFmt(error, "Cannot determine size of page. Continuing with expectation of {} byte pages.", + runtime_host_page_size); + } + else if (HOST_PAGE_SIZE != runtime_host_page_size) + { + Error::SetStringFmt( + error, "Page size mismatch. This build was compiled with {} byte pages, but the system has {} byte pages.", + HOST_PAGE_SIZE, runtime_host_page_size); + CPUThreadShutdown(); + return false; + } + + return true; +} + +void System::CheckCacheLineSize() +{ + const size_t runtime_cache_line_size = PlatformMisc::GetRuntimeCacheLineSize(); + if (runtime_cache_line_size == 0) + { + Log_ErrorFmt("Cannot determine size of cache line. Continuing with expectation of {} byte lines.", + runtime_cache_line_size); + } + else if (HOST_CACHE_LINE_SIZE != runtime_cache_line_size) + { + // Not fatal, but does have performance implications. + Log_WarningFmt( + "Cache line size mismatch. This build was compiled with {} byte lines, but the system has {} byte lines.", + HOST_CACHE_LINE_SIZE, runtime_cache_line_size); + } +} + bool System::Internal::CPUThreadInitialize(Error* error) { #ifdef _WIN32 @@ -269,15 +309,17 @@ bool System::Internal::CPUThreadInitialize(Error* error) } #endif - if (!Bus::AllocateMemory(error)) - return false; - - if (!CPU::CodeCache::ProcessStartup(error)) + if (!Bus::AllocateMemory(error) || !CPU::CodeCache::ProcessStartup(error)) + { + CPUThreadShutdown(); return false; + } // This will call back to Host::LoadSettings() -> ReloadSources(). LoadSettings(false); + CheckCacheLineSize(); + #ifdef ENABLE_RAINTEGRATION if (Host::GetBaseBoolSettingValue("Cheevos", "UseRAIntegration", false)) Achievements::SwitchToRAIntegration(); diff --git a/src/core/system.h b/src/core/system.h index 860bffd77..cc25ffa36 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -496,6 +496,9 @@ void UpdateDiscordPresence(bool update_session_time); #endif namespace Internal { +/// Performs mandatory hardware checks. +bool PerformEarlyHardwareChecks(Error* error); + /// Called on process startup. bool CPUThreadInitialize(Error* error); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index a678fbf91..fd5de464c 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -85,6 +85,7 @@ static constexpr u32 FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL = 8; // Local function declarations ////////////////////////////////////////////////////////////////////////// namespace QtHost { +static bool PerformEarlyHardwareChecks(); static void RegisterTypes(); static bool InitializeConfig(std::string settings_filename); static bool ShouldUsePortableMode(); @@ -124,6 +125,27 @@ EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread) EmuThread::~EmuThread() = default; +bool QtHost::PerformEarlyHardwareChecks() +{ + Error error; + const bool okay = System::Internal::PerformEarlyHardwareChecks(&error); + if (okay && !error.IsValid()) + return true; + + if (okay) + { + QMessageBox::warning(nullptr, QStringLiteral("Hardware Check Warning"), + QString::fromStdString(error.GetDescription())); + } + else + { + QMessageBox::critical(nullptr, QStringLiteral("Hardware Check Failed"), + QString::fromStdString(error.GetDescription())); + } + + return okay; +} + void QtHost::RegisterTypes() { // Register any standard types we need elsewhere @@ -1700,8 +1722,8 @@ void EmuThread::run() Error startup_error; if (!System::Internal::CPUThreadInitialize(&startup_error)) { - Host::ReportFatalError("Fatal Startup Error", startup_error.GetDescription()); moveToThread(m_ui_thread); + Host::ReportFatalError("Fatal Startup Error", startup_error.GetDescription()); return; } } @@ -2482,6 +2504,9 @@ int main(int argc, char* argv[]) QApplication app(argc, argv); + if (!QtHost::PerformEarlyHardwareChecks()) + return EXIT_FAILURE; + std::shared_ptr autoboot; if (!QtHost::ParseCommandLineParametersAndInitializeConfig(app, autoboot)) return EXIT_FAILURE; diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index bcf6c2363..76ece10bf 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -690,7 +690,8 @@ int main(int argc, char* argv[]) { Error startup_error; - if (!System::Internal::CPUThreadInitialize(&startup_error)) + if (!System::Internal::PerformEarlyHardwareChecks(&startup_error) || + !System::Internal::CPUThreadInitialize(&startup_error)) { Log_ErrorFmt("CPUThreadInitialize() failed: {}", startup_error.GetDescription()); return EXIT_FAILURE; diff --git a/src/util/platform_misc.h b/src/util/platform_misc.h index 69e64bded..13b4090ff 100644 --- a/src/util/platform_misc.h +++ b/src/util/platform_misc.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "window_info.h" @@ -9,6 +9,12 @@ namespace PlatformMisc { void SuspendScreensaver(); void ResumeScreensaver(); +/// Returns the size of pages for the current host. +size_t GetRuntimePageSize(); + +/// Returns the size of a cache line for the current host. +size_t GetRuntimeCacheLineSize(); + /// Abstracts platform-specific code for asynchronously playing a sound. /// On Windows, this will use PlaySound(). On Linux, it will shell out to aplay. On MacOS, it uses NSSound. bool PlaySoundAsync(const char* path); diff --git a/src/util/platform_misc_mac.mm b/src/util/platform_misc_mac.mm index 582b957c4..3ee73114e 100644 --- a/src/util/platform_misc_mac.mm +++ b/src/util/platform_misc_mac.mm @@ -1,17 +1,19 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) +#include "metal_layer.h" #include "platform_misc.h" #include "window_info.h" -#include "metal_layer.h" #include "common/log.h" #include "common/small_string.h" -#include #include +#include #include #include +#include +#include #include Log_SetChannel(PlatformMisc); @@ -50,11 +52,11 @@ void PlatformMisc::SuspendScreensaver() { if (s_screensaver_suspended) - if (!SetScreensaverInhibitMacOS(true)) - { - Log_ErrorPrintf("Failed to suspend screensaver."); - return; - } + if (!SetScreensaverInhibitMacOS(true)) + { + Log_ErrorPrintf("Failed to suspend screensaver."); + return; + } s_screensaver_suspended = true; } @@ -70,6 +72,27 @@ void PlatformMisc::ResumeScreensaver() s_screensaver_suspended = false; } +template +static std::optional sysctlbyname(const char* name) +{ + T output = 0; + size_t output_size = sizeof(output); + if (sysctlbyname(name, &output, &output_size, nullptr, 0) != 0) + return std::nullopt; + + return output; +} + +size_t PlatformMisc::GetRuntimePageSize() +{ + return sysctlbyname("hw.pagesize").value_or(0); +} + +size_t PlatformMisc::GetRuntimeCacheLineSize() +{ + return static_cast(std::max(sysctlbyname("hw.cachelinesize").value_or(0), 0)); +} + bool PlatformMisc::PlaySoundAsync(const char* path) { NSString* nspath = [[NSString alloc] initWithUTF8String:path]; @@ -80,46 +103,44 @@ bool PlatformMisc::PlaySoundAsync(const char* path) return result; } -bool CocoaTools::CreateMetalLayer(WindowInfo *wi) +bool CocoaTools::CreateMetalLayer(WindowInfo* wi) { // Punt off to main thread if we're not calling from it already. if (![NSThread isMainThread]) { bool ret; - dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { - ret = CreateMetalLayer(wi); - }); + dispatch_sync(dispatch_get_main_queue(), [&ret, wi]() { ret = CreateMetalLayer(wi); }); return ret; } - + CAMetalLayer* layer = [CAMetalLayer layer]; if (layer == nil) { Log_ErrorPrint("Failed to create CAMetalLayer"); return false; } - + NSView* view = (__bridge NSView*)wi->window_handle; [view setWantsLayer:TRUE]; [view setLayer:layer]; [layer setContentsScale:[[[view window] screen] backingScaleFactor]]; - + wi->surface_handle = (__bridge void*)layer; return true; } -void CocoaTools::DestroyMetalLayer(WindowInfo *wi) +void CocoaTools::DestroyMetalLayer(WindowInfo* wi) { if (!wi->surface_handle) return; - + // Punt off to main thread if we're not calling from it already. if (![NSThread isMainThread]) { dispatch_sync(dispatch_get_main_queue(), [wi]() { DestroyMetalLayer(wi); }); return; } - + NSView* view = (__bridge NSView*)wi->window_handle; CAMetalLayer* layer = (__bridge CAMetalLayer*)wi->surface_handle; [view setLayer:nil]; diff --git a/src/util/platform_misc_unix.cpp b/src/util/platform_misc_unix.cpp index 60e1ab90a..24ddd76a1 100644 --- a/src/util/platform_misc_unix.cpp +++ b/src/util/platform_misc_unix.cpp @@ -121,6 +121,34 @@ void PlatformMisc::ResumeScreensaver() s_screensaver_suspended = false; } +size_t PlatformMisc::GetRuntimePageSize() +{ + int res = sysconf(_SC_PAGESIZE); + return (res > 0) ? static_cast(res) : 0; +} + +size_t PlatformMisc::GetRuntimeCacheLineSize() +{ + int l1i = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); + int l1d = sysconf(_SC_LEVEL1_ICACHE_LINESIZE); + int res = (l1i > l1d) ? l1i : l1d; + for (int index = 0; index < 16; index++) + { + char buf[128]; + snprintf(buf, sizeof(buf), "/sys/devices/system/cpu/cpu0/cache/index%d/coherency_line_size", index); + std::FILE* fp = std::fopen(buf, "rb"); + if (!fp) + break; + + std::fread(buf, sizeof(buf), 1, fp); + std::fclose(fp); + int val = std::atoi(buf); + res = (val > res) ? val : res; + } + + return (res > 0) ? static_cast(res) : 0; +} + bool PlatformMisc::PlaySoundAsync(const char* path) { #ifdef __linux__ diff --git a/src/util/platform_misc_win32.cpp b/src/util/platform_misc_win32.cpp index 9090781ee..d6aa832f9 100644 --- a/src/util/platform_misc_win32.cpp +++ b/src/util/platform_misc_win32.cpp @@ -8,7 +8,9 @@ #include "common/small_string.h" #include "common/string_util.h" +#include #include +#include #include "common/windows_headers.h" #include @@ -53,6 +55,35 @@ void PlatformMisc::ResumeScreensaver() s_screensaver_suspended = false; } +size_t PlatformMisc::GetRuntimePageSize() +{ + SYSTEM_INFO si = {}; + GetSystemInfo(&si); + return si.dwPageSize; +} + +size_t PlatformMisc::GetRuntimeCacheLineSize() +{ + DWORD size = 0; + if (!GetLogicalProcessorInformation(nullptr, &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return 0; + + std::unique_ptr lpi = + std::make_unique( + (size + (sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) - 1)) / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)); + if (!GetLogicalProcessorInformation(lpi.get(), &size)) + return 0; + + u32 max_line_size = 0; + for (u32 i = 0; i < size / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); i++) + { + if (lpi[i].Relationship == RelationCache) + max_line_size = std::max(max_line_size, lpi[i].Cache.LineSize); + } + + return max_line_size; +} + bool PlatformMisc::PlaySoundAsync(const char* path) { const std::wstring wpath(FileSystem::GetWin32Path(path));