diff --git a/PowerControl/Controller.cs b/PowerControl/Controller.cs index 0ceb803..073f8e5 100644 --- a/PowerControl/Controller.cs +++ b/PowerControl/Controller.cs @@ -2,6 +2,7 @@ using CommonHelpers.FromLibreHardwareMonitor; using Microsoft.VisualBasic.Logging; using PowerControl.External; +using PowerControl.Helpers; using RTSSSharedMemoryNET; using System; using System.Collections.Generic; @@ -97,7 +98,7 @@ namespace PowerControl GlobalHotKey.RegisterHotKey(Settings.Default.MenuUpKey, () => { - if (!isForeground()) + if (!RTSS.IsOSDForeground()) return; rootMenu.Prev(); setDismissTimer(); @@ -106,7 +107,7 @@ namespace PowerControl GlobalHotKey.RegisterHotKey(Settings.Default.MenuDownKey, () => { - if (!isForeground()) + if (!RTSS.IsOSDForeground()) return; rootMenu.Next(); setDismissTimer(); @@ -115,7 +116,7 @@ namespace PowerControl GlobalHotKey.RegisterHotKey(Settings.Default.MenuLeftKey, () => { - if (!isForeground()) + if (!RTSS.IsOSDForeground()) return; rootMenu.SelectPrev(); setDismissTimer(); @@ -124,7 +125,7 @@ namespace PowerControl GlobalHotKey.RegisterHotKey(Settings.Default.MenuRightKey, () => { - if (!isForeground()) + if (!RTSS.IsOSDForeground()) return; rootMenu.SelectNext(); setDismissTimer(); @@ -167,28 +168,6 @@ namespace PowerControl } } - private bool isForeground() - { - try - { - var processId = Helpers.TopLevelWindow.GetTopLevelProcessId(); - if (processId is null) - return true; - - foreach (var app in OSD.GetAppEntries(AppFlags.MASK)) - { - if (app.ProcessId == processId) - return true; - } - - return false; - } - catch - { - return true; - } - } - private void OsdTimer_Tick(object? sender, EventArgs e) { try @@ -262,7 +241,7 @@ namespace PowerControl return; } - if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) == 0 || !isForeground()) + if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) == 0 || !RTSS.IsOSDForeground()) { // schedule next repeat far in the future dismissNeptuneInput(); diff --git a/PowerControl/Helpers/ProcessorCores.cs b/PowerControl/Helpers/ProcessorCores.cs new file mode 100644 index 0000000..dc782b8 --- /dev/null +++ b/PowerControl/Helpers/ProcessorCores.cs @@ -0,0 +1,190 @@ +using PowerControl.External; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace PowerControl.Helpers +{ + internal class ProcessorCores + { + public static int GetProcessorCoreCount() + { + return GetProcessorCores().Count(); + } + + public static IntPtr GetProcessorMask(bool firstThreadOnly = false) + { + Int64 mask = 0; + + foreach (var process in GetProcessorCores()) + { + // This works only up-to 63 CPUs + Int64 processorMask = (Int64)process.ProcessorMask.ToUInt64(); + + if (firstThreadOnly) + processorMask = LSB(processorMask); + + mask |= processorMask; + } + + return new IntPtr(mask); + } + + public static bool HasSMTThreads() + { + foreach (var processorMask in GetProcessorMasks()) + { + if (processorMask != LSB(processorMask)) + return true; + } + + return false; + } + + public static bool IsUsingSMT(int processId) + { + try + { + var p = Process.GetProcessById(processId); + UInt64 mask = (UInt64)p.ProcessorAffinity.ToInt64(); + + foreach (var processorMask in GetProcessorMasks()) + { + // look for CPU that has more than 1 thread + // and has both assigned to process + var filtered = mask & processorMask; + if (filtered != LSB(filtered)) + return true; + } + + return false; + } + catch(ArgumentException) + { + return false; + } + } + + public static bool SetProcessSMT(int processId, bool allThreads) + { + try + { + var p = Process.GetProcessById(processId); + UInt64 mask = (UInt64)p.ProcessorAffinity.ToInt64(); + + foreach (var processorMask in GetProcessorMasks()) + { + var selectedMask = mask & processorMask; + + if (selectedMask == 0) + continue; // ignore not assigned processors + else if (allThreads) + mask |= processorMask; // assign all threads + else + mask = LSB(selectedMask) | (mask & ~processorMask); // assign only first thread + } + + p.ProcessorAffinity = new IntPtr((Int64)mask); + return true; + } + catch (ArgumentException) + { + return false; + } + } + + private static UInt64 LSB(UInt64 value) + { + return (UInt64)LSB((Int64)value); + } + + private static Int64 LSB(Int64 value) + { + return (value & -value); + } + + static IEnumerable GetProcessorMasks() + { + return GetProcessorCores().Select((p) => p.ProcessorMask.ToUInt64()); + } + + static IEnumerable GetProcessorCores() + { + return GetLogicalProcessorInformation().Where((p) => p.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore); + } + + static SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] GetLogicalProcessorInformation() + { + int bufferSize = 0; + GetLogicalProcessorInformation(IntPtr.Zero, ref bufferSize); + if (bufferSize == 0) + return new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[0]; + + int sizeOfEntry = Marshal.SizeOf(); + int numEntries = bufferSize / sizeOfEntry; + var processors = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[numEntries]; + + var handle = Marshal.AllocHGlobal(bufferSize); + try + { + if (!GetLogicalProcessorInformation(handle, ref bufferSize)) + return new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[0]; + + for (int i = 0; i < processors.Length; i++) + processors[i] = Marshal.PtrToStructure(IntPtr.Add(handle, sizeOfEntry * i)); + + return processors; + } + finally + { + Marshal.FreeHGlobal(handle); + } + } + + // Taken from: https://stackoverflow.com/a/63744912 + [StructLayout(LayoutKind.Sequential)] + struct CACHE_DESCRIPTOR + { + public byte Level; + public byte Associativity; + public ushort LineSize; + public uint Size; + public uint Type; + }; + + [StructLayout(LayoutKind.Explicit)] + struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION + { + [FieldOffset(0)] public byte ProcessorCore; + [FieldOffset(0)] public uint NumaNode; + [FieldOffset(0)] public CACHE_DESCRIPTOR Cache; + [FieldOffset(0)] private UInt64 Reserved1; + [FieldOffset(8)] private UInt64 Reserved2; + }; + + public enum LOGICAL_PROCESSOR_RELATIONSHIP + { + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage, + RelationGroup, + RelationAll = 0xffff + } + + [StructLayout(LayoutKind.Sequential)] + struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION + { + public UIntPtr ProcessorMask; + public LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + public SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION ProcessorInformation; + } + + [DllImport("kernel32.dll")] + static extern bool GetLogicalProcessorInformation(IntPtr buffer, ref int bufferSize); + } +} diff --git a/PowerControl/External/RTSS.cs b/PowerControl/Helpers/RTSS.cs similarity index 83% rename from PowerControl/External/RTSS.cs rename to PowerControl/Helpers/RTSS.cs index 5e4d658..10e7a3d 100644 --- a/PowerControl/External/RTSS.cs +++ b/PowerControl/Helpers/RTSS.cs @@ -1,4 +1,5 @@ -using System; +using RTSSSharedMemoryNET; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -6,21 +7,49 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace PowerControl.External +namespace PowerControl.Helpers { internal static class RTSS { + public static bool IsOSDForeground() + { + return IsOSDForeground(out _); + } + + public static bool IsOSDForeground(out int? processId) + { + try + { + processId = (int?)Helpers.TopLevelWindow.GetTopLevelProcessId(); + if (processId is null) + return true; + + foreach (var app in OSD.GetAppEntries(AppFlags.MASK)) + { + if (app.ProcessId == processId) + return true; + } + + return false; + } + catch + { + processId = null; + return true; + } + } + public static bool GetProfileProperty(string propertyName, out T value) { var bytes = new byte[Marshal.SizeOf()]; var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); - value = default(T); + value = default; try { if (!GetProfileProperty(propertyName, handle.AddrOfPinnedObject(), (uint)bytes.Length)) return false; - value = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject()); + value = Marshal.PtrToStructure(handle.AddrOfPinnedObject()); return true; } catch diff --git a/PowerControl/MenuStack.cs b/PowerControl/MenuStack.cs index 77343af..1ded66d 100644 --- a/PowerControl/MenuStack.cs +++ b/PowerControl/MenuStack.cs @@ -1,5 +1,5 @@ using CommonHelpers; -using PowerControl.External; +using PowerControl.Helpers; using System; using System.Collections.Generic; using System.Diagnostics; @@ -158,6 +158,33 @@ namespace PowerControl ApplyDelay = 1000, Visible = false }, + new Menu.MenuItemWithOptions() + { + Name = "SMT", + ApplyDelay = 500, + Options = { "No", "Yes" }, + ResetValue = () => { return "Yes"; }, + CurrentValue = delegate() + { + if (!RTSS.IsOSDForeground(out var processId)) + return null; + if (!ProcessorCores.HasSMTThreads()) + return null; + + return ProcessorCores.IsUsingSMT(processId.Value) ? "Yes" : "No"; + }, + ApplyValue = delegate(object selected) + { + if (!RTSS.IsOSDForeground(out var processId)) + return null; + if (!ProcessorCores.HasSMTThreads()) + return null; + + ProcessorCores.SetProcessSMT(processId.Value, selected.ToString() == "Yes"); + + return ProcessorCores.IsUsingSMT(processId.Value) ? "Yes" : "No"; + } + }, new Menu.MenuItemSeparator(), new Menu.MenuItemWithOptions() { diff --git a/README.md b/README.md index 00ad798..986f605 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ There are currently 4 configurable settings: - Refresh Rate - FPS Limit (requires: RTSS > Setup > Enable Framelimit) - TDP +- SMT (Each core of AMD has 2 threads, this allows to enable/disable second threads) - OSD / OSDMode (requires PerformanceOverlay running) - Fan (requires FanControl running) diff --git a/RELEASE.md b/RELEASE.md index 1f1722e..3120d44 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -13,5 +13,6 @@ - Improve FanControl UI - Make increments for Brightness and Volume in 5 (fixed) - Press `3 dots + L4 + R4 + L5 + R5` to reset (TDP, Refresh Rate, FPS limit) to default +- Allow to disable SMT (second threads of each physical cores) If you found it useful buy me [Ko-fi](https://ko-fi.com/ayufan).