// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. // Copyright (C) LibreHardwareMonitor and Contributors. // Partial Copyright (C) Michael Möller and Contributors. // All Rights Reserved. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using LibreHardwareMonitor.Interop; using Microsoft.Win32; namespace LibreHardwareMonitor.Hardware.Gpu; internal sealed class NvidiaGpu : GenericGpu { private readonly int _adapterIndex; private readonly Sensor[] _clocks; private readonly int _clockVersion; private readonly Sensor[] _controls; private readonly string _d3dDeviceId; private readonly NvApi.NvDisplayHandle? _displayHandle; private readonly Control[] _fanControls; private readonly Sensor[] _fans; private readonly Sensor _gpuDedicatedMemoryUsage; private readonly Sensor[] _gpuNodeUsage; private readonly DateTime[] _gpuNodeUsagePrevTick; private readonly long[] _gpuNodeUsagePrevValue; private readonly Sensor _gpuSharedMemoryUsage; private readonly NvApi.NvPhysicalGpuHandle _handle; private readonly Sensor _hotSpotTemperature; private readonly Sensor[] _loads; private readonly Sensor _memoryFree; private readonly Sensor _memoryJunctionTemperature; private readonly Sensor _memoryTotal; private readonly Sensor _memoryUsed; private readonly Sensor _memoryLoad; private readonly NvidiaML.NvmlDevice? _nvmlDevice; private readonly Sensor _pcieThroughputRx; private readonly Sensor _pcieThroughputTx; private readonly Sensor[] _powers; private readonly Sensor _powerUsage; private readonly Sensor[] _temperatures; private readonly uint _thermalSensorsMask; public NvidiaGpu(int adapterIndex, NvApi.NvPhysicalGpuHandle handle, NvApi.NvDisplayHandle? displayHandle, ISettings settings) : base(GetName(handle), new Identifier("gpu-nvidia", adapterIndex.ToString(CultureInfo.InvariantCulture)), settings) { _adapterIndex = adapterIndex; _handle = handle; _displayHandle = displayHandle; bool hasBusId = NvApi.NvAPI_GPU_GetBusId(handle, out uint busId) == NvApi.NvStatus.OK; // Thermal settings. NvApi.NvThermalSettings thermalSettings = GetThermalSettings(out NvApi.NvStatus status); if (status == NvApi.NvStatus.OK && thermalSettings.Count > 0) { _temperatures = new Sensor[thermalSettings.Count]; for (int i = 0; i < thermalSettings.Count; i++) { NvApi.NvSensor sensor = thermalSettings.Sensor[i]; string name = sensor.Target switch { NvApi.NvThermalTarget.Gpu => "GPU Core", NvApi.NvThermalTarget.Memory => "GPU Memory", NvApi.NvThermalTarget.PowerSupply => "GPU Power Supply", NvApi.NvThermalTarget.Board => "GPU Board", NvApi.NvThermalTarget.VisualComputingBoard => "GPU Visual Computing Board", NvApi.NvThermalTarget.VisualComputingInlet => "GPU Visual Computing Inlet", NvApi.NvThermalTarget.VisualComputingOutlet => "GPU Visual Computing Outlet", _ => "GPU" }; _temperatures[i] = new Sensor(name, i, SensorType.Temperature, this, Array.Empty(), settings); ActivateSensor(_temperatures[i]); } } // Thermal sensors. _hotSpotTemperature = new Sensor("GPU Hot Spot", (int)thermalSettings.Count + 1, SensorType.Temperature, this, settings); _memoryJunctionTemperature = new Sensor("GPU Memory Junction", (int)thermalSettings.Count + 2, SensorType.Temperature, this, settings); bool hasAnyThermalSensor = false; for (int thermalSensorsMaxBit = 0; thermalSensorsMaxBit < 32; thermalSensorsMaxBit++) { // Find the maximum thermal sensor mask value. _thermalSensorsMask = 1u << thermalSensorsMaxBit; GetThermalSensors(_thermalSensorsMask, out NvApi.NvStatus thermalSensorsStatus); if (thermalSensorsStatus == NvApi.NvStatus.OK) { hasAnyThermalSensor = true; continue; } _thermalSensorsMask--; break; } if (!hasAnyThermalSensor) { _thermalSensorsMask = 0; } // Clock frequencies. for (int clockVersion = 1; clockVersion <= 3; clockVersion++) { _clockVersion = clockVersion; NvApi.NvGpuClockFrequencies clockFrequencies = GetClockFrequencies(out status); if (status == NvApi.NvStatus.OK) { var clocks = new List(); for (int i = 0; i < clockFrequencies.Clocks.Length; i++) { NvApi.NvGpuClockFrequenciesDomain clock = clockFrequencies.Clocks[i]; if (clock.IsPresent && Enum.IsDefined(typeof(NvApi.NvGpuPublicClockId), i)) { var clockId = (NvApi.NvGpuPublicClockId)i; string name = clockId switch { NvApi.NvGpuPublicClockId.Graphics => "GPU Core", NvApi.NvGpuPublicClockId.Memory => "GPU Memory", NvApi.NvGpuPublicClockId.Processor => "GPU Shader", NvApi.NvGpuPublicClockId.Video => "GPU Video", _ => null }; if (name != null) clocks.Add(new Sensor(name, i, SensorType.Clock, this, settings)); } } if (clocks.Count > 0) { _clocks = clocks.ToArray(); foreach (Sensor sensor in clocks) ActivateSensor(sensor); break; } } } // Fans + controllers. NvApi.NvFanCoolersStatus fanCoolers = GetFanCoolersStatus(out status); if (status == NvApi.NvStatus.OK && fanCoolers.Count > 0) { _fans = new Sensor[fanCoolers.Count]; for (int i = 0; i < fanCoolers.Count; i++) { NvApi.NvFanCoolersStatusItem item = fanCoolers.Items[i]; string name = "GPU Fan" + (fanCoolers.Count > 1 ? " " + (i + 1) : string.Empty); _fans[i] = new Sensor(name, (int)item.CoolerId, SensorType.Fan, this, settings); ActivateSensor(_fans[i]); } } else { GetTachReading(out status); if (status == NvApi.NvStatus.OK) { _fans = new[] { new Sensor("GPU", 1, SensorType.Fan, this, settings) }; ActivateSensor(_fans[0]); } } NvApi.NvFanCoolerControl fanControllers = GetFanCoolersControllers(out status); if (status == NvApi.NvStatus.OK && fanControllers.Count > 0 && fanCoolers.Count > 0) { _controls = new Sensor[fanControllers.Count]; _fanControls = new Control[fanControllers.Count]; for (int i = 0; i < fanControllers.Count; i++) { NvApi.NvFanCoolerControlItem item = fanControllers.Items[i]; string name = "GPU Fan" + (fanControllers.Count > 1 ? " " + (i + 1) : string.Empty); NvApi.NvFanCoolersStatusItem fanItem = Array.Find(fanCoolers.Items, x => x.CoolerId == item.CoolerId); if (!fanItem.Equals(default(NvApi.NvFanCoolersStatusItem))) { _controls[i] = new Sensor(name, (int)item.CoolerId, SensorType.Control, this, settings); ActivateSensor(_controls[i]); _fanControls[i] = new Control(_controls[i], settings, fanItem.CurrentMinLevel, fanItem.CurrentMaxLevel); _fanControls[i].ControlModeChanged += ControlModeChanged; _fanControls[i].SoftwareControlValueChanged += SoftwareControlValueChanged; _controls[i].Control = _fanControls[i]; ControlModeChanged(_fanControls[i]); } } } else { NvApi.NvCoolerSettings coolerSettings = GetCoolerSettings(out status); if (status == NvApi.NvStatus.OK && coolerSettings.Count > 0) { _controls = new Sensor[coolerSettings.Count]; _fanControls = new Control[coolerSettings.Count]; for (int i = 0; i < coolerSettings.Count; i++) { NvApi.NvCooler cooler = coolerSettings.Cooler[i]; string name = "GPU Fan" + (coolerSettings.Count > 1 ? " " + cooler.Controller : string.Empty); _controls[i] = new Sensor(name, i, SensorType.Control, this, settings); ActivateSensor(_controls[i]); _fanControls[i] = new Control(_controls[i], settings, cooler.DefaultMin, cooler.DefaultMax); _fanControls[i].ControlModeChanged += ControlModeChanged; _fanControls[i].SoftwareControlValueChanged += SoftwareControlValueChanged; _controls[i].Control = _fanControls[i]; ControlModeChanged(_fanControls[i]); } } } // Load usages. NvApi.NvDynamicPStatesInfo pStatesInfo = GetDynamicPstatesInfoEx(out status); if (status == NvApi.NvStatus.OK) { var loads = new List(); for (int index = 0; index < pStatesInfo.Utilizations.Length; index++) { NvApi.NvDynamicPState load = pStatesInfo.Utilizations[index]; if (load.IsPresent && Enum.IsDefined(typeof(NvApi.NvUtilizationDomain), index)) { var utilizationDomain = (NvApi.NvUtilizationDomain)index; string name = GetUtilizationDomainName(utilizationDomain); if (name != null) loads.Add(new Sensor(name, index, SensorType.Load, this, settings)); } } if (loads.Count > 0) { _loads = loads.ToArray(); foreach (Sensor sensor in loads) ActivateSensor(sensor); } } else { NvApi.NvUsages usages = GetUsages(out status); if (status == NvApi.NvStatus.OK) { var loads = new List(); for (int index = 0; index < usages.Entries.Length; index++) { NvApi.NvUsagesEntry load = usages.Entries[index]; if (load.IsPresent > 0 && Enum.IsDefined(typeof(NvApi.NvUtilizationDomain), index)) { var utilizationDomain = (NvApi.NvUtilizationDomain)index; string name = GetUtilizationDomainName(utilizationDomain); if (name != null) loads.Add(new Sensor(name, index, SensorType.Load, this, settings)); } } if (loads.Count > 0) { _loads = loads.ToArray(); foreach (Sensor sensor in loads) ActivateSensor(sensor); } } } // Power. NvApi.NvPowerTopology powerTopology = GetPowerTopology(out NvApi.NvStatus powerStatus); if (powerStatus == NvApi.NvStatus.OK && powerTopology.Count > 0) { _powers = new Sensor[powerTopology.Count]; for (int i = 0; i < powerTopology.Count; i++) { NvApi.NvPowerTopologyEntry entry = powerTopology.Entries[i]; string name = entry.Domain switch { NvApi.NvPowerTopologyDomain.Gpu => "GPU Power", NvApi.NvPowerTopologyDomain.Board => "GPU Board Power", _ => null }; if (name != null) { _powers[i] = new Sensor(name, i + (_loads?.Length ?? 0), SensorType.Load, this, settings); ActivateSensor(_powers[i]); } } } if (NvidiaML.IsAvailable || NvidiaML.Initialize()) { if (hasBusId) _nvmlDevice = NvidiaML.NvmlDeviceGetHandleByPciBusId($" 0000:{busId:X2}:00.0") ?? NvidiaML.NvmlDeviceGetHandleByIndex(_adapterIndex); else _nvmlDevice = NvidiaML.NvmlDeviceGetHandleByIndex(_adapterIndex); if (_nvmlDevice.HasValue) { _powerUsage = new Sensor("GPU Package", 0, SensorType.Power, this, settings); _pcieThroughputRx = new Sensor("GPU PCIe Rx", 0, SensorType.Throughput, this, settings); _pcieThroughputTx = new Sensor("GPU PCIe Tx", 1, SensorType.Throughput, this, settings); if (!Software.OperatingSystem.IsUnix) { NvidiaML.NvmlPciInfo? pciInfo = NvidiaML.NvmlDeviceGetPciInfo(_nvmlDevice.Value); if (pciInfo is { } pci) { string[] deviceIds = D3DDisplayDevice.GetDeviceIdentifiers(); if (deviceIds != null) { foreach (string deviceId in deviceIds) { if (deviceId.IndexOf("VEN_" + pci.pciVendorId.ToString("X"), StringComparison.OrdinalIgnoreCase) != -1 && deviceId.IndexOf("DEV_" + pci.pciDeviceId.ToString("X"), StringComparison.OrdinalIgnoreCase) != -1 && deviceId.IndexOf("SUBSYS_" + pci.pciSubSystemId.ToString("X"), StringComparison.OrdinalIgnoreCase) != -1) { bool isMatch = false; string actualDeviceId = D3DDisplayDevice.GetActualDeviceIdentifier(deviceId); try { if (Registry.GetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\nvlddmkm\Enum", adapterIndex.ToString(), null) is string adapterPnpId) { if (actualDeviceId.IndexOf(adapterPnpId, StringComparison.OrdinalIgnoreCase) != -1 || adapterPnpId.IndexOf(actualDeviceId, StringComparison.OrdinalIgnoreCase) != -1) { isMatch = true; } } } catch { // Ignored. } if (!isMatch) { try { string path = actualDeviceId; path = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\" + path; if (Registry.GetValue(path, "LocationInformation", null) is string locationInformation) { // For example: // @System32\drivers\pci.sys,#65536;PCI bus %1, device %2, function %3;(38,0,0) int index = locationInformation.IndexOf('('); if (index != -1) { index++; int secondIndex = locationInformation.IndexOf(',', index); if (secondIndex != -1) { string bus = locationInformation.Substring(index, secondIndex - index); if (pci.bus.ToString() == bus) isMatch = true; } } } } catch { // Ignored. } } if (isMatch && D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo)) { int sensorCount = (_loads?.Length ?? 0) + (_powers?.Length ?? 0); int loadSensorIndex = sensorCount > 0 ? sensorCount + 1 : 0; int smallDataSensorIndex = 3; // There are three normal GPU memory sensors. _d3dDeviceId = deviceId; _gpuDedicatedMemoryUsage = new Sensor("D3D Dedicated Memory Used", smallDataSensorIndex++, SensorType.SmallData, this, settings); _gpuSharedMemoryUsage = new Sensor("D3D Shared Memory Used", smallDataSensorIndex, SensorType.SmallData, this, settings); _gpuNodeUsage = new Sensor[deviceInfo.Nodes.Length]; _gpuNodeUsagePrevValue = new long[deviceInfo.Nodes.Length]; _gpuNodeUsagePrevTick = new DateTime[deviceInfo.Nodes.Length]; foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes.OrderBy(x => x.Name)) { _gpuNodeUsage[node.Id] = new Sensor(node.Name, loadSensorIndex++, SensorType.Load, this, settings); _gpuNodeUsagePrevValue[node.Id] = node.RunningTime; _gpuNodeUsagePrevTick[node.Id] = node.QueryTime; } } } } } } } } } _memoryFree = new Sensor("GPU Memory Free", 0, SensorType.SmallData, this, settings); _memoryUsed = new Sensor("GPU Memory Used", 1, SensorType.SmallData, this, settings); _memoryTotal = new Sensor("GPU Memory Total", 2, SensorType.SmallData, this, settings); _memoryLoad = new Sensor("GPU Memory", 3, SensorType.Load, this, settings); Update(); } /// public override string DeviceId { get { return _d3dDeviceId != null ? D3DDisplayDevice.GetActualDeviceIdentifier(_d3dDeviceId) : null; } } public override HardwareType HardwareType { get { return HardwareType.GpuNvidia; } } public override void Update() { if (_d3dDeviceId != null && D3DDisplayDevice.GetDeviceInfoByIdentifier(_d3dDeviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo)) { _gpuDedicatedMemoryUsage.Value = 1f * deviceInfo.GpuDedicatedUsed / 1024 / 1024; _gpuSharedMemoryUsage.Value = 1f * deviceInfo.GpuSharedUsed / 1024 / 1024; ActivateSensor(_gpuDedicatedMemoryUsage); ActivateSensor(_gpuSharedMemoryUsage); foreach (D3DDisplayDevice.D3DDeviceNodeInfo node in deviceInfo.Nodes) { long runningTimeDiff = node.RunningTime - _gpuNodeUsagePrevValue[node.Id]; long timeDiff = node.QueryTime.Ticks - _gpuNodeUsagePrevTick[node.Id].Ticks; _gpuNodeUsage[node.Id].Value = 100f * runningTimeDiff / timeDiff; _gpuNodeUsagePrevValue[node.Id] = node.RunningTime; _gpuNodeUsagePrevTick[node.Id] = node.QueryTime; ActivateSensor(_gpuNodeUsage[node.Id]); } } NvApi.NvStatus status; if (_temperatures is { Length: > 0 }) { NvApi.NvThermalSettings settings = GetThermalSettings(out status); // settings.Count is 0 when no valid data available, this happens when you try to read out this value with a high polling interval. if (status == NvApi.NvStatus.OK && settings.Count > 0) { foreach (Sensor sensor in _temperatures) sensor.Value = settings.Sensor[sensor.Index].CurrentTemp; } } if (_thermalSensorsMask > 0) { NvApi.NvThermalSensors thermalSensors = GetThermalSensors(_thermalSensorsMask, out status); if (status == NvApi.NvStatus.OK) { _hotSpotTemperature.Value = thermalSensors.Temperatures[1] / 256.0f; _memoryJunctionTemperature.Value = thermalSensors.Temperatures[9] / 256.0f; } if (_hotSpotTemperature.Value != 0) ActivateSensor(_hotSpotTemperature); if (_memoryJunctionTemperature.Value != 0) ActivateSensor(_memoryJunctionTemperature); } else { _hotSpotTemperature.Value = null; _memoryJunctionTemperature.Value = null; } if (_clocks is { Length: > 0 }) { NvApi.NvGpuClockFrequencies clockFrequencies = GetClockFrequencies(out status); if (status == NvApi.NvStatus.OK) { int current = 0; for (int i = 0; i < clockFrequencies.Clocks.Length; i++) { NvApi.NvGpuClockFrequenciesDomain clock = clockFrequencies.Clocks[i]; if (clock.IsPresent && Enum.IsDefined(typeof(NvApi.NvGpuPublicClockId), i)) _clocks[current++].Value = clock.Frequency / 1000f; } } } if (_fans is { Length: > 0 }) { NvApi.NvFanCoolersStatus fanCoolers = GetFanCoolersStatus(out status); if (status == NvApi.NvStatus.OK && fanCoolers.Count > 0) { for (int i = 0; i < fanCoolers.Count; i++) { NvApi.NvFanCoolersStatusItem item = fanCoolers.Items[i]; _fans[i].Value = item.CurrentRpm; } } else { int tachReading = GetTachReading(out status); if (status == NvApi.NvStatus.OK) _fans[0].Value = tachReading; } } if (_controls is { Length: > 0 }) { NvApi.NvFanCoolersStatus fanCoolers = GetFanCoolersStatus(out status); if (status == NvApi.NvStatus.OK && fanCoolers.Count > 0 && fanCoolers.Count == _controls.Length) { for (int i = 0; i < fanCoolers.Count; i++) { NvApi.NvFanCoolersStatusItem item = fanCoolers.Items[i]; if (Array.Find(_controls, c => c.Index == item.CoolerId) is { } control) control.Value = item.CurrentLevel; } } else { NvApi.NvCoolerSettings coolerSettings = GetCoolerSettings(out status); if (status == NvApi.NvStatus.OK && coolerSettings.Count > 0) { for (int i = 0; i < coolerSettings.Count; i++) { NvApi.NvCooler cooler = coolerSettings.Cooler[i]; _controls[i].Value = cooler.CurrentLevel; } } } } if (_loads is { Length: > 0 }) { NvApi.NvDynamicPStatesInfo pStatesInfo = GetDynamicPstatesInfoEx(out status); if (status == NvApi.NvStatus.OK) { for (int index = 0; index < pStatesInfo.Utilizations.Length; index++) { NvApi.NvDynamicPState load = pStatesInfo.Utilizations[index]; if (load.IsPresent && Enum.IsDefined(typeof(NvApi.NvUtilizationDomain), index)) _loads[index].Value = load.Percentage; } } else { NvApi.NvUsages usages = GetUsages(out status); if (status == NvApi.NvStatus.OK) { for (int index = 0; index < usages.Entries.Length; index++) { NvApi.NvUsagesEntry load = usages.Entries[index]; if (load.IsPresent > 0 && Enum.IsDefined(typeof(NvApi.NvUtilizationDomain), index)) _loads[index].Value = load.Percentage; } } } } if (_powers is { Length: > 0 }) { NvApi.NvPowerTopology powerTopology = GetPowerTopology(out status); if (status == NvApi.NvStatus.OK && powerTopology.Count > 0) { for (int i = 0; i < powerTopology.Count; i++) { NvApi.NvPowerTopologyEntry entry = powerTopology.Entries[i]; _powers[i].Value = entry.PowerUsage / 1000f; } } } if (_displayHandle != null) { NvApi.NvMemoryInfo memoryInfo = GetMemoryInfo(out status); if (status == NvApi.NvStatus.OK) { uint free = memoryInfo.CurrentAvailableDedicatedVideoMemory; uint total = memoryInfo.DedicatedVideoMemory; _memoryTotal.Value = total / 1024; ActivateSensor(_memoryTotal); _memoryFree.Value = free / 1024; ActivateSensor(_memoryFree); _memoryUsed.Value = (total - free) / 1024; ActivateSensor(_memoryUsed); _memoryLoad.Value = ((float)(total - free) / total) * 100; ActivateSensor(_memoryLoad); } } if (NvidiaML.IsAvailable && _nvmlDevice.HasValue) { int? result = NvidiaML.NvmlDeviceGetPowerUsage(_nvmlDevice.Value); if (result.HasValue) { _powerUsage.Value = result.Value / 1000f; ActivateSensor(_powerUsage); } // In MB/s, throughput sensors are passed as in KB/s. uint? rx = NvidiaML.NvmlDeviceGetPcieThroughput(_nvmlDevice.Value, NvidiaML.NvmlPcieUtilCounter.RxBytes); if (rx.HasValue) { _pcieThroughputRx.Value = rx * 1024; ActivateSensor(_pcieThroughputRx); } uint? tx = NvidiaML.NvmlDeviceGetPcieThroughput(_nvmlDevice.Value, NvidiaML.NvmlPcieUtilCounter.TxBytes); if (tx.HasValue) { _pcieThroughputTx.Value = tx * 1024; ActivateSensor(_pcieThroughputTx); } } } public override string GetReport() { StringBuilder r = new(); r.AppendLine("Nvidia GPU"); r.AppendLine(); r.AppendFormat("Name: {0}{1}", _name, Environment.NewLine); r.AppendFormat("Index: {0}{1}", _adapterIndex, Environment.NewLine); if (_displayHandle.HasValue && NvApi.NvAPI_GetDisplayDriverVersion != null) { NvApi.NvDisplayDriverVersion driverVersion = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1) }; if (NvApi.NvAPI_GetDisplayDriverVersion(_displayHandle.Value, ref driverVersion) == NvApi.NvStatus.OK) { r.Append("Driver Version: "); r.Append(driverVersion.DriverVersion / 100); r.Append("."); r.Append((driverVersion.DriverVersion % 100).ToString("00", CultureInfo.InvariantCulture)); r.AppendLine(); r.Append("Driver Branch: "); r.AppendLine(driverVersion.BuildBranch); } } if (NvApi.NvAPI_GPU_GetPCIIdentifiers != null) { NvApi.NvStatus status = NvApi.NvAPI_GPU_GetPCIIdentifiers(_handle, out uint deviceId, out uint subSystemId, out uint revisionId, out uint extDeviceId); if (status == NvApi.NvStatus.OK) { r.Append("DeviceID: 0x"); r.AppendLine(deviceId.ToString("X", CultureInfo.InvariantCulture)); r.Append("SubSystemID: 0x"); r.AppendLine(subSystemId.ToString("X", CultureInfo.InvariantCulture)); r.Append("RevisionID: 0x"); r.AppendLine(revisionId.ToString("X", CultureInfo.InvariantCulture)); r.Append("ExtDeviceID: 0x"); r.AppendLine(extDeviceId.ToString("X", CultureInfo.InvariantCulture)); r.AppendLine(); } } if (NvApi.NvAPI_GPU_GetThermalSettings != null) { NvApi.NvThermalSettings thermalSettings = GetThermalSettings(out NvApi.NvStatus status); r.AppendLine("Thermal Settings"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < thermalSettings.Count; i++) { r.AppendFormat(" Sensor[{0}].Controller: {1}{2}", i, thermalSettings.Sensor[i].Controller, Environment.NewLine); r.AppendFormat(" Sensor[{0}].DefaultMinTemp: {1}{2}", i, thermalSettings.Sensor[i].DefaultMinTemp, Environment.NewLine); r.AppendFormat(" Sensor[{0}].DefaultMaxTemp: {1}{2}", i, thermalSettings.Sensor[i].DefaultMaxTemp, Environment.NewLine); r.AppendFormat(" Sensor[{0}].CurrentTemp: {1}{2}", i, thermalSettings.Sensor[i].CurrentTemp, Environment.NewLine); r.AppendFormat(" Sensor[{0}].Target: {1}{2}", i, thermalSettings.Sensor[i].Target, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetAllClocks != null) { NvApi.NvGpuClockFrequencies clocks = GetClockFrequencies(out NvApi.NvStatus status); r.AppendLine("Clocks"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < clocks.Clocks.Length; i++) { if (clocks.Clocks[i].IsPresent) r.AppendFormat(" Clock[{0}]: {1}{2}", i, clocks.Clocks[i].Frequency, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetTachReading != null) { NvApi.NvStatus status = NvApi.NvAPI_GPU_GetTachReading(_handle, out int tachValue); r.AppendLine("Tachometer"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { r.AppendFormat(" Value: {0}{1}", tachValue, Environment.NewLine); } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetDynamicPstatesInfoEx != null) { NvApi.NvDynamicPStatesInfo pStatesInfo = GetDynamicPstatesInfoEx(out NvApi.NvStatus status); r.AppendLine("P-States"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < pStatesInfo.Utilizations.Length; i++) { if (pStatesInfo.Utilizations[i].IsPresent) r.AppendFormat(" Percentage[{0}]: {1}{2}", i, pStatesInfo.Utilizations[i].Percentage, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetUsages != null) { NvApi.NvUsages usages = GetUsages(out NvApi.NvStatus status); r.AppendLine("Usages"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < usages.Entries.Length; i++) { if (usages.Entries[i].IsPresent > 0) r.AppendFormat(" Usage[{0}]: {1}{2}", i, usages.Entries[i].Percentage, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetCoolerSettings != null) { NvApi.NvCoolerSettings coolerSettings = GetCoolerSettings(out NvApi.NvStatus status); r.AppendLine("Cooler Settings"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < coolerSettings.Count; i++) { r.AppendFormat(" Cooler[{0}].Type: {1}{2}", i, coolerSettings.Cooler[i].Type, Environment.NewLine); r.AppendFormat(" Cooler[{0}].Controller: {1}{2}", i, coolerSettings.Cooler[i].Controller, Environment.NewLine); r.AppendFormat(" Cooler[{0}].DefaultMin: {1}{2}", i, coolerSettings.Cooler[i].DefaultMin, Environment.NewLine); r.AppendFormat(" Cooler[{0}].DefaultMax: {1}{2}", i, coolerSettings.Cooler[i].DefaultMax, Environment.NewLine); r.AppendFormat(" Cooler[{0}].CurrentMin: {1}{2}", i, coolerSettings.Cooler[i].CurrentMin, Environment.NewLine); r.AppendFormat(" Cooler[{0}].CurrentMax: {1}{2}", i, coolerSettings.Cooler[i].CurrentMax, Environment.NewLine); r.AppendFormat(" Cooler[{0}].CurrentLevel: {1}{2}", i, coolerSettings.Cooler[i].CurrentLevel, Environment.NewLine); r.AppendFormat(" Cooler[{0}].DefaultPolicy: {1}{2}", i, coolerSettings.Cooler[i].DefaultPolicy, Environment.NewLine); r.AppendFormat(" Cooler[{0}].CurrentPolicy: {1}{2}", i, coolerSettings.Cooler[i].CurrentPolicy, Environment.NewLine); r.AppendFormat(" Cooler[{0}].Target: {1}{2}", i, coolerSettings.Cooler[i].Target, Environment.NewLine); r.AppendFormat(" Cooler[{0}].ControlType: {1}{2}", i, coolerSettings.Cooler[i].ControlType, Environment.NewLine); r.AppendFormat(" Cooler[{0}].Active: {1}{2}", i, coolerSettings.Cooler[i].Active, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_ClientFanCoolersGetStatus != null) { NvApi.NvFanCoolersStatus coolers = GetFanCoolersStatus(out NvApi.NvStatus status); r.AppendLine("Fan Coolers Status"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < coolers.Count; i++) { r.AppendFormat(" Items[{0}].CoolerId: {1}{2}", i, coolers.Items[i].CoolerId, Environment.NewLine); r.AppendFormat(" Items[{0}].CurrentRpm: {1}{2}", i, coolers.Items[i].CurrentRpm, Environment.NewLine); r.AppendFormat(" Items[{0}].CurrentMinLevel: {1}{2}", i, coolers.Items[i].CurrentMinLevel, Environment.NewLine); r.AppendFormat(" Items[{0}].CurrentMaxLevel: {1}{2}", i, coolers.Items[i].CurrentMaxLevel, Environment.NewLine); r.AppendFormat(" Items[{0}].CurrentLevel: {1}{2}", i, coolers.Items[i].CurrentLevel, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_ClientPowerTopologyGetStatus != null) { NvApi.NvPowerTopology powerTopology = GetPowerTopology(out NvApi.NvStatus status); r.AppendLine("Power Topology"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { for (int i = 0; i < powerTopology.Count; i++) { NvApi.NvPowerTopologyEntry entry = powerTopology.Entries[i]; _powers[i].Value = entry.PowerUsage / 1000f; r.AppendFormat(" Entries[{0}].Domain: {1}{2}", i, entry.Domain, Environment.NewLine); r.AppendFormat(" Entries[{0}].PowerUsage: {1}{2}", i, entry.PowerUsage, Environment.NewLine); } } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (NvApi.NvAPI_GPU_GetMemoryInfo != null) { NvApi.NvMemoryInfo memoryInfo = GetMemoryInfo(out NvApi.NvStatus status); r.AppendLine("Memory Info"); r.AppendLine(); if (status == NvApi.NvStatus.OK) { r.AppendFormat(" AvailableDedicatedVideoMemory: {0}{1}", memoryInfo.AvailableDedicatedVideoMemory, Environment.NewLine); r.AppendFormat(" DedicatedVideoMemory: {0}{1}", memoryInfo.DedicatedVideoMemory, Environment.NewLine); r.AppendFormat(" CurrentAvailableDedicatedVideoMemory: {0}{1}", memoryInfo.CurrentAvailableDedicatedVideoMemory, Environment.NewLine); r.AppendFormat(" SharedSystemMemory: {0}{1}", memoryInfo.SharedSystemMemory, Environment.NewLine); r.AppendFormat(" SystemVideoMemory: {0}{1}", memoryInfo.SystemVideoMemory, Environment.NewLine); } else { r.Append(" Status: "); r.AppendLine(status.ToString()); } r.AppendLine(); } if (_d3dDeviceId != null) { r.AppendLine("D3D"); r.AppendLine(); r.AppendLine(" Id: " + _d3dDeviceId); r.AppendLine(); } return r.ToString(); } private static string GetName(NvApi.NvPhysicalGpuHandle handle) { if (NvApi.NvAPI_GPU_GetFullName(handle, out string gpuName) == NvApi.NvStatus.OK) { string name = gpuName.Trim(); return name.StartsWith("NVIDIA", StringComparison.OrdinalIgnoreCase) ? name : "NVIDIA " + name; } return "NVIDIA"; } private NvApi.NvMemoryInfo GetMemoryInfo(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetMemoryInfo == null || _displayHandle == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvMemoryInfo memoryInfo = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(2) }; status = NvApi.NvAPI_GPU_GetMemoryInfo(_displayHandle.Value, ref memoryInfo); return status == NvApi.NvStatus.OK ? memoryInfo : default; } private NvApi.NvGpuClockFrequencies GetClockFrequencies(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetAllClockFrequencies == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvGpuClockFrequencies clockFrequencies = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(_clockVersion) }; status = NvApi.NvAPI_GPU_GetAllClockFrequencies(_handle, ref clockFrequencies); return status == NvApi.NvStatus.OK ? clockFrequencies : default; } private NvApi.NvThermalSettings GetThermalSettings(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetThermalSettings == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvThermalSettings settings = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1), Count = NvApi.MAX_THERMAL_SENSORS_PER_GPU }; status = NvApi.NvAPI_GPU_GetThermalSettings(_handle, (int)NvApi.NvThermalTarget.All, ref settings); return status == NvApi.NvStatus.OK ? settings : default; } private NvApi.NvThermalSensors GetThermalSensors(uint mask, out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_ThermalGetSensors == null) { status = NvApi.NvStatus.Error; return default; } var thermalSensors = new NvApi.NvThermalSensors { Version = (uint)NvApi.MAKE_NVAPI_VERSION(2), Mask = mask }; status = NvApi.NvAPI_GPU_ThermalGetSensors(_handle, ref thermalSensors); return status == NvApi.NvStatus.OK ? thermalSensors : default; } private NvApi.NvFanCoolersStatus GetFanCoolersStatus(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_ClientFanCoolersGetStatus == null) { status = NvApi.NvStatus.Error; return default; } var coolers = new NvApi.NvFanCoolersStatus { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1), Items = new NvApi.NvFanCoolersStatusItem[NvApi.MAX_FAN_COOLERS_STATUS_ITEMS] }; status = NvApi.NvAPI_GPU_ClientFanCoolersGetStatus(_handle, ref coolers); return status == NvApi.NvStatus.OK ? coolers : default; } private NvApi.NvFanCoolerControl GetFanCoolersControllers(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_ClientFanCoolersGetControl == null) { status = NvApi.NvStatus.Error; return default; } var controllers = new NvApi.NvFanCoolerControl { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1) }; status = NvApi.NvAPI_GPU_ClientFanCoolersGetControl(_handle, ref controllers); return status == NvApi.NvStatus.OK ? controllers : default; } private NvApi.NvCoolerSettings GetCoolerSettings(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetCoolerSettings == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvCoolerSettings settings = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(2), Cooler = new NvApi.NvCooler[NvApi.MAX_COOLERS_PER_GPU] }; status = NvApi.NvAPI_GPU_GetCoolerSettings(_handle, NvApi.NvCoolerTarget.All, ref settings); return status == NvApi.NvStatus.OK ? settings : default; } private NvApi.NvDynamicPStatesInfo GetDynamicPstatesInfoEx(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetDynamicPstatesInfoEx == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvDynamicPStatesInfo pStatesInfo = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1), Utilizations = new NvApi.NvDynamicPState[NvApi.MAX_GPU_UTILIZATIONS] }; status = NvApi.NvAPI_GPU_GetDynamicPstatesInfoEx(_handle, ref pStatesInfo); return status == NvApi.NvStatus.OK ? pStatesInfo : default; } private NvApi.NvUsages GetUsages(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetUsages == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvUsages usages = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1) }; status = NvApi.NvAPI_GPU_GetUsages(_handle, ref usages); return status == NvApi.NvStatus.OK ? usages : default; } private NvApi.NvPowerTopology GetPowerTopology(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_ClientPowerTopologyGetStatus == null) { status = NvApi.NvStatus.Error; return default; } NvApi.NvPowerTopology powerTopology = new() { Version = NvApi.MAKE_NVAPI_VERSION(1) }; status = NvApi.NvAPI_GPU_ClientPowerTopologyGetStatus(_handle, ref powerTopology); return status == NvApi.NvStatus.OK ? powerTopology : default; } private int GetTachReading(out NvApi.NvStatus status) { if (NvApi.NvAPI_GPU_GetTachReading == null) { status = NvApi.NvStatus.Error; return default; } status = NvApi.NvAPI_GPU_GetTachReading(_handle, out int value); return value; } private static string GetUtilizationDomainName(NvApi.NvUtilizationDomain utilizationDomain) => utilizationDomain switch { NvApi.NvUtilizationDomain.Gpu => "GPU Core", NvApi.NvUtilizationDomain.FrameBuffer => "GPU Memory Controller", NvApi.NvUtilizationDomain.VideoEngine => "GPU Video Engine", NvApi.NvUtilizationDomain.BusInterface => "GPU Bus", _ => null }; private void ControlModeChanged(IControl control) { switch (control.ControlMode) { case ControlMode.Default: RestoreDefaultFanBehavior(control.Sensor.Index); break; case ControlMode.Software: SoftwareControlValueChanged(control); break; } } private void SoftwareControlValueChanged(IControl control) { int index = control.Sensor?.Index ?? 0; NvApi.NvCoolerLevels coolerLevels = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1), Levels = new NvApi.NvLevel[NvApi.MAX_COOLERS_PER_GPU] }; coolerLevels.Levels[0].Level = (int)control.SoftwareValue; coolerLevels.Levels[0].Policy = NvApi.NvLevelPolicy.Manual; if (NvApi.NvAPI_GPU_SetCoolerLevels(_handle, index, ref coolerLevels) == NvApi.NvStatus.OK) return; NvApi.NvFanCoolerControl fanCoolersControllers = GetFanCoolersControllers(out _); for (int i = 0; i < fanCoolersControllers.Count; i++) { NvApi.NvFanCoolerControlItem nvFanCoolerControlItem = fanCoolersControllers.Items[i]; if (nvFanCoolerControlItem.CoolerId == index) { nvFanCoolerControlItem.ControlMode = NvApi.NvFanControlMode.Manual; nvFanCoolerControlItem.Level = (uint)control.SoftwareValue; fanCoolersControllers.Items[i] = nvFanCoolerControlItem; } } NvApi.NvAPI_GPU_ClientFanCoolersSetControl(_handle, ref fanCoolersControllers); } private void RestoreDefaultFanBehavior(int index) { NvApi.NvCoolerLevels coolerLevels = new() { Version = (uint)NvApi.MAKE_NVAPI_VERSION(1), Levels = new NvApi.NvLevel[NvApi.MAX_COOLERS_PER_GPU] }; coolerLevels.Levels[0].Policy = NvApi.NvLevelPolicy.Auto; if (NvApi.NvAPI_GPU_SetCoolerLevels(_handle, index, ref coolerLevels) == NvApi.NvStatus.OK) return; NvApi.NvFanCoolerControl fanCoolersControllers = GetFanCoolersControllers(out _); for (int i = 0; i < fanCoolersControllers.Count; i++) { NvApi.NvFanCoolerControlItem nvFanCoolerControlItem = fanCoolersControllers.Items[i]; if (nvFanCoolerControlItem.CoolerId == index) { nvFanCoolerControlItem.ControlMode = NvApi.NvFanControlMode.Auto; nvFanCoolerControlItem.Level = 0; fanCoolersControllers.Items[i] = nvFanCoolerControlItem; } } NvApi.NvAPI_GPU_ClientFanCoolersSetControl(_handle, ref fanCoolersControllers); } public override void Close() { if (_fanControls != null) { for (int i = 0; i < _fanControls.Length; i++) { _fanControls[i].ControlModeChanged -= ControlModeChanged; _fanControls[i].SoftwareControlValueChanged -= SoftwareControlValueChanged; if (_fanControls[i].ControlMode != ControlMode.Undefined) RestoreDefaultFanBehavior(i); } } base.Close(); } }