From 30096a5ae46ef42593fe754eec69498d760d2efb Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 27 May 2022 19:48:59 +1000 Subject: [PATCH] Qt: Add hardware check for SSE4 and AVX2 --- common/emitter/cpudetect.cpp | 8 +++++ pcsx2-qt/CMakeLists.txt | 1 + pcsx2-qt/EarlyHardwareCheck.cpp | 59 +++++++++++++++++++++++++++++++ pcsx2-qt/Main.cpp | 24 +++++++++++++ pcsx2-qt/pcsx2-qt.vcxproj | 1 + pcsx2-qt/pcsx2-qt.vcxproj.filters | 1 + pcsx2/VMManager.cpp | 32 +++++++++++++++++ pcsx2/VMManager.h | 3 ++ 8 files changed, 129 insertions(+) create mode 100644 pcsx2-qt/EarlyHardwareCheck.cpp diff --git a/common/emitter/cpudetect.cpp b/common/emitter/cpudetect.cpp index 1d2a2ed7ad..baee148596 100644 --- a/common/emitter/cpudetect.cpp +++ b/common/emitter/cpudetect.cpp @@ -46,6 +46,11 @@ using namespace x86Emitter; alignas(16) x86capabilities x86caps; +#ifdef _MSC_VER +// We disable optimizations for this function, because we need x86capabilities for AVX +// detection, but if we keep opts on, it'll use AVX instructions for inlining memzero. +#pragma optimize("", off) +#endif x86capabilities::x86capabilities() : isIdentified(false) , VendorID(x86Vendor_Unknown) @@ -65,6 +70,9 @@ x86capabilities::x86capabilities() memzero(VendorName); memzero(FamilyName); } +#ifdef _MSC_VER +#pragma optimize("", on) +#endif // Warning! We've had problems with the MXCSR detection code causing stack corruption in // MSVC PGO builds. The problem was fixed when I moved the MXCSR code to this function, and diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index b562c7b0a8..56dee96bac 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(pcsx2-qt PRIVATE AutoUpdaterDialog.ui DisplayWidget.cpp DisplayWidget.h + EarlyHardwareCheck.cpp EmuThread.cpp EmuThread.h Main.cpp diff --git a/pcsx2-qt/EarlyHardwareCheck.cpp b/pcsx2-qt/EarlyHardwareCheck.cpp new file mode 100644 index 0000000000..78af2e6d6e --- /dev/null +++ b/pcsx2-qt/EarlyHardwareCheck.cpp @@ -0,0 +1,59 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#if defined(_WIN32) && defined(_MSC_VER) + +#include "pcsx2/VMManager.h" + +#include "common/RedtapeWindows.h" + +// The problem with AVX2 builds on Windows, is that MSVC generates AVX instructions for zeroing memory, +// which is pretty common in our global object constructors. So, we have to use a special object which +// gets initialized before all other global objects, that does the hardware check, and terminates the +// process before main() or any of the other objects are constructed (which would subsequently crash). +struct EarlyHardwareCheckObject +{ +#pragma optimize("", off) + EarlyHardwareCheckObject() + { + const char* error; + if (VMManager::PerformEarlyHardwareChecks(&error)) + return; + + // we can't use StringUtil::UTF8StringToWideString because *that* constructor uses AVX.. + const int error_len = static_cast(std::strlen(error)); + int wlen = MultiByteToWideChar(CP_UTF8, 0, error, error_len, nullptr, 0); + if (wlen > 0) + { + wchar_t* werror = static_cast(HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t) * (error_len + 1))); + if (werror && (wlen = MultiByteToWideChar(CP_UTF8, 0, error, error_len, werror, wlen)) > 0) + { + werror[wlen] = 0; + MessageBoxW(NULL, werror, L"Hardware Check Failed", MB_ICONERROR); + HeapFree(GetProcessHeap(), 0, werror); + } + } + + TerminateProcess(GetCurrentProcess(), 0xFFFFFFFF); + } +#pragma optimize("", on) +}; +#pragma warning(disable : 4075) // warning C4075: initializers put in unrecognized initialization area +#pragma init_seg(".CRT$XCT") +EarlyHardwareCheckObject s_hardware_checker; + +#endif diff --git a/pcsx2-qt/Main.cpp b/pcsx2-qt/Main.cpp index f2e082f8df..2778dd703b 100644 --- a/pcsx2-qt/Main.cpp +++ b/pcsx2-qt/Main.cpp @@ -16,6 +16,7 @@ #include "PrecompiledHeader.h" #include +#include #include #include @@ -182,6 +183,23 @@ static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr autoboot; if (!ParseCommandLineOptions(argc, argv, autoboot)) return EXIT_FAILURE; diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 5f7e505aa4..37107c4487 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -134,6 +134,7 @@ + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 015a7cd874..99627fdc11 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -204,6 +204,7 @@ Tools\Input Recording + diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 301b171c8b..cf287c4b7a 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -128,6 +128,38 @@ static s32 s_current_save_slot = 1; static u32 s_frame_advance_count = 0; static u32 s_mxcsr_saved; +bool VMManager::PerformEarlyHardwareChecks(const char** error) +{ +#define COMMON_DOWNLOAD_MESSAGE \ + "PCSX2 builds can be downloaded from https://pcsx2.net/downloads/" + +#if defined(_M_X86) + // On Windows, this gets called as a global object constructor, before any of our objects are constructed. + // So, we have to put it on the stack instead. + x86capabilities temp_x86_caps; + temp_x86_caps.Identify(); + + if (!temp_x86_caps.hasStreamingSIMD4Extensions) + { + *error = "PCSX2 requires the Streaming SIMD 4 Extensions instruction set, which your CPU does not support.\n\n" + "SSE4 is now a minimum requirement for PCSX2. You should either upgrade your CPU, or use an older build such as 1.6.0.\n\n" COMMON_DOWNLOAD_MESSAGE; + return false; + } + +#if _M_SSE >= 0x0501 + if (!temp_x86_caps.hasAVX || !temp_x86_caps.hasAVX2) + { + *error = "This build of PCSX2 requires the Advanced Vector Extensions 2 instruction set, which your CPU does not support.\n\n" + "You should download and run the SSE4 build of PCSX2 instead, or upgrade to a CPU that supports AVX2 to use this build.\n\n" COMMON_DOWNLOAD_MESSAGE; + return false; + } +#endif +#endif + +#undef COMMON_DOWNLOAD_MESSAGE + return true; +} + VMState VMManager::GetState() { return s_state.load(); diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index b5e4c5486b..4910b7791d 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -52,6 +52,9 @@ struct VMBootParameters namespace VMManager { + /// Makes sure that AVX2 is available if we were compiled with it. + bool PerformEarlyHardwareChecks(const char** error); + /// Returns the current state of the VM. VMState GetState();