Add intel vram sensors (#2077)

* Add Intel Arc VRAM sensors support

- Implement BDF-based device matching for Intel discrete GPUs
- Add VRAM sensors (total, used, free, load percentage)
- Use D3D device enumeration with fallback logic
- Enable Windows-specific VRAM monitoring for Intel Arc GPUs
- Fix device identification to distinguish between multiple identical GPUs

Resolves issue where both Intel Arc GPUs showed identical VRAM values.
Now each GPU displays unique, accurate memory usage statistics.

* Add defensive validation to GetBdfFromDeviceIdentifier

- Add null/empty check for deviceIdentifier parameter
- Return false explicitly when LastIndexOf('#') returns -1
- Add DEVPROPTYPE.DEVPROP_TYPE_UINT32 validation after property queries
- Use sizeof(uint) instead of hardcoded 4 for buffer sizes
- Apply PCI spec bit masks (5-bit device, 3-bit function)
- Add bus range validation (0-255)

* Revert to Windows DEVPKEY_Device_Address spec masks

Use 0xFFFF masks as per Windows documentation instead of PCI spec
bit widths. The Windows API uses full 16-bit fields for device and
function numbers.

* Update IntelDiscreteGpu.cs

* Update NativeMethods.txt

---------

Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
This commit is contained in:
David Renelt
2025-11-28 21:54:04 +01:00
committed by GitHub
co-authored by PhyxionNL
parent 6bb1c627e1
commit a792c26891
2 changed files with 105 additions and 6 deletions
@@ -6,6 +6,10 @@
using System;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
using Windows.Win32;
using Windows.Win32.Devices.DeviceAndDriverInstallation;
using Windows.Win32.Devices.Properties;
using Windows.Win32.Foundation;
namespace LibreHardwareMonitor.Hardware.Gpu;
@@ -207,10 +211,26 @@ internal sealed class IntelDiscreteGpu : GenericGpu
if (deviceIdentifier.IndexOf(vendorPattern, StringComparison.OrdinalIgnoreCase) != -1 &&
deviceIdentifier.IndexOf(devicePattern, StringComparison.OrdinalIgnoreCase) != -1)
{
// Verify it's a valid D3D device by trying to get device info
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceIdentifier, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
// Check BDF if available
if (GetBdfFromDeviceIdentifier(deviceIdentifier, out int bus, out int device, out int function))
{
return deviceIdentifier;
if (bus == _properties.adapter_bdf.bus && device == _properties.adapter_bdf.device && function == _properties.adapter_bdf.function)
{
// Verify it's a valid D3D device by trying to get device info
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceIdentifier, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
return deviceIdentifier;
}
}
}
else
{
// Fallback to old behavior if BDF check fails (e.g. P/Invoke error)
// Verify it's a valid D3D device by trying to get device info
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceIdentifier, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
return deviceIdentifier;
}
}
}
}
@@ -497,7 +517,7 @@ internal sealed class IntelDiscreteGpu : GenericGpu
{
if (string.IsNullOrEmpty(_d3dDeviceId))
{
// Fallback: Try to find any Intel discrete GPU
// Fallback: Try to find any Intel D3D device
string[] deviceIdentifiers = D3DDisplayDevice.GetDeviceIdentifiers();
if (deviceIdentifiers != null)
{
@@ -524,7 +544,7 @@ internal sealed class IntelDiscreteGpu : GenericGpu
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(_d3dDeviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
{
// Get dedicated video memory (VRAM) usage
ulong totalBytes = deviceInfo.GpuDedicatedLimit;
ulong totalBytes = deviceInfo.GpuVideoMemoryLimit;
ulong usedBytes = deviceInfo.GpuDedicatedUsed;
ulong freeBytes = totalBytes > usedBytes ? totalBytes - usedBytes : 0;
@@ -556,7 +576,7 @@ internal sealed class IntelDiscreteGpu : GenericGpu
if (bandwidthItem.bSupported)
{
double bandwidthValue = GetTelemetryValue(bandwidthItem);
if (!double.IsNaN(bandwidthValue) && bandwidthValue >= 0)
{
// Bandwidth is typically in GB/s or MB/s, convert to B/s for Throughput sensor
@@ -585,4 +605,76 @@ internal sealed class IntelDiscreteGpu : GenericGpu
bandwidthSensor.Value = null;
}
}
private unsafe bool GetBdfFromDeviceIdentifier(string deviceIdentifier, out int bus, out int device, out int function)
{
bus = -1;
device = -1;
function = -1;
if (string.IsNullOrEmpty(deviceIdentifier))
return false;
// deviceIdentifier is like "\\?\PCI#VEN_8086&DEV_56A0&SUBSYS_10208086&REV_08#4&3834663c&0&0008#{5b45201d-f2f2-4f3b-85bb-30ff1f953599}"
// We need to extract the instance ID from this.
// The instance ID is "PCI\VEN_8086&DEV_56A0&SUBSYS_10208086&REV_08\4&3834663c&0&0008"
// Extract the device instance path from the interface path
// Remove "\\?\" prefix and the GUID suffix
string instanceId = deviceIdentifier;
if (instanceId.StartsWith(@"\\?\"))
instanceId = instanceId.Substring(4);
int lastHash = instanceId.LastIndexOf('#');
if (lastHash == -1)
return false;
instanceId = instanceId.Substring(0, lastHash);
// Replace '#' with '\' to match the Instance ID format expected by CM
instanceId = instanceId.Replace('#', '\\');
uint devInst;
fixed (char* pInstanceId = instanceId)
{
if (PInvoke.CM_Locate_DevNode(out devInst, (PWSTR)pInstanceId, CM_LOCATE_DEVNODE_FLAGS.CM_LOCATE_DEVNODE_NORMAL) != CONFIGRET.CR_SUCCESS)
{
return false;
}
}
DEVPROPTYPE propertyType;
uint bufferSize = sizeof(uint);
uint busNum = 0;
uint address = 0;
fixed (DEVPROPKEY* pBusKey = &PInvoke.DEVPKEY_Device_BusNumber)
{
if (PInvoke.CM_Get_DevNode_Property(devInst, pBusKey, &propertyType, (byte*)&busNum, &bufferSize, 0) == CONFIGRET.CR_SUCCESS && propertyType == DEVPROPTYPE.DEVPROP_TYPE_UINT32)
{
bus = (int)busNum;
}
else
{
return false;
}
}
bufferSize = sizeof(uint);
propertyType = default;
fixed (DEVPROPKEY* pAddrKey = &PInvoke.DEVPKEY_Device_Address)
{
if (PInvoke.CM_Get_DevNode_Property(devInst, pAddrKey, &propertyType, (byte*)&address, &bufferSize, 0) == CONFIGRET.CR_SUCCESS &&
propertyType == DEVPROPTYPE.DEVPROP_TYPE_UINT32)
{
// Address contains device and function per Windows DEVPKEY_Device_Address spec
// Bits 16-31: Device number
// Bits 0-15: Function number
device = (int)(address >> 16) & 0xFFFF;
function = (int)address & 0xFFFF;
return true;
}
}
return false;
}
}
@@ -17,6 +17,8 @@ SetupDiDestroyDeviceInfoList
SetupDiGetDeviceInterfaceDetail
SetupDiEnumDeviceInterfaces
SetupDiGetClassDevs
SP_DEVICE_INTERFACE_DATA
SP_DEVICE_INTERFACE_DETAIL_DATA_W
GUID_DEVICE_BATTERY
WIN32_ERROR
HRESULT_FROM_WIN32
@@ -42,3 +44,8 @@ LoadLibrary
FreeLibrary
GetProcAddress
BATTERY_UNKNOWN_*
CM_Locate_DevNode
CM_Get_DevNode_Property
CM_LOCATE_DEVNODE_FLAGS
DEVPKEY_Device_BusNumber
DEVPKEY_Device_Address