diff --git a/DDrawCompat/Config/Config.cpp b/DDrawCompat/Config/Config.cpp index 6615246..beaf024 100644 --- a/DDrawCompat/Config/Config.cpp +++ b/DDrawCompat/Config/Config.cpp @@ -9,6 +9,7 @@ namespace Config Settings::BltFilter bltFilter; Settings::ConfigHotKey configHotKey; Settings::CpuAffinity cpuAffinity; + Settings::CpuAffinityRotation cpuAffinityRotation; Settings::DesktopColorDepth desktopColorDepth; Settings::DisplayFilter displayFilter; Settings::DisplayRefreshRate displayRefreshRate; diff --git a/DDrawCompat/Config/Config.h b/DDrawCompat/Config/Config.h index d571c23..d42cba6 100644 --- a/DDrawCompat/Config/Config.h +++ b/DDrawCompat/Config/Config.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ namespace Config extern Settings::BltFilter bltFilter; extern Settings::ConfigHotKey configHotKey; extern Settings::CpuAffinity cpuAffinity; + extern Settings::CpuAffinityRotation cpuAffinityRotation; extern Settings::DesktopColorDepth desktopColorDepth; extern Settings::DisplayFilter displayFilter; extern Settings::DisplayRefreshRate displayRefreshRate; diff --git a/DDrawCompat/Config/Settings/CpuAffinityRotation.h b/DDrawCompat/Config/Settings/CpuAffinityRotation.h new file mode 100644 index 0000000..8553b20 --- /dev/null +++ b/DDrawCompat/Config/Settings/CpuAffinityRotation.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Config +{ + namespace Settings + { + class CpuAffinityRotation : public MappedSetting + { + public: + CpuAffinityRotation() + : MappedSetting("CpuAffinityRotation", "on", { {"off", false}, {"on", true} }) + { + } + }; + } +} diff --git a/DDrawCompat/DDraw/RealPrimarySurface.cpp b/DDrawCompat/DDraw/RealPrimarySurface.cpp index 2e9181c..5b3a1fe 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.cpp +++ b/DDrawCompat/DDraw/RealPrimarySurface.cpp @@ -33,6 +33,7 @@ #include #include #include +#include namespace { @@ -418,6 +419,7 @@ namespace int msUntilUpdateReady = 0; while (true) { + Win32::Thread::rotateCpuAffinity(); if (msUntilUpdateReady > 0) { Sleep(1); diff --git a/DDrawCompat/DDrawCompat.vcxproj b/DDrawCompat/DDrawCompat.vcxproj index ab2693c..9cf3467 100644 --- a/DDrawCompat/DDrawCompat.vcxproj +++ b/DDrawCompat/DDrawCompat.vcxproj @@ -166,6 +166,7 @@ + diff --git a/DDrawCompat/DDrawCompat.vcxproj.filters b/DDrawCompat/DDrawCompat.vcxproj.filters index 9b080e1..6cc8a85 100644 --- a/DDrawCompat/DDrawCompat.vcxproj.filters +++ b/DDrawCompat/DDrawCompat.vcxproj.filters @@ -633,6 +633,9 @@ Header Files\Config\Settings + + Header Files\Config\Settings + diff --git a/DDrawCompat/Dll/Dll.cpp b/DDrawCompat/Dll/Dll.cpp index 866ddaf..f4d96bd 100644 --- a/DDrawCompat/Dll/Dll.cpp +++ b/DDrawCompat/Dll/Dll.cpp @@ -1,5 +1,6 @@ #include +#include #include namespace Dll @@ -16,6 +17,7 @@ namespace Dll if (thread) { SetThreadPriority(thread, priority); + CALL_ORIG_FUNC(SetThreadPriorityBoost)(thread, FALSE); } return thread; } diff --git a/DDrawCompat/Win32/Thread.cpp b/DDrawCompat/Win32/Thread.cpp index 38c6d7f..150c005 100644 --- a/DDrawCompat/Win32/Thread.cpp +++ b/DDrawCompat/Win32/Thread.cpp @@ -1,12 +1,181 @@ +#include +#include +#include +#include + #include #include #include +#include #include +#include #include namespace { + DWORD g_cpuAffinity = 0; + bool g_cpuAffinityRotationEnabled = false; + std::map g_nextProcessor; + + std::string maskToString(ULONG_PTR mask); + void setNextProcessorSet(DWORD fromSet, DWORD toSet); + + std::map> getProcessorSets() + { + std::map> result; + DWORD primaryProcessorGroup = 0; + auto getThreadIdealProcessorEx = reinterpret_cast( + Compat::getProcAddress(GetModuleHandle("kernel32"), "GetThreadIdealProcessorEx")); + if (getThreadIdealProcessorEx) + { + PROCESSOR_NUMBER pn = {}; + getThreadIdealProcessorEx(GetCurrentThread(), &pn); + primaryProcessorGroup = pn.Group; + LOG_INFO << "Primary CPU group number: " << primaryProcessorGroup; + } + + auto getLogicalProcessorInformationEx = reinterpret_cast( + Compat::getProcAddress(GetModuleHandle("kernel32"), "GetLogicalProcessorInformationEx")); + if (getLogicalProcessorInformationEx) + { + DWORD size = 0; + std::vector buffer; + getLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &size); + buffer.resize(size); + getLogicalProcessorInformationEx(RelationProcessorCore, + reinterpret_cast(buffer.data()), &size); + + DWORD offset = 0; + while (offset < size) + { + auto& pi = *reinterpret_cast(buffer.data() + offset); + for (DWORD i = 0; i < pi.Processor.GroupCount; ++i) + { + if (primaryProcessorGroup == pi.Processor.GroupMask[i].Group) + { + result[pi.Processor.EfficiencyClass].push_back(pi.Processor.GroupMask[i].Mask); + } + } + offset += pi.Size; + } + } + else + { + DWORD size = 0; + GetLogicalProcessorInformation(nullptr, &size); + std::vector processorInfo; + processorInfo.resize(size / sizeof(processorInfo[0])); + GetLogicalProcessorInformation(processorInfo.data(), &size); + + for (auto& pi : processorInfo) + { + if (RelationProcessorCore == pi.Relationship) + { + result[0].push_back(pi.ProcessorMask); + } + } + } + + if (result.empty()) + { + LOG_INFO << "ERROR: Failed to detect CPU topology!"; + return result; + } + + for (auto it = result.rbegin(); it != result.rend(); ++it) + { + LOG_INFO << "Physical to logical CPU core mapping for efficiency class " << static_cast(it->first) << ':'; + DWORD core = 0; + for (auto& set : it->second) + { + ++core; + LOG_INFO << " Physical core #" << core << ": " << maskToString(set); + } + } + return result; + } + + void initNextProcessorMap(const std::map>& processorSets) + { + for (auto& ps : processorSets) + { + for (BYTE fromIndex = 0; fromIndex < ps.second.size(); ++fromIndex) + { + auto bitCount = std::bitset<32>(ps.second[fromIndex]).count(); + bool found = false; + for (BYTE toIndex = fromIndex + 1; toIndex < ps.second.size() && !found; ++toIndex) + { + if (bitCount == std::bitset<32>(ps.second[toIndex]).count()) + { + setNextProcessorSet(ps.second[fromIndex], ps.second[toIndex]); + found = true; + } + } + for (BYTE toIndex = 0; toIndex < fromIndex && !found; ++toIndex) + { + if (bitCount == std::bitset<32>(ps.second[toIndex]).count()) + { + setNextProcessorSet(ps.second[fromIndex], ps.second[toIndex]); + found = true; + } + } + if (!found) + { + setNextProcessorSet(ps.second[fromIndex], ps.second[fromIndex]); + } + } + } + } + + std::string maskToString(ULONG_PTR mask) + { + std::ostringstream oss; + for (BYTE i = 0; i < 32; ++i) + { + if (mask & (1U << i)) + { + oss << i + 1 << ", "; + } + } + return oss.str().substr(0, max(0, oss.str().length() - 2)); + } + + DWORD rotateMask(DWORD mask) + { + DWORD result = 0; + for (BYTE i = 0; i < 32; ++i) + { + if (mask & (1U << i)) + { + auto it = g_nextProcessor.find(i); + result |= 1U << (it != g_nextProcessor.end() ? it->second : i); + } + } + return result; + } + + void setNextProcessorSet(DWORD fromSet, DWORD toSet) + { + BYTE from = 0; + BYTE to = 0; + while (0 != fromSet) + { + while (!(fromSet & (1U << from))) + { + ++from; + } + while (!(toSet & (1U << to))) + { + ++to; + } + g_nextProcessor[from] = to; + fromSet &= ~(1U << from); + ++from; + ++to; + } + } + BOOL WINAPI setProcessAffinityMask(HANDLE hProcess, DWORD_PTR dwProcessAffinityMask) { LOG_FUNC("SetProcessAffinityMask", hProcess, Compat::hex(dwProcessAffinityMask)); @@ -44,22 +213,49 @@ namespace Win32 { void applyConfig() { - auto cpuAffinity = Config::cpuAffinity.get(); - if (0 != cpuAffinity) - { - SYSTEM_INFO si = {}; - GetSystemInfo(&si); - const unsigned cpuCount = min(si.dwNumberOfProcessors, 32); - cpuAffinity &= UINT_MAX >> (32 - cpuCount); + initNextProcessorMap(getProcessorSets()); - if (0 == cpuAffinity || !CALL_ORIG_FUNC(SetProcessAffinityMask)(GetCurrentProcess(), cpuAffinity)) + g_cpuAffinity = Config::cpuAffinity.get(); + if (0 != g_cpuAffinity) + { + for (BYTE i = 0; i < 32; ++i) { - LOG_INFO << (0 == cpuAffinity ? "Invalid" : "Failed to set") << " CPU affinity, falling back to default"; - Config::cpuAffinity.reset(); - CALL_ORIG_FUNC(SetProcessAffinityMask)(GetCurrentProcess(), Config::cpuAffinity.get()); + if (g_nextProcessor.find(i) == g_nextProcessor.end()) + { + g_cpuAffinity &= ~(1U << i); + } + } + + if (0 == g_cpuAffinity) + { + LOG_INFO << "Warning: CPU affinity setting doesn't match any existing logical cores, using default"; + g_cpuAffinity = 1; } } + DWORD processMask = 0; + DWORD systemMask = 0; + GetProcessAffinityMask(GetCurrentProcess(), &processMask, &systemMask); + LOG_INFO << "Current CPU affinity: " << maskToString(processMask); + + if (0 == g_cpuAffinity && 0 != processMask && processMask != systemMask) + { + g_cpuAffinity = processMask; + } + + if (0 != g_cpuAffinity) + { + LOG_INFO << "Applying configured CPU affinity: " << maskToString(g_cpuAffinity); + if (!CALL_ORIG_FUNC(SetProcessAffinityMask)(GetCurrentProcess(), g_cpuAffinity)) + { + LOG_INFO << "ERROR: Failed to set CPU affinity"; + g_cpuAffinity = 0; + } + } + + g_cpuAffinityRotationEnabled = Config::cpuAffinityRotation.get() && rotateMask(g_cpuAffinity) != g_cpuAffinity; + LOG_INFO << "CPU affinity rotation is " << (g_cpuAffinityRotationEnabled ? "enabled" : "disabled"); + switch (Config::threadPriorityBoost.get()) { case Config::Settings::ThreadPriorityBoost::OFF: @@ -83,5 +279,27 @@ namespace Win32 HOOK_FUNCTION(kernel32, SetProcessPriorityBoost, setProcessPriorityBoost); HOOK_FUNCTION(kernel32, SetThreadPriorityBoost, setThreadPriorityBoost); } + + void rotateCpuAffinity() + { + if (!g_cpuAffinityRotationEnabled) + { + return; + } + + static auto g_qpcLastRotation = Time::queryPerformanceCounter(); + auto qpcNow = Time::queryPerformanceCounter(); + if (qpcNow - g_qpcLastRotation < Time::g_qpcFrequency / 10) + { + return; + } + g_qpcLastRotation = qpcNow; + + g_cpuAffinity = rotateMask(g_cpuAffinity); + if (!CALL_ORIG_FUNC(SetProcessAffinityMask)(GetCurrentProcess(), g_cpuAffinity)) + { + LOG_ONCE("ERROR: Failed to set rotated CPU affinity: " << maskToString(g_cpuAffinity)); + } + } } } diff --git a/DDrawCompat/Win32/Thread.h b/DDrawCompat/Win32/Thread.h index e0ad174..1e00bba 100644 --- a/DDrawCompat/Win32/Thread.h +++ b/DDrawCompat/Win32/Thread.h @@ -6,5 +6,6 @@ namespace Win32 { void applyConfig(); void installHooks(); + void rotateCpuAffinity(); } }