From 2ec64bf8b5f456235c4a5073cf353e949e870f74 Mon Sep 17 00:00:00 2001 From: seerge Date: Mon, 13 Mar 2023 23:28:56 +0100 Subject: [PATCH] feat: added GPU temperature indication. Supports both NVIDIA and AMD discrete GPUs feat: immediately update sensors when opening GHelper window --- GHelper.csproj | 1 + Gpu/AmdAdl2.cs | 485 ++++++++++++++++++++++++++++ Gpu/AmdGpuTemperatureProvider.cs | 92 ++++++ Gpu/IGpuTemperatureProvider.cs | 6 + Gpu/NvidiaGpuTemperatureProvider.cs | 44 +++ Program.cs | 42 ++- Settings.cs | 5 + 7 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 Gpu/AmdAdl2.cs create mode 100644 Gpu/AmdGpuTemperatureProvider.cs create mode 100644 Gpu/IGpuTemperatureProvider.cs create mode 100644 Gpu/NvidiaGpuTemperatureProvider.cs diff --git a/GHelper.csproj b/GHelper.csproj index ed4be555..a88c141b 100644 --- a/GHelper.csproj +++ b/GHelper.csproj @@ -39,6 +39,7 @@ + diff --git a/Gpu/AmdAdl2.cs b/Gpu/AmdAdl2.cs new file mode 100644 index 00000000..8a0813d8 --- /dev/null +++ b/Gpu/AmdAdl2.cs @@ -0,0 +1,485 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace AmdAdl2; + +#region Export Struct + +[StructLayout(LayoutKind.Sequential)] +public struct ADLSingleSensorData { + public int Supported; + public int Value; +} + +[StructLayout(LayoutKind.Sequential)] +public struct ADLPMLogDataOutput { + int Size; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = Adl2.ADL_PMLOG_MAX_SENSORS)] + public ADLSingleSensorData[] Sensors; +} + +[StructLayout(LayoutKind.Sequential)] +public struct ADLGcnInfo +{ + public int CuCount; //Number of compute units on the ASIC. + public int TexCount; //Number of texture mapping units. + public int RopCount; //Number of Render backend Units. + public int ASICFamilyId; //Such SI, VI. See /inc/asic_reg/atiid.h for family ids + public int ASICRevisionId; //Such as Ellesmere, Fiji. For example - VI family revision ids are stored in /inc/asic_reg/vi_id.h +} + +[Flags] +public enum ADLAsicFamilyType { + Undefined = 0, + Discrete = 1 << 0, + Integrated = 1 << 1, + Workstation = 1 << 2, + FireMV = 1 << 3, + Xgp = 1 << 4, + Fusion = 1 << 5, + Firestream = 1 << 6, + Embedded = 1 << 7, +} + +public enum ADLSensorType { + SENSOR_MAXTYPES = 0, + PMLOG_CLK_GFXCLK = 1, // Current graphic clock value in MHz + PMLOG_CLK_MEMCLK = 2, // Current memory clock value in MHz + PMLOG_CLK_SOCCLK = 3, + PMLOG_CLK_UVDCLK1 = 4, + PMLOG_CLK_UVDCLK2 = 5, + PMLOG_CLK_VCECLK = 6, + PMLOG_CLK_VCNCLK = 7, + PMLOG_TEMPERATURE_EDGE = 8, // Current edge of the die temperature value in C + PMLOG_TEMPERATURE_MEM = 9, + PMLOG_TEMPERATURE_VRVDDC = 10, + PMLOG_TEMPERATURE_VRMVDD = 11, + PMLOG_TEMPERATURE_LIQUID = 12, + PMLOG_TEMPERATURE_PLX = 13, + PMLOG_FAN_RPM = 14, // Current fan RPM value + PMLOG_FAN_PERCENTAGE = 15, // Current ratio of fan RPM and max RPM + PMLOG_SOC_VOLTAGE = 16, + PMLOG_SOC_POWER = 17, + PMLOG_SOC_CURRENT = 18, + PMLOG_INFO_ACTIVITY_GFX = 19, // Current graphic activity level in percentage + PMLOG_INFO_ACTIVITY_MEM = 20, // Current memory activity level in percentage + PMLOG_GFX_VOLTAGE = 21, // Current graphic voltage in mV + PMLOG_MEM_VOLTAGE = 22, + PMLOG_ASIC_POWER = 23, // Current ASIC power draw in Watt + PMLOG_TEMPERATURE_VRSOC = 24, + PMLOG_TEMPERATURE_VRMVDD0 = 25, + PMLOG_TEMPERATURE_VRMVDD1 = 26, + PMLOG_TEMPERATURE_HOTSPOT = 27, // Current center of the die temperature value in C + PMLOG_TEMPERATURE_GFX = 28, + PMLOG_TEMPERATURE_SOC = 29, + PMLOG_GFX_POWER = 30, + PMLOG_GFX_CURRENT = 31, + PMLOG_TEMPERATURE_CPU = 32, + PMLOG_CPU_POWER = 33, + PMLOG_CLK_CPUCLK = 34, + PMLOG_THROTTLER_STATUS = 35, // A bit map of GPU throttle information. If a bit is set, the bit represented type of thorttling occurred in the last metrics sampling period + PMLOG_CLK_VCN1CLK1 = 36, + PMLOG_CLK_VCN1CLK2 = 37, + PMLOG_SMART_POWERSHIFT_CPU = 38, + PMLOG_SMART_POWERSHIFT_DGPU = 39, + PMLOG_BUS_SPEED = 40, // Current PCIE bus speed running + PMLOG_BUS_LANES = 41, // Current PCIE bus lanes using + PMLOG_TEMPERATURE_LIQUID0 = 42, + PMLOG_TEMPERATURE_LIQUID1 = 43, + PMLOG_CLK_FCLK = 44, + PMLOG_THROTTLER_STATUS_CPU = 45, + PMLOG_SSPAIRED_ASICPOWER = 46, // apuPower + PMLOG_SSTOTAL_POWERLIMIT = 47, // Total Power limit + PMLOG_SSAPU_POWERLIMIT = 48, // APU Power limit + PMLOG_SSDGPU_POWERLIMIT = 49, // DGPU Power limit + PMLOG_TEMPERATURE_HOTSPOT_GCD = 50, + PMLOG_TEMPERATURE_HOTSPOT_MCD = 51, + PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE = 52, + PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE = 53, + PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54, + PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55, + PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE = 56, + PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE = 57, + PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58, + PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59, + PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE = 60, + PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61, + PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62, + PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63, + PMLOG_THROTTLER_TDC_GFX_PERCENTAGE = 64, + PMLOG_THROTTLER_TDC_SOC_PERCENTAGE = 65, + PMLOG_THROTTLER_TDC_USR_PERCENTAGE = 66, + PMLOG_THROTTLER_PPT0_PERCENTAGE = 67, + PMLOG_THROTTLER_PPT1_PERCENTAGE = 68, + PMLOG_THROTTLER_PPT2_PERCENTAGE = 69, + PMLOG_THROTTLER_PPT3_PERCENTAGE = 70, + PMLOG_THROTTLER_FIT_PERCENTAGE = 71, + PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72, + PMLOG_BOARD_POWER = 73, + PMLOG_MAX_SENSORS_REAL +}; + +//Throttle Status +[Flags] +public enum ADL_THROTTLE_NOTIFICATION { + ADL_PMLOG_THROTTLE_POWER = 1 << 0, + ADL_PMLOG_THROTTLE_THERMAL = 1 << 1, + ADL_PMLOG_THROTTLE_CURRENT = 1 << 2, +}; + +public enum ADL_PMLOG_SENSORS { + ADL_SENSOR_MAXTYPES = 0, + ADL_PMLOG_CLK_GFXCLK = 1, + ADL_PMLOG_CLK_MEMCLK = 2, + ADL_PMLOG_CLK_SOCCLK = 3, + ADL_PMLOG_CLK_UVDCLK1 = 4, + ADL_PMLOG_CLK_UVDCLK2 = 5, + ADL_PMLOG_CLK_VCECLK = 6, + ADL_PMLOG_CLK_VCNCLK = 7, + ADL_PMLOG_TEMPERATURE_EDGE = 8, + ADL_PMLOG_TEMPERATURE_MEM = 9, + ADL_PMLOG_TEMPERATURE_VRVDDC = 10, + ADL_PMLOG_TEMPERATURE_VRMVDD = 11, + ADL_PMLOG_TEMPERATURE_LIQUID = 12, + ADL_PMLOG_TEMPERATURE_PLX = 13, + ADL_PMLOG_FAN_RPM = 14, + ADL_PMLOG_FAN_PERCENTAGE = 15, + ADL_PMLOG_SOC_VOLTAGE = 16, + ADL_PMLOG_SOC_POWER = 17, + ADL_PMLOG_SOC_CURRENT = 18, + ADL_PMLOG_INFO_ACTIVITY_GFX = 19, + ADL_PMLOG_INFO_ACTIVITY_MEM = 20, + ADL_PMLOG_GFX_VOLTAGE = 21, + ADL_PMLOG_MEM_VOLTAGE = 22, + ADL_PMLOG_ASIC_POWER = 23, + ADL_PMLOG_TEMPERATURE_VRSOC = 24, + ADL_PMLOG_TEMPERATURE_VRMVDD0 = 25, + ADL_PMLOG_TEMPERATURE_VRMVDD1 = 26, + ADL_PMLOG_TEMPERATURE_HOTSPOT = 27, + ADL_PMLOG_TEMPERATURE_GFX = 28, + ADL_PMLOG_TEMPERATURE_SOC = 29, + ADL_PMLOG_GFX_POWER = 30, + ADL_PMLOG_GFX_CURRENT = 31, + ADL_PMLOG_TEMPERATURE_CPU = 32, + ADL_PMLOG_CPU_POWER = 33, + ADL_PMLOG_CLK_CPUCLK = 34, + ADL_PMLOG_THROTTLER_STATUS = 35, // GFX + ADL_PMLOG_CLK_VCN1CLK1 = 36, + ADL_PMLOG_CLK_VCN1CLK2 = 37, + ADL_PMLOG_SMART_POWERSHIFT_CPU = 38, + ADL_PMLOG_SMART_POWERSHIFT_DGPU = 39, + ADL_PMLOG_BUS_SPEED = 40, + ADL_PMLOG_BUS_LANES = 41, + ADL_PMLOG_TEMPERATURE_LIQUID0 = 42, + ADL_PMLOG_TEMPERATURE_LIQUID1 = 43, + ADL_PMLOG_CLK_FCLK = 44, + ADL_PMLOG_THROTTLER_STATUS_CPU = 45, + ADL_PMLOG_SSPAIRED_ASICPOWER = 46, // apuPower + ADL_PMLOG_SSTOTAL_POWERLIMIT = 47, // Total Power limit + ADL_PMLOG_SSAPU_POWERLIMIT = 48, // APU Power limit + ADL_PMLOG_SSDGPU_POWERLIMIT = 49, // DGPU Power limit + ADL_PMLOG_TEMPERATURE_HOTSPOT_GCD = 50, + ADL_PMLOG_TEMPERATURE_HOTSPOT_MCD = 51, + ADL_PMLOG_THROTTLER_TEMP_EDGE_PERCENTAGE = 52, + ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_PERCENTAGE = 53, + ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_GCD_PERCENTAGE = 54, + ADL_PMLOG_THROTTLER_TEMP_HOTSPOT_MCD_PERCENTAGE = 55, + ADL_PMLOG_THROTTLER_TEMP_MEM_PERCENTAGE = 56, + ADL_PMLOG_THROTTLER_TEMP_VR_GFX_PERCENTAGE = 57, + ADL_PMLOG_THROTTLER_TEMP_VR_MEM0_PERCENTAGE = 58, + ADL_PMLOG_THROTTLER_TEMP_VR_MEM1_PERCENTAGE = 59, + ADL_PMLOG_THROTTLER_TEMP_VR_SOC_PERCENTAGE = 60, + ADL_PMLOG_THROTTLER_TEMP_LIQUID0_PERCENTAGE = 61, + ADL_PMLOG_THROTTLER_TEMP_LIQUID1_PERCENTAGE = 62, + ADL_PMLOG_THROTTLER_TEMP_PLX_PERCENTAGE = 63, + ADL_PMLOG_THROTTLER_TDC_GFX_PERCENTAGE = 64, + ADL_PMLOG_THROTTLER_TDC_SOC_PERCENTAGE = 65, + ADL_PMLOG_THROTTLER_TDC_USR_PERCENTAGE = 66, + ADL_PMLOG_THROTTLER_PPT0_PERCENTAGE = 67, + ADL_PMLOG_THROTTLER_PPT1_PERCENTAGE = 68, + ADL_PMLOG_THROTTLER_PPT2_PERCENTAGE = 69, + ADL_PMLOG_THROTTLER_PPT3_PERCENTAGE = 70, + ADL_PMLOG_THROTTLER_FIT_PERCENTAGE = 71, + ADL_PMLOG_THROTTLER_GFX_APCC_PLUS_PERCENTAGE = 72, + ADL_PMLOG_BOARD_POWER = 73, + ADL_PMLOG_MAX_SENSORS_REAL +} + +#region ADLAdapterInfo + +/// ADLAdapterInfo Structure +[StructLayout(LayoutKind.Sequential)] +public struct ADLAdapterInfo { + /// The size of the structure + int Size; + + /// Adapter Index + public int AdapterIndex; + + /// Adapter UDID + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string UDID; + + /// Adapter Bus Number + public int BusNumber; + + /// Adapter Driver Number + public int DriverNumber; + + /// Adapter Function Number + public int FunctionNumber; + + /// Adapter Vendor ID + public int VendorID; + + /// Adapter Adapter name + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string AdapterName; + + /// Adapter Display name + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string DisplayName; + + /// Adapter Present status + public int Present; + + /// Adapter Exist status + public int Exist; + + /// Adapter Driver Path + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string DriverPath; + + /// Adapter Driver Ext Path + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string DriverPathExt; + + /// Adapter PNP String + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string PNPString; + + /// OS Display Index + public int OSDisplayIndex; +} + +/// ADLAdapterInfo Array +[StructLayout(LayoutKind.Sequential)] +public struct ADLAdapterInfoArray { + /// ADLAdapterInfo Array + [MarshalAs(UnmanagedType.ByValArray, SizeConst = Adl2.ADL_MAX_ADAPTERS)] + public ADLAdapterInfo[] ADLAdapterInfo; +} + +#endregion ADLAdapterInfo + +#region ADLDisplayInfo + +/// ADLDisplayID Structure +[StructLayout(LayoutKind.Sequential)] +public struct ADLDisplayID { + /// Display Logical Index + public int DisplayLogicalIndex; + + /// Display Physical Index + public int DisplayPhysicalIndex; + + /// Adapter Logical Index + public int DisplayLogicalAdapterIndex; + + /// Adapter Physical Index + public int DisplayPhysicalAdapterIndex; +} + +/// ADLDisplayInfo Structure +[StructLayout(LayoutKind.Sequential)] +public struct ADLDisplayInfo { + /// Display Index + public ADLDisplayID DisplayID; + + /// Display Controller Index + public int DisplayControllerIndex; + + /// Display Name + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string DisplayName; + + /// Display Manufacturer Name + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = Adl2.ADL_MAX_PATH)] + public string DisplayManufacturerName; + + /// Display Type : < The Display type. CRT, TV,CV,DFP are some of display types, + public int DisplayType; + + /// Display output type + public int DisplayOutputType; + + /// Connector type + public int DisplayConnector; + + /// Indicating the display info bits' mask. + public int DisplayInfoMask; + + /// Indicating the display info value. + public int DisplayInfoValue; +} + +#endregion ADLDisplayInfo + +#endregion Export Struct + +public class Adl2 { + public const string Atiadlxx_FileName = "atiadlxx.dll"; + + #region Internal Constant + + /// Define the maximum path + public const int ADL_MAX_PATH = 256; + + /// Define the maximum adapters + public const int ADL_MAX_ADAPTERS = 40 /* 150 */; + + /// Define the maximum displays + public const int ADL_MAX_DISPLAYS = 40 /* 150 */; + + /// Define the maximum device name length + public const int ADL_MAX_DEVICENAME = 32; + + /// Define the successful + public const int ADL_SUCCESS = 0; + + /// Define the failure + public const int ADL_FAIL = -1; + + /// Define the driver ok + public const int ADL_DRIVER_OK = 0; + + /// Maximum number of GL-Sync ports on the GL-Sync module + public const int ADL_MAX_GLSYNC_PORTS = 8; + + /// Maximum number of GL-Sync ports on the GL-Sync module + public const int ADL_MAX_GLSYNC_PORT_LEDS = 8; + + /// Maximum number of ADLMOdes for the adapter + public const int ADL_MAX_NUM_DISPLAYMODES = 1024; + + /// Performance Metrics Log max sensors number + public const int ADL_PMLOG_MAX_SENSORS = 256; + + #endregion Internal Constant + + // ///// ADL Create Function to create ADL Data + /// If it is 1, then ADL will only return the physical exist adapters + ///// retrun ADL Error Code + public static int ADL2_Main_Control_Create(int enumConnectedAdapters, out IntPtr adlContextHandle) { + return NativeMethods.ADL2_Main_Control_Create(ADL_Main_Memory_Alloc_Impl_Reference, enumConnectedAdapters, out adlContextHandle); + } + + public static void FreeMemory(IntPtr buffer) { + Memory_Free_Impl(buffer); + } + + private static bool? isDllLoaded; + + public static bool Load() { + if (isDllLoaded != null) + return isDllLoaded.Value; + + try { + Marshal.PrelinkAll(typeof(Adl2)); + isDllLoaded = true; + } catch (Exception e) when (e is DllNotFoundException or EntryPointNotFoundException) { + Debug.WriteLine(e); + isDllLoaded = false; + } + + return isDllLoaded.Value; + } + + private static NativeMethods.ADL_Main_Memory_Alloc ADL_Main_Memory_Alloc_Impl_Reference = Memory_Alloc_Impl; + + /// Build in memory allocation function + /// input size + /// return the memory buffer + private static IntPtr Memory_Alloc_Impl(int size) { + return Marshal.AllocCoTaskMem(size); + } + + /// Build in memory free function + /// input buffer + private static void Memory_Free_Impl(IntPtr buffer) { + if (IntPtr.Zero != buffer) { + Marshal.FreeCoTaskMem(buffer); + } + } + + public static class NativeMethods { + /// ADL Memory allocation function allows ADL to callback for memory allocation + /// input size + /// retrun ADL Error Code + public delegate IntPtr ADL_Main_Memory_Alloc(int size); + + // ///// ADL Create Function to create ADL Data + /// Call back functin pointer which is ised to allocate memeory + /// If it is 1, then ADL will only retuen the physical exist adapters + ///// retrun ADL Error Code + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Main_Control_Create(ADL_Main_Memory_Alloc callback, int enumConnectedAdapters, out IntPtr adlContextHandle); + + /// ADL Destroy Function to free up ADL Data + /// retrun ADL Error Code + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Main_Control_Destroy(IntPtr adlContextHandle); + + /// ADL Function to get the number of adapters + /// return number of adapters + /// retrun ADL Error Code + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Adapter_NumberOfAdapters_Get(IntPtr adlContextHandle, out int numAdapters); + + /// ADL Function to get the GPU adapter information + /// return GPU adapter information + /// the size of the GPU adapter struct + /// retrun ADL Error Code + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Adapter_AdapterInfo_Get(IntPtr adlContextHandle, IntPtr info, int inputSize); + + /// Function to determine if the adapter is active or not. + /// The function is used to check if the adapter associated with iAdapterIndex is active + /// Adapter Index. + /// Status of the adapter. True: Active; False: Dsiabled + /// Non zero is successfull + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Adapter_Active_Get(IntPtr adlContextHandle, int adapterIndex, out int status); + + /// Get display information based on adapter index + /// Adapter Index + /// return the total number of supported displays + /// return ADLDisplayInfo Array for supported displays' information + /// force detect or not + /// return ADL Error Code + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Display_DisplayInfo_Get( + IntPtr adlContextHandle, + int adapterIndex, + out int numDisplays, + out IntPtr displayInfoArray, + int forceDetect + ); + + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Overdrive_Caps( + IntPtr adlContextHandle, + int adapterIndex, + out int supported, + out int enabled, + out int version + ); + + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_New_QueryPMLogData_Get(IntPtr adlContextHandle, int adapterIndex, out ADLPMLogDataOutput adlpmLogDataOutput); + + [DllImport(Atiadlxx_FileName)] + public static extern int ADL2_Adapter_ASICFamilyType_Get(IntPtr adlContextHandle, int adapterIndex, out ADLAsicFamilyType asicFamilyType, out int asicFamilyTypeValids); + } +} diff --git a/Gpu/AmdGpuTemperatureProvider.cs b/Gpu/AmdGpuTemperatureProvider.cs new file mode 100644 index 00000000..8cfac2e6 --- /dev/null +++ b/Gpu/AmdGpuTemperatureProvider.cs @@ -0,0 +1,92 @@ +using System.Runtime.InteropServices; +using AmdAdl2; + +namespace GHelper.Gpu; + +// Reference: https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Sample-Managed/Program.cs +public class AmdGpuTemperatureProvider : IGpuTemperatureProvider { + private bool _isReady; + private IntPtr _adlContextHandle; + private readonly ADLAdapterInfo _internalDiscreteAdapter; + + public AmdGpuTemperatureProvider() { + if (!Adl2.Load()) + return; + + if (Adl2.ADL2_Main_Control_Create(1, out _adlContextHandle) != Adl2.ADL_SUCCESS) + return; + + Adl2.NativeMethods.ADL2_Adapter_NumberOfAdapters_Get(_adlContextHandle, out int numberOfAdapters); + if (numberOfAdapters <= 0) + return; + + ADLAdapterInfoArray osAdapterInfoData = new(); + int osAdapterInfoDataSize = Marshal.SizeOf(osAdapterInfoData); + IntPtr AdapterBuffer = Marshal.AllocCoTaskMem(osAdapterInfoDataSize); + Marshal.StructureToPtr(osAdapterInfoData, AdapterBuffer, false); + if (Adl2.NativeMethods.ADL2_Adapter_AdapterInfo_Get(_adlContextHandle, AdapterBuffer, osAdapterInfoDataSize) != Adl2.ADL_SUCCESS) + return; + + osAdapterInfoData = (ADLAdapterInfoArray) Marshal.PtrToStructure(AdapterBuffer, osAdapterInfoData.GetType())!; + + const int amdVendorId = 1002; + + // Determine which GPU is internal discrete AMD GPU + ADLAdapterInfo internalDiscreteAdapter = + osAdapterInfoData.ADLAdapterInfo + .FirstOrDefault(adapter => { + if (adapter.Exist == 0 || adapter.Present == 0) + return false; + + if (adapter.VendorID != amdVendorId) + return false; + + if (Adl2.NativeMethods.ADL2_Adapter_ASICFamilyType_Get(_adlContextHandle, adapter.AdapterIndex, out ADLAsicFamilyType asicFamilyType, out int asicFamilyTypeValids) != Adl2.ADL_SUCCESS) + return false; + + asicFamilyType = (ADLAsicFamilyType) ((int) asicFamilyType & asicFamilyTypeValids); + + // FIXME: is this correct for G14 2022? + return (asicFamilyType & ADLAsicFamilyType.Discrete) != 0; + }); + + if (internalDiscreteAdapter.Exist == 0) + return; + + _internalDiscreteAdapter = internalDiscreteAdapter; + _isReady = true; + } + + public bool IsValid => _isReady && _adlContextHandle != IntPtr.Zero; + + public int? GetCurrentTemperature() { + if (!IsValid) + return null; + + if (Adl2.NativeMethods.ADL2_New_QueryPMLogData_Get(_adlContextHandle, _internalDiscreteAdapter.AdapterIndex, out ADLPMLogDataOutput adlpmLogDataOutput) != Adl2.ADL_SUCCESS) + return null; + + ADLSingleSensorData temperatureSensor = adlpmLogDataOutput.Sensors[(int) ADLSensorType.PMLOG_TEMPERATURE_EDGE]; + if (temperatureSensor.Supported == 0) + return null; + + return temperatureSensor.Value; + } + + private void ReleaseUnmanagedResources() { + if (_adlContextHandle != IntPtr.Zero) { + Adl2.NativeMethods.ADL2_Main_Control_Destroy(_adlContextHandle); + _adlContextHandle = IntPtr.Zero; + _isReady = false; + } + } + + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~AmdGpuTemperatureProvider() { + ReleaseUnmanagedResources(); + } +} diff --git a/Gpu/IGpuTemperatureProvider.cs b/Gpu/IGpuTemperatureProvider.cs new file mode 100644 index 00000000..3f5d1b77 --- /dev/null +++ b/Gpu/IGpuTemperatureProvider.cs @@ -0,0 +1,6 @@ +namespace GHelper.Gpu; + +public interface IGpuTemperatureProvider : IDisposable { + bool IsValid { get; } + int? GetCurrentTemperature(); +} diff --git a/Gpu/NvidiaGpuTemperatureProvider.cs b/Gpu/NvidiaGpuTemperatureProvider.cs new file mode 100644 index 00000000..07106a09 --- /dev/null +++ b/Gpu/NvidiaGpuTemperatureProvider.cs @@ -0,0 +1,44 @@ +using NvAPIWrapper.GPU; +using NvAPIWrapper.Native; +using NvAPIWrapper.Native.Exceptions; +using NvAPIWrapper.Native.GPU; +using NvAPIWrapper.Native.Interfaces.GPU; + +namespace GHelper.Gpu; + +public class NvidiaGpuTemperatureProvider : IGpuTemperatureProvider { + private readonly PhysicalGPU? _internalGpu; + + public NvidiaGpuTemperatureProvider() { + _internalGpu = GetInternalDiscreteGpu(); + } + + public bool IsValid => _internalGpu != null; + + public int? GetCurrentTemperature() { + if (!IsValid) + return null; + + IThermalSensor? gpuSensor = + GPUApi.GetThermalSettings(_internalGpu!.Handle).Sensors + .FirstOrDefault(s => s.Target == ThermalSettingsTarget.GPU); + + if (gpuSensor == null) + return null; + + return gpuSensor.CurrentTemperature; + } + + public void Dispose() { + } + + private static PhysicalGPU? GetInternalDiscreteGpu() { + try { + return PhysicalGPU + .GetPhysicalGPUs() + .FirstOrDefault(gpu => gpu.SystemType == SystemType.Laptop); + } catch (NVIDIAApiException) { + return null; + } + } +} diff --git a/Program.cs b/Program.cs index b3b2eb7e..4900f13e 100644 --- a/Program.cs +++ b/Program.cs @@ -5,13 +5,15 @@ using System.Management; using System.Reflection; using System.Runtime.InteropServices; using System.Text.Json; +using GHelper.Gpu; public class HardwareMonitor { + private static IGpuTemperatureProvider? GpuTemperatureProvider; public static float? cpuTemp = -1; public static float? batteryDischarge = -1; - + public static int? gpuTemp = null; public static void ReadSensors() { @@ -27,6 +29,8 @@ public class HardwareMonitor var cb = new PerformanceCounter("Power Meter", "Power", "Power Meter (0)", true); batteryDischarge = cb.NextValue() / 1000; cb.Dispose(); + + gpuTemp = GpuTemperatureProvider?.GetCurrentTemperature(); } catch { @@ -34,6 +38,34 @@ public class HardwareMonitor } } + public static void RecreateGpuTemperatureProvider() { + try { + if (GpuTemperatureProvider != null) { + GpuTemperatureProvider.Dispose(); + } + + // Detect valid GPU temperature provider. + // We start with NVIDIA because there's always at least an integrated AMD GPU + IGpuTemperatureProvider gpuTemperatureProvider = new NvidiaGpuTemperatureProvider(); + if (gpuTemperatureProvider.IsValid) { + GpuTemperatureProvider = gpuTemperatureProvider; + return; + } + + gpuTemperatureProvider.Dispose(); + gpuTemperatureProvider = new AmdGpuTemperatureProvider(); + if (gpuTemperatureProvider.IsValid) { + GpuTemperatureProvider = gpuTemperatureProvider; + return; + } + + gpuTemperatureProvider.Dispose(); + + GpuTemperatureProvider = null; + } finally { + Debug.WriteLine($"GpuTemperatureProvider: {GpuTemperatureProvider.GetType().Name}"); + } + } } namespace GHelper @@ -199,6 +231,14 @@ namespace GHelper settingsForm.SetMatrix(isPlugged); + HardwareMonitor.RecreateGpuTemperatureProvider(); + + // Re-enabling the discrete GPU takes a bit of time, + // so a simple workaround is to refresh again after that happens + Task.Run(async () => { + await Task.Delay(TimeSpan.FromSeconds(3)); + HardwareMonitor.RecreateGpuTemperatureProvider(); + }); } private static void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e) diff --git a/Settings.cs b/Settings.cs index 5dc75a53..9c079d81 100644 --- a/Settings.cs +++ b/Settings.cs @@ -653,6 +653,10 @@ namespace GHelper if (HardwareMonitor.batteryDischarge > 0) battery = "Discharging: " + Math.Round((decimal)HardwareMonitor.batteryDischarge, 1).ToString() + "W"; + if (HardwareMonitor.gpuTemp != null) { + gpuTemp = $": {HardwareMonitor.gpuTemp}°C - "; + } + Program.settingsForm.BeginInvoke(delegate { Program.settingsForm.labelCPUFan.Text = "CPU" + cpuTemp + cpuFan; @@ -680,6 +684,7 @@ namespace GHelper aTimer.Interval = 300; aTimer.Enabled = true; + RefreshSensors(); } else {