diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp
index 1f6a4e0e4d..823c9babd9 100644
--- a/pcsx2-qt/QtHost.cpp
+++ b/pcsx2-qt/QtHost.cpp
@@ -268,6 +268,8 @@ void QtHost::SetDefaultConfig()
{
EmuConfig = Pcsx2Config();
EmuFolders::SetDefaults();
+ EmuFolders::EnsureFoldersExist();
+ VMManager::SetHardwareDependentDefaultSettings(EmuConfig);
SettingsInterface& si = *s_base_settings_interface.get();
si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION);
diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.cpp b/pcsx2-qt/Settings/SystemSettingsWidget.cpp
index bf4a4db941..21ef8f3d96 100644
--- a/pcsx2-qt/Settings/SystemSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/SystemSettingsWidget.cpp
@@ -40,6 +40,7 @@ SystemSettingsWidget::SystemSettingsWidget(SettingsDialog* dialog, QWidget* pare
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.eeCycleSkipping, "EmuCore/Speedhacks", "EECycleSkip", DEFAULT_EE_CYCLE_SKIP);
+ SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.affinityControl, "EmuCore/CPU", "AffinityControlMode", 0);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.MTVU, "EmuCore/Speedhacks", "vuThread", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.instantVU1, "EmuCore/Speedhacks", "vu1Instant", true);
diff --git a/pcsx2-qt/Settings/SystemSettingsWidget.ui b/pcsx2-qt/Settings/SystemSettingsWidget.ui
index ed8fc93b43..d87e2c6840 100644
--- a/pcsx2-qt/Settings/SystemSettingsWidget.ui
+++ b/pcsx2-qt/Settings/SystemSettingsWidget.ui
@@ -171,6 +171,52 @@
+ -
+
+
+ Affinity Control:
+
+
+
+ -
+
+
-
+
+ Disabled
+
+
+ -
+
+ EE > VU > GS
+
+
+ -
+
+ EE > GS > VU
+
+
+ -
+
+ VU > EE > GS
+
+
+ -
+
+ VU > GS > EE
+
+
+ -
+
+ GS > EE > VU
+
+
+ -
+
+ GS > VU > EE
+
+
+
+
diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index 85277fb186..de68c4a18f 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -1604,6 +1604,7 @@ target_link_libraries(PCSX2_FLAGS INTERFACE
ryml
chdr-static
libzip::zip
+ cpuinfo
ZLIB::ZLIB
PkgConfig::SOUNDTOUCH
PkgConfig::SAMPLERATE
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index 7f797ed999..dcf794b0d5 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -399,6 +399,8 @@ struct Pcsx2Config
SSE_MXCSR sseMXCSR;
SSE_MXCSR sseVUMXCSR;
+ u32 AffinityControlMode;
+
CpuOptions();
void LoadSave(SettingsWrapper& wrap);
void ApplySanityCheck();
@@ -407,7 +409,7 @@ struct Pcsx2Config
bool operator==(const CpuOptions& right) const
{
- return OpEqu(sseMXCSR) && OpEqu(sseVUMXCSR) && OpEqu(Recompiler);
+ return OpEqu(sseMXCSR) && OpEqu(sseVUMXCSR) && OpEqu(AffinityControlMode) && OpEqu(Recompiler);
}
bool operator!=(const CpuOptions& right) const
diff --git a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp
index ff51707d2d..bd8eed1bb4 100644
--- a/pcsx2/GS/Renderers/SW/GSRasterizer.cpp
+++ b/pcsx2/GS/Renderers/SW/GSRasterizer.cpp
@@ -21,6 +21,10 @@
#include "PerformanceMetrics.h"
#include "common/StringUtil.h"
+#ifdef PCSX2_CORE
+#include "VMManager.h"
+#endif
+
#define ENABLE_DRAW_STATS 0
int GSRasterizerData::s_counter = 0;
@@ -1176,7 +1180,25 @@ GSRasterizerList::~GSRasterizerList()
void GSRasterizerList::OnWorkerStartup(int i)
{
Threading::SetNameOfCurrentThread(StringUtil::StdStringFromFormat("GS-SW-%d", i).c_str());
- PerformanceMetrics::SetGSSWThread(i, Threading::ThreadHandle::GetForCallingThread());
+
+ Threading::ThreadHandle handle(Threading::ThreadHandle::GetForCallingThread());
+
+#ifdef PCSX2_CORE
+ if (EmuConfig.Cpu.AffinityControlMode != 0)
+ {
+ const std::vector& procs = VMManager::GetSortedProcessorList();
+ const u32 processor_index = (THREAD_VU1 ? 3 : 2) + i;
+ if (processor_index < procs.size())
+ {
+ const u32 procid = procs[processor_index];
+ const u64 affinity = static_cast(1) << procid;
+ Console.WriteLn("Pinning GS thread %d to CPU %u (0x%llx)", i, procid, affinity);
+ handle.SetAffinity(affinity);
+ }
+ }
+#endif
+
+ PerformanceMetrics::SetGSSWThread(i, std::move(handle));
}
void GSRasterizerList::OnWorkerShutdown(int i)
diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp
index 25ae84f2bf..b74796f37b 100644
--- a/pcsx2/Pcsx2Config.cpp
+++ b/pcsx2/Pcsx2Config.cpp
@@ -238,12 +238,14 @@ Pcsx2Config::CpuOptions::CpuOptions()
{
sseMXCSR.bitmask = DEFAULT_sseMXCSR;
sseVUMXCSR.bitmask = DEFAULT_sseVUMXCSR;
+ AffinityControlMode = 0;
}
void Pcsx2Config::CpuOptions::ApplySanityCheck()
{
sseMXCSR.ClearExceptionFlags().DisableExceptions();
sseVUMXCSR.ClearExceptionFlags().DisableExceptions();
+ AffinityControlMode = std::min(AffinityControlMode, 6);
Recompiler.ApplySanityCheck();
}
@@ -255,6 +257,7 @@ void Pcsx2Config::CpuOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBoolEx(sseMXCSR.DenormalsAreZero, "FPU.DenormalsAreZero");
SettingsWrapBitBoolEx(sseMXCSR.FlushToZero, "FPU.FlushToZero");
SettingsWrapBitfieldEx(sseMXCSR.RoundingControl, "FPU.Roundmode");
+ SettingsWrapEntry(AffinityControlMode);
SettingsWrapBitBoolEx(sseVUMXCSR.DenormalsAreZero, "VU.DenormalsAreZero");
SettingsWrapBitBoolEx(sseVUMXCSR.FlushToZero, "VU.FlushToZero");
diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp
index cc4b14c315..07f41a3a98 100644
--- a/pcsx2/VMManager.cpp
+++ b/pcsx2/VMManager.cpp
@@ -18,6 +18,7 @@
#include "VMManager.h"
#include
+#include
#include
#include "common/Console.h"
@@ -26,6 +27,7 @@
#include "common/StringUtil.h"
#include "common/SettingsWrapper.h"
#include "common/Timer.h"
+#include "common/Threading.h"
#include "fmt/core.h"
#include "Counters.h"
@@ -98,7 +100,8 @@ namespace VMManager
std::string filename, s32 slot_for_message);
static void SetTimerResolutionIncreased(bool enabled);
- static void SetEmuThreadAffinities(bool force);
+ static void EnsureCPUInfoInitialized();
+ static void SetEmuThreadAffinities();
} // namespace VMManager
static std::unique_ptr s_vm_memory;
@@ -924,7 +927,7 @@ bool VMManager::Initialize(const VMBootParameters& boot_params)
UpdateRunningGame(true, false);
- SetEmuThreadAffinities(true);
+ SetEmuThreadAffinities();
PerformanceMetrics::Clear();
@@ -1410,6 +1413,12 @@ void VMManager::CheckForCPUConfigChanges(const Pcsx2Config& old_config)
// possible and reset next time we're called.
s_cpu_implementation_changed = true;
}
+
+ if (EmuConfig.Cpu.AffinityControlMode != old_config.Cpu.AffinityControlMode ||
+ EmuConfig.Speedhacks.vuThread != old_config.Speedhacks.vuThread)
+ {
+ SetEmuThreadAffinities();
+ }
}
void VMManager::CheckForGSConfigChanges(const Pcsx2Config& old_config)
@@ -1573,10 +1582,7 @@ void VMManager::ApplySettings()
LoadSettings();
if (HasValidVM())
- {
CheckForConfigChanges(old_config);
- SetEmuThreadAffinities(false);
- }
}
bool VMManager::ReloadGameSettings()
@@ -1722,7 +1728,211 @@ void VMManager::SetTimerResolutionIncreased(bool enabled)
#endif
-void VMManager::SetEmuThreadAffinities(bool force)
+static std::vector s_processor_list;
+static std::once_flag s_processor_list_initialized;
+
+#if defined(__linux__) || defined(_WIN32)
+
+#include "cpuinfo.h"
+
+static u32 GetProcessorIdForProcessor(const cpuinfo_processor* proc)
{
- Console.Error("(SetEmuThreadAffinities) Not implemented");
+#if defined(__linux__)
+ return static_cast(proc->linux_id);
+#elif defined(_WIN32)
+ return static_cast(proc->windows_processor_id);
+#else
+ return 0;
+#endif
+}
+
+static void InitializeCPUInfo()
+{
+ if (!cpuinfo_initialize())
+ {
+ Console.Error("Failed to initialize cpuinfo");
+ return;
+ }
+
+ const u32 cluster_count = cpuinfo_get_clusters_count();
+ if (cluster_count == 0)
+ {
+ Console.Error("Invalid CPU count returned");
+ return;
+ }
+
+ Console.WriteLn(Color_StrongYellow, "Processor count: %u cores, %u processors", cpuinfo_get_cores_count(), cpuinfo_get_processors_count());
+ Console.WriteLn(Color_StrongYellow, "Cluster count: %u", cluster_count);
+
+ static std::vector ordered_processors;
+ for (u32 i = 0; i < cluster_count; i++)
+ {
+ const cpuinfo_cluster* cluster = cpuinfo_get_cluster(i);
+ for (u32 j = 0; j < cluster->processor_count; j++)
+ {
+ const cpuinfo_processor* proc = cpuinfo_get_processor(cluster->processor_start + j);
+ if (!proc)
+ continue;
+
+ ordered_processors.push_back(proc);
+ }
+ }
+ // find the large and small clusters based on frequency
+ // this is assuming the large cluster is always clocked higher
+ // sort based on core, so that hyperthreads get pushed down
+ std::sort(ordered_processors.begin(), ordered_processors.end(), [](const cpuinfo_processor* lhs, const cpuinfo_processor* rhs) {
+ return (lhs->core->frequency > rhs->core->frequency || lhs->smt_id < rhs->smt_id);
+ });
+
+ s_processor_list.reserve(ordered_processors.size());
+ std::stringstream ss;
+ ss << "Ordered processor list: ";
+ for (const cpuinfo_processor* proc : ordered_processors)
+ {
+ if (proc != ordered_processors.front())
+ ss << ", ";
+
+ const u32 procid = GetProcessorIdForProcessor(proc);
+ ss << procid;
+ if (proc->smt_id != 0)
+ ss << "[SMT " << proc->smt_id << "]";
+
+ s_processor_list.push_back(procid);
+ }
+ Console.WriteLn(ss.str());
+}
+
+static void SetMTVUAndAffinityControlDefault(Pcsx2Config& config)
+{
+ VMManager::EnsureCPUInfoInitialized();
+
+ const u32 cluster_count = cpuinfo_get_clusters_count();
+ if (cluster_count == 0)
+ {
+ Console.Error("Invalid CPU count returned");
+ return;
+ }
+
+ Console.WriteLn("Cluster count: %u", cluster_count);
+
+ for (u32 i = 0; i < cluster_count; i++)
+ {
+ const cpuinfo_cluster* cluster = cpuinfo_get_cluster(i);
+ Console.WriteLn(" Cluster %u: %u cores and %u processors at %u MHz",
+ i, cluster->core_count, cluster->processor_count, static_cast(cluster->frequency /* / 1000000u*/));
+ }
+
+ const bool has_big_little = cluster_count > 1;
+ Console.WriteLn("Big-Little: %s", has_big_little ? "yes" : "no");
+
+ const u32 big_cores = cpuinfo_get_cluster(0)->core_count + ((cluster_count > 2) ? cpuinfo_get_cluster(1)->core_count : 0u);
+ Console.WriteLn("Guessing we have %u big/medium cores...", big_cores);
+
+ bool mtvu_enable;
+ bool affinity_control;
+ if (big_cores >= 3 || big_cores == 1)
+ {
+ Console.WriteLn(" So enabling MTVU and disabling affinity control");
+ mtvu_enable = true;
+ affinity_control = false;
+ }
+ else
+ {
+ Console.WriteLn(" So disabling MTVU and enabling affinity control");
+ mtvu_enable = false;
+ affinity_control = true;
+ }
+
+ config.Speedhacks.vuThread = mtvu_enable;
+ config.Cpu.AffinityControlMode = affinity_control ? 1 : 0;
+}
+
+#else
+
+static void InitializeCPUInfo()
+{
+ DevCon.WriteLn("(VMManager) InitializeCPUInfo() not implemented.");
+}
+
+static void SetMTVUAndAffinityControlDefault(Pcsx2Config& config)
+{
+}
+
+#endif
+
+void VMManager::EnsureCPUInfoInitialized()
+{
+ std::call_once(s_processor_list_initialized, InitializeCPUInfo);
+}
+
+void VMManager::SetEmuThreadAffinities()
+{
+ EnsureCPUInfoInitialized();
+
+ if (s_processor_list.empty())
+ {
+ // not supported on this platform
+ return;
+ }
+
+ if (EmuConfig.Cpu.AffinityControlMode == 0 ||
+ s_processor_list.size() < (EmuConfig.Speedhacks.vuThread ? 3 : 2))
+ {
+ if (EmuConfig.Cpu.AffinityControlMode != 0)
+ Console.Error("Insufficient processors for affinity control.");
+
+ GetMTGS().GetThreadHandle().SetAffinity(0);
+ vu1Thread.GetThreadHandle().SetAffinity(0);
+ s_vm_thread_handle.SetAffinity(0);
+ return;
+ }
+
+ static constexpr u8 processor_assignment[7][2][3] = {
+ //EE xx GS EE VU GS
+ {{0, 2, 1}, {0, 1, 2}}, // Disabled
+ {{0, 2, 1}, {0, 1, 2}}, // EE > VU > GS
+ {{0, 2, 1}, {0, 2, 1}}, // EE > GS > VU
+ {{0, 2, 1}, {1, 0, 2}}, // VU > EE > GS
+ {{1, 2, 0}, {2, 0, 1}}, // VU > GS > EE
+ {{1, 2, 0}, {1, 2, 0}}, // GS > EE > VU
+ {{1, 2, 0}, {2, 1, 0}}, // GS > VU > EE
+ };
+
+ // steal vu's thread if mtvu is off
+ const u8* this_proc_assigment = processor_assignment[EmuConfig.Cpu.AffinityControlMode][EmuConfig.Speedhacks.vuThread];
+ const u32 ee_index = s_processor_list[this_proc_assigment[0]];
+ const u32 vu_index = s_processor_list[this_proc_assigment[1]];
+ const u32 gs_index = s_processor_list[this_proc_assigment[2]];
+ Console.WriteLn("Processor order assignment: EE=%u, VU=%u, GS=%u",
+ this_proc_assigment[0], this_proc_assigment[1], this_proc_assigment[2]);
+
+ const u64 ee_affinity = static_cast(1) << ee_index;
+ Console.WriteLn(Color_StrongGreen, "EE thread is on processor %u (0x%llx)", ee_index, ee_affinity);
+ s_vm_thread_handle.SetAffinity(ee_affinity);
+
+ if (EmuConfig.Speedhacks.vuThread)
+ {
+ const u64 vu_affinity = static_cast(1) << vu_index;
+ Console.WriteLn(Color_StrongGreen, "VU thread is on processor %u (0x%llx)", vu_index, vu_affinity);
+ vu1Thread.GetThreadHandle().SetAffinity(vu_affinity);
+ }
+ else
+ {
+ vu1Thread.GetThreadHandle().SetAffinity(0);
+ }
+
+ const u64 gs_affinity = static_cast(1) << gs_index;
+ Console.WriteLn(Color_StrongGreen, "GS thread is on processor %u (0x%llx)", gs_index, gs_affinity);
+ GetMTGS().GetThreadHandle().SetAffinity(gs_affinity);
+}
+
+void VMManager::SetHardwareDependentDefaultSettings(Pcsx2Config& config)
+{
+ SetMTVUAndAffinityControlDefault(config);
+}
+
+const std::vector& VMManager::GetSortedProcessorList()
+{
+ EnsureCPUInfoInitialized();
+ return s_processor_list;
}
diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h
index a7814213b2..61a935e76f 100644
--- a/pcsx2/VMManager.h
+++ b/pcsx2/VMManager.h
@@ -159,6 +159,13 @@ namespace VMManager
/// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x.
void RequestDisplaySize(float scale = 0.0f);
+ /// Sets default settings based on hardware configuration.
+ void SetHardwareDependentDefaultSettings(Pcsx2Config& config);
+
+ /// Returns a list of processors in the system, and their corresponding affinity mask.
+ /// This list is ordered by most performant to least performant for pinning threads to.
+ const std::vector& GetSortedProcessorList();
+
/// Internal callbacks, implemented in the emu core.
namespace Internal
{
diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj
index d27074ff32..1a7076ea14 100644
--- a/pcsx2/pcsx2core.vcxproj
+++ b/pcsx2/pcsx2core.vcxproj
@@ -48,6 +48,7 @@
$(SolutionDir)3rdparty\libzip;$(SolutionDir)3rdparty\libzip\libzip\lib;%(AdditionalIncludeDirectories)
$(SolutionDir)3rdparty\d3d12memalloc\include;%(AdditionalIncludeDirectories)
%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\zstd\zstd\lib
+ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\cpuinfo\include
Async
Use
PrecompiledHeader.h
@@ -791,6 +792,9 @@
{20b2e9fe-f020-42a0-b324-956f5b06ea68}
+
+ {7e183337-a7e9-460c-9d3d-568bc9f9bcc1}
+
{812b4434-fd6b-4cb2-8865-5fd8eb34b046}
@@ -803,4 +807,4 @@
-
\ No newline at end of file
+