* 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>
681 lines
25 KiB
C#
681 lines
25 KiB
C#
// 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.
|
|
// All Rights Reserved.
|
|
|
|
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;
|
|
|
|
internal sealed class IntelDiscreteGpu : GenericGpu
|
|
{
|
|
// Constants
|
|
private const double MemoryFrequencyDivisor = 8.0; // Intel GCL returns memory frequency multiplied by 8
|
|
|
|
// Clock sensors
|
|
private readonly Sensor _clockCore;
|
|
private readonly Sensor _clockMemory;
|
|
|
|
// Fan sensors
|
|
private readonly Sensor[] _fans;
|
|
|
|
// Utilization sensors
|
|
private readonly Sensor _loadGlobalActivity;
|
|
private readonly Sensor _loadMedia;
|
|
private readonly Sensor _loadRenderCompute;
|
|
|
|
// Power sensors
|
|
private readonly Sensor _powerGpu;
|
|
private readonly Sensor _powerTotal;
|
|
|
|
// Temperature sensors
|
|
private readonly Sensor _temperatureGpuCore;
|
|
private readonly Sensor _temperatureMemory;
|
|
|
|
// Voltage sensors
|
|
private readonly Sensor _voltageCore;
|
|
private readonly Sensor _voltageMemory;
|
|
|
|
// Memory sensors
|
|
private readonly Sensor _memoryFree;
|
|
private readonly Sensor _memoryTotal;
|
|
private readonly Sensor _memoryUsed;
|
|
private readonly Sensor _memoryLoad;
|
|
|
|
// Bandwidth sensors
|
|
private readonly Sensor _memoryBandwidthRead;
|
|
private readonly Sensor _memoryBandwidthWrite;
|
|
|
|
// Timestamps
|
|
private double _currentTimestamp = double.NaN;
|
|
private string _deviceId;
|
|
private string _d3dDeviceId;
|
|
|
|
// Intel GCL properties and data
|
|
private readonly IntelGcl.ctl_device_adapter_handle_t _handle;
|
|
|
|
// Power calculation support
|
|
private double _lastEnergyReading = double.NaN;
|
|
|
|
// Activity counter calculation support
|
|
private double _lastGlobalActivityCounter = double.NaN;
|
|
private double _lastMediaActivityCounter = double.NaN;
|
|
private double _lastRenderComputeActivityCounter = double.NaN;
|
|
private double _lastTimestamp = double.NaN;
|
|
private double _lastTotalCardEnergyReading = double.NaN;
|
|
private double _lastVramReadBandwidthCounter = double.NaN;
|
|
private double _lastVramWriteBandwidthCounter = double.NaN;
|
|
private IntelGcl.ctl_device_adapter_properties_t _properties;
|
|
|
|
// Telemetry data
|
|
private IntelGcl.ctl_power_telemetry_t _telemetry;
|
|
|
|
public IntelDiscreteGpu(IntelGcl.ctl_device_adapter_handle_t handle, ISettings settings)
|
|
: base(GetDeviceName(handle), new Identifier("gpu-intel", GetDeviceId(handle)), settings)
|
|
{
|
|
_handle = handle;
|
|
IsValid = false;
|
|
|
|
// Initialize device properties
|
|
if (!InitializeDevice())
|
|
return;
|
|
|
|
// Try to get D3D device identifier for memory monitoring
|
|
_d3dDeviceId = GetD3DDeviceId();
|
|
|
|
// Initialize temperature sensors
|
|
_temperatureGpuCore = new Sensor("GPU Core", 0, SensorType.Temperature, this, settings);
|
|
_temperatureMemory = new Sensor("GPU Memory", 1, SensorType.Temperature, this, settings);
|
|
|
|
// Initialize clock sensors
|
|
_clockCore = new Sensor("GPU Core", 0, SensorType.Clock, this, settings);
|
|
_clockMemory = new Sensor("GPU Memory", 1, SensorType.Clock, this, settings);
|
|
|
|
// Initialize voltage sensors
|
|
_voltageCore = new Sensor("GPU Core", 0, SensorType.Voltage, this, settings);
|
|
_voltageMemory = new Sensor("GPU Memory", 1, SensorType.Voltage, this, settings);
|
|
|
|
// Initialize power sensors
|
|
_powerGpu = new Sensor("GPU Package", 0, SensorType.Power, this, settings);
|
|
_powerTotal = new Sensor("GPU Total", 1, SensorType.Power, this, settings);
|
|
|
|
// Initialize utilization sensors
|
|
_loadGlobalActivity = new Sensor("GPU Core", 0, SensorType.Load, this, settings);
|
|
_loadRenderCompute = new Sensor("GPU Render/Compute", 1, SensorType.Load, this, settings);
|
|
_loadMedia = new Sensor("GPU Media", 2, SensorType.Load, this, settings);
|
|
|
|
// Initialize memory sensors
|
|
_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);
|
|
|
|
// Initialize bandwidth sensors
|
|
_memoryBandwidthRead = new Sensor("GPU Memory Read", 0, SensorType.Throughput, this, settings);
|
|
_memoryBandwidthWrite = new Sensor("GPU Memory Write", 1, SensorType.Throughput, this, settings);
|
|
|
|
// Initialize fan sensors based on available fans
|
|
int fanCount = (int)GetFanCount();
|
|
_fans = new Sensor[fanCount];
|
|
|
|
for (int i = 0; i < fanCount; i++)
|
|
{
|
|
string fanName = fanCount == 1 ? "GPU Fan" : $"GPU Fan {i + 1}";
|
|
_fans[i] = new Sensor(fanName, i, SensorType.Fan, this, settings);
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
public override string DeviceId => _deviceId ?? GetDeviceId(_handle);
|
|
|
|
public uint DriverVersion { get; private set; }
|
|
|
|
public override HardwareType HardwareType => HardwareType.GpuIntel;
|
|
|
|
public bool IsValid { get; private set; }
|
|
|
|
public uint RevisionId { get; private set; }
|
|
|
|
public uint VendorId { get; private set; }
|
|
|
|
private static bool TryGetDeviceProperties(IntelGcl.ctl_device_adapter_handle_t handle, out IntelGcl.ctl_device_adapter_properties_t properties)
|
|
{
|
|
properties = new IntelGcl.ctl_device_adapter_properties_t
|
|
{
|
|
Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_device_adapter_properties_t)),
|
|
Version = 2
|
|
};
|
|
|
|
int result = IntelGcl.ctlGetDeviceProperties(handle, ref properties);
|
|
return result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS &&
|
|
properties.device_type == IntelGcl.ctl_device_type_t.CTL_DEVICE_TYPE_GRAPHICS;
|
|
}
|
|
|
|
private static string GetDeviceName(IntelGcl.ctl_device_adapter_handle_t handle)
|
|
{
|
|
if (TryGetDeviceProperties(handle, out IntelGcl.ctl_device_adapter_properties_t properties))
|
|
{
|
|
return properties.name;
|
|
}
|
|
|
|
return "Intel GPU";
|
|
}
|
|
|
|
private static string GetDeviceId(IntelGcl.ctl_device_adapter_handle_t handle)
|
|
{
|
|
if (TryGetDeviceProperties(handle, out IntelGcl.ctl_device_adapter_properties_t properties))
|
|
{
|
|
return $"0x{properties.pci_device_id:X4}";
|
|
}
|
|
|
|
return "0x0000";
|
|
}
|
|
|
|
// Device initialization
|
|
private bool InitializeDevice()
|
|
{
|
|
if (TryGetDeviceProperties(_handle, out _properties))
|
|
{
|
|
_deviceId = $"0x{_properties.pci_device_id:X4}";
|
|
VendorId = _properties.pci_vendor_id;
|
|
RevisionId = _properties.rev_id;
|
|
DriverVersion = (uint)_properties.driver_version;
|
|
IsValid = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private string GetD3DDeviceId()
|
|
{
|
|
// Try to find matching D3D device using PCI device ID
|
|
string[] deviceIdentifiers = D3DDisplayDevice.GetDeviceIdentifiers();
|
|
if (deviceIdentifiers == null || deviceIdentifiers.Length == 0)
|
|
return null;
|
|
|
|
// Intel vendor ID is 0x8086
|
|
string vendorPattern = $"VEN_{_properties.pci_vendor_id:X}";
|
|
string devicePattern = $"DEV_{_properties.pci_device_id:X}";
|
|
|
|
foreach (string deviceIdentifier in deviceIdentifiers)
|
|
{
|
|
// Check if this device matches Intel vendor and device IDs
|
|
if (deviceIdentifier.IndexOf(vendorPattern, StringComparison.OrdinalIgnoreCase) != -1 &&
|
|
deviceIdentifier.IndexOf(devicePattern, StringComparison.OrdinalIgnoreCase) != -1)
|
|
{
|
|
// Check BDF if available
|
|
if (GetBdfFromDeviceIdentifier(deviceIdentifier, out int bus, out int device, out int function))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override void Update()
|
|
{
|
|
if (!IsValid)
|
|
return;
|
|
|
|
try
|
|
{
|
|
// Update telemetry data from Intel GCL
|
|
if (!UpdateTelemetry())
|
|
return;
|
|
|
|
// Update power sensors
|
|
UpdatePowerFromEnergyCounter(_telemetry.gpuEnergyCounter, ref _lastEnergyReading, _powerGpu);
|
|
UpdatePowerFromEnergyCounter(_telemetry.totalCardEnergyCounter, ref _lastTotalCardEnergyReading, _powerTotal);
|
|
|
|
// Update temperature sensors
|
|
UpdateSensorFromTelemetry(_telemetry.gpuCurrentTemperature, _temperatureGpuCore);
|
|
UpdateSensorFromTelemetry(_telemetry.vramCurrentTemperature, _temperatureMemory);
|
|
|
|
// Update clock sensors
|
|
UpdateSensorFromTelemetry(_telemetry.gpuCurrentClockFrequency, _clockCore);
|
|
UpdateMemoryFrequency(_clockMemory);
|
|
|
|
// Update voltage sensors
|
|
UpdateSensorFromTelemetry(_telemetry.gpuVoltage, _voltageCore);
|
|
UpdateSensorFromTelemetry(_telemetry.vramVoltage, _voltageMemory);
|
|
|
|
// Update utilization sensors
|
|
UpdateUtilizationFromActivityCounter(_telemetry.globalActivityCounter, ref _lastGlobalActivityCounter, _loadGlobalActivity);
|
|
UpdateUtilizationFromActivityCounter(_telemetry.renderComputeActivityCounter, ref _lastRenderComputeActivityCounter, _loadRenderCompute);
|
|
UpdateUtilizationFromActivityCounter(_telemetry.mediaActivityCounter, ref _lastMediaActivityCounter, _loadMedia);
|
|
|
|
// Try to get D3D device ID if we haven't found it yet
|
|
if (string.IsNullOrEmpty(_d3dDeviceId))
|
|
{
|
|
_d3dDeviceId = GetD3DDeviceId();
|
|
}
|
|
|
|
// Update VRAM memory sensors (using D3D API)
|
|
UpdateMemorySensors();
|
|
|
|
// Update VRAM bandwidth sensors
|
|
UpdateBandwidthFromCounter(_telemetry.vramReadBandwidth, ref _lastVramReadBandwidthCounter, _memoryBandwidthRead);
|
|
UpdateBandwidthFromCounter(_telemetry.vramWriteBandwidth, ref _lastVramWriteBandwidthCounter, _memoryBandwidthWrite);
|
|
|
|
// Update fan sensors
|
|
UpdateFanSpeeds(_fans);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log error but don't crash the update
|
|
System.Diagnostics.Debug.WriteLine($"Error updating Intel GPU sensors: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private bool UpdateTelemetry()
|
|
{
|
|
if (!IsValid)
|
|
return false;
|
|
|
|
var telemetry = new IntelGcl.ctl_power_telemetry_t
|
|
{
|
|
Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_power_telemetry_t)),
|
|
Version = 1,
|
|
psu = new IntelGcl.ctl_psu_info_t[IntelGcl.CTL_PSU_COUNT],
|
|
fanSpeed = new IntelGcl.ctl_oc_telemetry_item_t[IntelGcl.CTL_FAN_COUNT]
|
|
};
|
|
|
|
if (IntelGcl.ctlPowerTelemetryGet(_handle, ref telemetry) == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS)
|
|
{
|
|
_telemetry = telemetry;
|
|
_lastTimestamp = _currentTimestamp;
|
|
_currentTimestamp = _telemetry.timeStamp.bSupported ? GetTelemetryValue(_telemetry.timeStamp) : DateTimeOffset.UtcNow.Ticks;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void UpdateMemoryFrequency(Sensor sensor)
|
|
{
|
|
double frequency = double.NaN;
|
|
|
|
uint freqCount = 0;
|
|
int result = IntelGcl.ctlEnumFrequencyDomains(_handle, ref freqCount, null);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && freqCount > 0)
|
|
{
|
|
var freqHandles = new IntelGcl.ctl_freq_handle_t[freqCount];
|
|
result = IntelGcl.ctlEnumFrequencyDomains(_handle, ref freqCount, freqHandles);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS)
|
|
{
|
|
for (int i = 0; i < freqCount; i++)
|
|
{
|
|
var properties = new IntelGcl.ctl_freq_properties_t
|
|
{
|
|
Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_freq_properties_t)),
|
|
Version = 0
|
|
};
|
|
|
|
result = IntelGcl.ctlFrequencyGetProperties(freqHandles[i], ref properties);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS &&
|
|
properties.type == IntelGcl.ctl_freq_domain_t.CTL_FREQ_DOMAIN_MEMORY)
|
|
{
|
|
var state = new IntelGcl.ctl_freq_state_t
|
|
{
|
|
Size = (uint)Marshal.SizeOf(typeof(IntelGcl.ctl_freq_state_t)),
|
|
Version = 0
|
|
};
|
|
|
|
result = IntelGcl.ctlFrequencyGetState(freqHandles[i], ref state);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && state.actual >= 0)
|
|
{
|
|
frequency = state.actual / MemoryFrequencyDivisor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (double.IsNaN(frequency) && _telemetry.vramCurrentClockFrequency.bSupported)
|
|
{
|
|
frequency = GetTelemetryValue(_telemetry.vramCurrentClockFrequency);
|
|
}
|
|
|
|
if (!double.IsNaN(frequency))
|
|
{
|
|
sensor.Value = (float)frequency;
|
|
ActivateSensor(sensor);
|
|
}
|
|
else
|
|
{
|
|
sensor.Value = null;
|
|
}
|
|
}
|
|
|
|
private uint GetFanCount()
|
|
{
|
|
uint fanCount = 0;
|
|
int result = IntelGcl.ctlEnumFans(_handle, ref fanCount, null);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS)
|
|
{
|
|
return fanCount;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private void UpdateFanSpeeds(Sensor[] fanSensors)
|
|
{
|
|
uint fanCount = (uint)Math.Min(Math.Max(0, GetFanCount()), fanSensors.Length);
|
|
if (fanCount == 0)
|
|
return;
|
|
|
|
var fanHandles = new IntelGcl.ctl_fan_handle_t[fanCount];
|
|
int result = IntelGcl.ctlEnumFans(_handle, ref fanCount, fanHandles);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS)
|
|
{
|
|
for (int i = 0; i < fanCount; i++)
|
|
{
|
|
int fanSpeed = -1;
|
|
result = IntelGcl.ctlFanGetState(fanHandles[i], IntelGcl.ctl_fan_speed_units_t.CTL_FAN_SPEED_UNITS_RPM, ref fanSpeed);
|
|
|
|
if (result == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS && fanSpeed >= 0)
|
|
{
|
|
fanSensors[i].Value = fanSpeed;
|
|
ActivateSensor(fanSensors[i]);
|
|
}
|
|
else
|
|
{
|
|
fanSensors[i].Value = null;
|
|
}
|
|
}
|
|
|
|
for (int i = (int)fanCount; i < fanSensors.Length; i++)
|
|
{
|
|
fanSensors[i].Value = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateSensorFromTelemetry(IntelGcl.ctl_oc_telemetry_item_t telemetryItem, Sensor sensor)
|
|
{
|
|
if (telemetryItem.bSupported)
|
|
{
|
|
sensor.Value = (float)GetTelemetryValue(telemetryItem);
|
|
ActivateSensor(sensor);
|
|
}
|
|
else
|
|
{
|
|
sensor.Value = null;
|
|
}
|
|
}
|
|
|
|
private double GetTelemetryValue(IntelGcl.ctl_oc_telemetry_item_t item)
|
|
{
|
|
return item.type switch
|
|
{
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_FLOAT => item.value.datafloat,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_DOUBLE => item.value.datadouble,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT32 => item.value.datau32,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT32 => item.value.data32,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT64 => item.value.datau64,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT64 => item.value.data64,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT16 => item.value.datau16,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT16 => item.value.data16,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_UINT8 => item.value.datau8,
|
|
IntelGcl.ctl_data_type_t.CTL_DATA_TYPE_INT8 => item.value.data8,
|
|
_ => double.NaN
|
|
};
|
|
}
|
|
|
|
private void UpdatePowerFromEnergyCounter(IntelGcl.ctl_oc_telemetry_item_t energyCounter, ref double lastEnergyReading, Sensor powerSensor)
|
|
{
|
|
if (!IsValid || powerSensor == null)
|
|
return;
|
|
|
|
double currentEnergy = energyCounter.bSupported ? GetTelemetryValue(energyCounter) : double.NaN;
|
|
double deltaTime = _currentTimestamp - _lastTimestamp;
|
|
|
|
if (deltaTime > 0.0 && !double.IsNaN(currentEnergy) && !double.IsNaN(lastEnergyReading))
|
|
{
|
|
double deltaEnergy = currentEnergy - lastEnergyReading;
|
|
double power = deltaEnergy / deltaTime;
|
|
power = power < 0 ? 0 : power;
|
|
|
|
powerSensor.Value = (float)power;
|
|
ActivateSensor(powerSensor);
|
|
}
|
|
else
|
|
{
|
|
powerSensor.Value = null;
|
|
}
|
|
|
|
lastEnergyReading = currentEnergy;
|
|
}
|
|
|
|
private void UpdateUtilizationFromActivityCounter(IntelGcl.ctl_oc_telemetry_item_t activityCounter, ref double lastActivityReading, Sensor activitySensor)
|
|
{
|
|
if (!IsValid || activitySensor == null)
|
|
return;
|
|
|
|
double currentActivity = activityCounter.bSupported ? GetTelemetryValue(activityCounter) : double.NaN;
|
|
double deltaTime = _currentTimestamp - _lastTimestamp;
|
|
|
|
if (deltaTime > 0 && !double.IsNaN(currentActivity) && !double.IsNaN(lastActivityReading))
|
|
{
|
|
double activeDiff = currentActivity - lastActivityReading;
|
|
if (activeDiff >= 0)
|
|
{
|
|
double activity = (activeDiff / deltaTime) * 100.0;
|
|
activity = Math.Min(Math.Max(activity, 0.0), 100.0);
|
|
|
|
activitySensor.Value = (float)activity;
|
|
ActivateSensor(activitySensor);
|
|
}
|
|
else
|
|
{
|
|
activitySensor.Value = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
activitySensor.Value = null;
|
|
}
|
|
|
|
lastActivityReading = currentActivity;
|
|
}
|
|
|
|
private void UpdateMemorySensors()
|
|
{
|
|
if (string.IsNullOrEmpty(_d3dDeviceId))
|
|
{
|
|
// Fallback: Try to find any Intel D3D device
|
|
string[] deviceIdentifiers = D3DDisplayDevice.GetDeviceIdentifiers();
|
|
if (deviceIdentifiers != null)
|
|
{
|
|
foreach (string deviceId in deviceIdentifiers)
|
|
{
|
|
if (deviceId.IndexOf("VEN_8086", StringComparison.OrdinalIgnoreCase) != -1)
|
|
{
|
|
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(deviceId, out D3DDisplayDevice.D3DDeviceInfo testInfo))
|
|
{
|
|
if (testInfo.GpuDedicatedLimit > 0 && !testInfo.Integrated)
|
|
{
|
|
_d3dDeviceId = deviceId;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(_d3dDeviceId))
|
|
return;
|
|
}
|
|
|
|
if (D3DDisplayDevice.GetDeviceInfoByIdentifier(_d3dDeviceId, out D3DDisplayDevice.D3DDeviceInfo deviceInfo))
|
|
{
|
|
// Get dedicated video memory (VRAM) usage
|
|
ulong totalBytes = deviceInfo.GpuVideoMemoryLimit;
|
|
ulong usedBytes = deviceInfo.GpuDedicatedUsed;
|
|
ulong freeBytes = totalBytes > usedBytes ? totalBytes - usedBytes : 0;
|
|
|
|
if (totalBytes > 0)
|
|
{
|
|
// Convert bytes to MB for display
|
|
_memoryTotal.Value = totalBytes / (1024.0f * 1024.0f);
|
|
ActivateSensor(_memoryTotal);
|
|
|
|
_memoryUsed.Value = usedBytes / (1024.0f * 1024.0f);
|
|
ActivateSensor(_memoryUsed);
|
|
|
|
_memoryFree.Value = freeBytes / (1024.0f * 1024.0f);
|
|
ActivateSensor(_memoryFree);
|
|
|
|
// Calculate load percentage
|
|
_memoryLoad.Value = (float)((double)usedBytes / totalBytes * 100.0);
|
|
ActivateSensor(_memoryLoad);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateBandwidthFromCounter(IntelGcl.ctl_oc_telemetry_item_t bandwidthItem, ref double lastBandwidthReading, Sensor bandwidthSensor)
|
|
{
|
|
if (!IsValid || bandwidthSensor == null)
|
|
return;
|
|
|
|
// If the telemetry item directly provides bandwidth value (not a counter)
|
|
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
|
|
// Check the units to determine if conversion is needed
|
|
if (bandwidthItem.units == IntelGcl.ctl_units_t.CTL_UNITS_BANDWIDTH_MBPS)
|
|
{
|
|
// Convert MB/s to B/s (multiply by 1024*1024)
|
|
bandwidthValue = bandwidthValue * 1024.0 * 1024.0;
|
|
}
|
|
else if (bandwidthItem.units == IntelGcl.ctl_units_t.CTL_UNITS_MEM_SPEED_GBPS)
|
|
{
|
|
// Convert GB/s to B/s (multiply by 1024*1024*1024)
|
|
bandwidthValue = bandwidthValue * 1024.0 * 1024.0 * 1024.0;
|
|
}
|
|
|
|
bandwidthSensor.Value = (float)bandwidthValue;
|
|
ActivateSensor(bandwidthSensor);
|
|
}
|
|
else
|
|
{
|
|
bandwidthSensor.Value = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|