Skip to content

Commit

Permalink
Migrated from WMI to NVAPI for checking GPU activity.
Browse files Browse the repository at this point in the history
  • Loading branch information
BartoszCichecki committed Nov 30, 2021
1 parent 0ada9e0 commit a6d2063
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 64 deletions.
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/LenovoLegionToolkit.Lib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="6.0.0" />
<PackageReference Include="NvAPIWrapper.Net" Version="0.8.1.101" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="TaskScheduler" Version="2.9.2" />
</ItemGroup>
Expand Down
45 changes: 17 additions & 28 deletions LenovoLegionToolkit.Lib/Utils/GPUController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -11,11 +10,10 @@ public class GPUController
public enum Status
{
Unknown,
Inactive,
DeactivatePossible,
SingleVideoCardFound,
DiscreteNVGPUNotFound,
NVIDIAGPUNotFound,
MonitorsConnected,
DeactivatePossible,
Inactive,
}

public class RefreshedEventArgs : EventArgs
Expand Down Expand Up @@ -46,12 +44,12 @@ public RefreshedEventArgs(bool isActive, bool canBeDeactivated, Status status, i
private IEnumerable<string> _processNames = null;
private string _pnpDeviceId = null;

private bool IsActive => _status == Status.SingleVideoCardFound || _status == Status.MonitorsConnected || _status == Status.DeactivatePossible;
private bool IsActive => _status == Status.MonitorsConnected || _status == Status.DeactivatePossible;
private bool CanBeDeactivated => _status == Status.DeactivatePossible;

public event EventHandler<RefreshedEventArgs> Refreshed;

public void Start(int interval = 2_000)
public void Start(int delay = 2_500, int interval = 5_000)
{
Stop(true);

Expand All @@ -60,6 +58,8 @@ public void Start(int interval = 2_000)

_refreshTask = Task.Run(async () =>
{
await Task.Delay(delay, token);

while (true)
{
token.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -104,42 +104,31 @@ private void Refresh()
_processNames = null;
_pnpDeviceId = null;

var videoControllers = OS.GetVideoControllersInformation();
if (videoControllers.Count() < 2)
if (!NVAPIWrapper.IsGPUPresent(out var gpu))
{
_status = Status.SingleVideoCardFound;
_status = Status.NVIDIAGPUNotFound;
return;
}

var nvidiaVideoController = videoControllers
.Where(vci => vci.IsNVidia())
.Cast<VideoCardInformation?>()
.FirstOrDefault();
if (nvidiaVideoController == null)
if (!NVAPIWrapper.IsGPUActive(gpu))
{
_status = Status.DiscreteNVGPUNotFound;
_status = Status.Inactive;
return;
}

var nvidiaInformation = OS.GetNVidiaInformation();
if (nvidiaInformation.DisplayActive)
{
_status = Status.MonitorsConnected;
_processCount = nvidiaInformation.ProcessCount;
_processNames = nvidiaInformation.ProcessNames;
return;
}

if (nvidiaInformation.ProcessCount < 1)
_processCount = nvidiaInformation.ProcessCount;
_processNames = nvidiaInformation.ProcessNames;

if (NVAPIWrapper.IsDisplayConnected(gpu))
{
_status = Status.Inactive;
_status = Status.MonitorsConnected;
return;
}

_pnpDeviceId = NVAPIWrapper.GetGPUId(gpu);
_status = Status.DeactivatePossible;
_processCount = nvidiaInformation.ProcessCount;
_processNames = nvidiaInformation.ProcessNames;
_pnpDeviceId = nvidiaVideoController.Value.PnpDeviceId;
}
}
}
96 changes: 96 additions & 0 deletions LenovoLegionToolkit.Lib/Utils/NVAPIWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NvAPIWrapper;
using NvAPIWrapper.Display;
using NvAPIWrapper.GPU;
using NvAPIWrapper.Native.Exceptions;
using NvAPIWrapper.Native.GPU;

namespace LenovoLegionToolkit.Lib.Utils
{
static class NVAPIWrapper
{
private static bool _isInitialized = false;

private static void EnsureInitialized()
{
if (_isInitialized) return;
_isInitialized = true;
NVIDIA.Initialize();
}

public static bool IsGPUPresent(out PhysicalGPU gpu)
{
try
{
EnsureInitialized();
gpu = PhysicalGPU.GetPhysicalGPUs().FirstOrDefault(gpu => gpu.SystemType == SystemType.Laptop);
return gpu != null;
}
catch (NVIDIAApiException)
{
gpu = null;
return false;
}
}

public static bool IsGPUActive(PhysicalGPU gpu)
{
try
{
EnsureInitialized();
if (gpu == null)
return false;
_ = gpu.PerformanceStatesInfo;
return true;
}
catch (NVIDIAApiException)
{
return false;
}
}

public static bool IsDisplayConnected(PhysicalGPU gpu)
{
try
{
EnsureInitialized();
if (gpu == null)
return false;

return Display.GetDisplays().Any(d => d.PhysicalGPUs.Contains(gpu, PhysicalGPUEqualityComparer.Instance));
}
catch (NVIDIAApiException)
{
return false;
}
}

public static string GetGPUId(PhysicalGPU gpu)
{
try
{
EnsureInitialized();
if (gpu == null)
return null;
return gpu.BusInformation.PCIIdentifiers.ToString();
}
catch (NVIDIAApiException)
{
return null;
}
}

private class PhysicalGPUEqualityComparer : IEqualityComparer<PhysicalGPU>
{
public static PhysicalGPUEqualityComparer Instance = new();

private PhysicalGPUEqualityComparer() { }

public bool Equals(PhysicalGPU x, PhysicalGPU y) => x.GPUId == y.GPUId;

public int GetHashCode([DisallowNull] PhysicalGPU obj) => obj.GPUId.GetHashCode();
}
}
}
30 changes: 1 addition & 29 deletions LenovoLegionToolkit.Lib/Utils/OS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,17 @@ public struct MachineInformation
public string Model;
}

public struct VideoCardInformation
{
public string Manufacturer;
public string PnpDeviceId;
}

public struct NVidiaInformation
{
public bool DisplayActive;
public int ProcessCount;
public IEnumerable<string> ProcessNames;
}

public static class VideoCardInformationExtensions
{
public static bool IsNVidia(this VideoCardInformation vci) => vci.Manufacturer.Contains("nvidia", System.StringComparison.OrdinalIgnoreCase);
}

public static class OS
{
public static void Restart() => ExecuteProcess("shutdown", "-r -t 0");

public static void RestartDevice(string _pnpDeviceId) => ExecuteProcess("pnputil", $"-restart-device \"{_pnpDeviceId}\"");
public static void RestartDevice(string _pnpDeviceId) => ExecuteProcess("pnputil", $"/restart-device /deviceid \"{_pnpDeviceId}\"");

public static void SetPowerPlan(string guid) => ExecuteProcess("powercfg", $"-setactive {guid}");

Expand All @@ -45,14 +33,12 @@ public static NVidiaInformation GetNVidiaInformation()

var xdoc = XDocument.Parse(output);
var gpu = xdoc.Element("nvidia_smi_log").Element("gpu");
var displayActive = gpu.Element("display_active").Value == "Enabled";
var processInfo = gpu.Element("processes").Elements("process_info");
var processesCount = processInfo.Count();
var processNames = processInfo.Select(e => e.Element("process_name").Value).Select(Path.GetFileName);

return new NVidiaInformation
{
DisplayActive = displayActive,
ProcessCount = processesCount,
ProcessNames = processNames,
};
Expand All @@ -74,20 +60,6 @@ public static MachineInformation GetMachineInformation()
}
return default;
}
public static IEnumerable<VideoCardInformation> GetVideoControllersInformation()
{
var searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_VideoController");
foreach (var queryObj in searcher.Get())
{
var manufacturer = queryObj["AdapterCompatibility"].ToString();
var pnpDeviceId = queryObj["PNPDeviceID"].ToString();
yield return new VideoCardInformation
{
Manufacturer = manufacturer,
PnpDeviceId = pnpDeviceId,
};
}
}

private static void ExecuteProcess(string file, string arguments)
{
Expand Down
9 changes: 4 additions & 5 deletions LenovoLegionToolkit.WPF/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private void gpuController_Refreshed(object sender, GPUController.RefreshedEvent
{
Dispatcher.Invoke(() =>
{
if (e.Status == GPUController.Status.Unknown || e.Status == GPUController.Status.DiscreteNVGPUNotFound || e.Status == GPUController.Status.SingleVideoCardFound)
if (e.Status == GPUController.Status.Unknown || e.Status == GPUController.Status.NVIDIAGPUNotFound)
{
lblDiscreteGPUStatus.Content = "-";
lblDiscreteGPUStatus.ToolTip = null;
Expand Down Expand Up @@ -205,10 +205,9 @@ private void gpuController_Refreshed(object sender, GPUController.RefreshedEvent
btnDeactivateDiscreteGPU.IsEnabled = e.CanBeDeactivated;
btnDeactivateDiscreteGPU.ToolTip = e.Status switch
{
GPUController.Status.DiscreteNVGPUNotFound => "Discrete nVidia GPU not found. AMD GPUs are not supported.",
GPUController.Status.SingleVideoCardFound => "There is only one GPU active.",
GPUController.Status.MonitorsConnected => "Monitor is connected to discrete GPU.",
GPUController.Status.DeactivatePossible => "Discrete GPU can be disabled. Remember, that some programs might crash if you do it.",
GPUController.Status.NVIDIAGPUNotFound => "Discrete nVidia GPU not found. AMD GPUs are not supported.",
GPUController.Status.MonitorsConnected => "A monitor is connected to nVidia GPU.",
GPUController.Status.DeactivatePossible => "nVidia GPU can be disabled.\n\nRemember, that some programs might crash if you do it.",
_ => null,
};
});
Expand Down
4 changes: 2 additions & 2 deletions LenovoLegionToolkit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LenovoLegionToolkit", "LenovoLegionToolkit.WPF\LenovoLegionToolkit.WPF.csproj", "{DC01FDB3-CDCF-4D08-B87E-CCCAE5A24FF7}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LenovoLegionToolkit.WPF", "LenovoLegionToolkit.WPF\LenovoLegionToolkit.WPF.csproj", "{DC01FDB3-CDCF-4D08-B87E-CCCAE5A24FF7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LenovoLegionToolkit.Lib", "LenovoLegionToolkit.Lib\LenovoLegionToolkit.Lib.csproj", "{4B902DDC-0EBE-44B3-A53B-5EA176C7522B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LenovoLegionToolkit.Lib", "LenovoLegionToolkit.Lib\LenovoLegionToolkit.Lib.csproj", "{4B902DDC-0EBE-44B3-A53B-5EA176C7522B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down

0 comments on commit a6d2063

Please sign in to comment.