fix: skip integrated GPU in IGCL enumeration to prevent duplicate hardware entries (#2217)

IGCL's ctlEnumerateDevices returns all Intel graphics devices including
the integrated GPU, but IntelDiscreteGpu was created for every device
without filtering. This caused the iGPU to appear twice: once from the
IGCL path (as IntelDiscreteGpu) and once from the D3D path (as
IntelIntegratedGpu), with overlapping sensors.

Check the CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED flag on each device's
graphics_adapter_properties and skip integrated adapters, since they are
already handled by IntelIntegratedGpu via D3D enumeration.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
kyranmcdonnell
2026-02-13 18:16:37 +01:00
committed by GitHub
co-authored by Cursor
parent e2e0d1c9ad
commit 1564a69c52
2 changed files with 30 additions and 2 deletions
@@ -1,4 +1,4 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// 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.
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Hardware.Cpu;
using LibreHardwareMonitor.Interop;
@@ -21,7 +22,7 @@ internal class IntelGpuGroup : IGroup
{
if (!Software.OperatingSystem.IsUnix)
{
// Initialize Intel GCL for discrete GPUs
// Initialize Intel GCL for discrete and integrated GPU telemetry
bool gclInitialized = false;
try
@@ -56,6 +57,22 @@ internal class IntelGpuGroup : IGroup
{
try
{
// Skip integrated GPUs — they are handled separately by IntelIntegratedGpu
// via the D3D enumeration path below. IGCL's ctlEnumerateDevices returns all
// Intel graphics devices including the iGPU, which would otherwise cause the
// same physical device to appear twice with overlapping sensors.
var props = new IntelGcl.ctl_device_adapter_properties_t
{
Size = (uint)Marshal.SizeOf<IntelGcl.ctl_device_adapter_properties_t>(),
Version = 2
};
if (IntelGcl.ctlGetDeviceProperties(handle, ref props) == (int)IntelGcl.ctl_result_t.CTL_RESULT_SUCCESS &&
(props.graphics_adapter_properties & (uint)IntelGcl.ctl_adapter_properties_flag_t.CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED) != 0)
{
continue;
}
var gpu = new IntelDiscreteGpu(handle, settings);
if (gpu.IsValid)
{
@@ -48,6 +48,17 @@ internal static class IntelGcl
CTL_DEVICE_TYPE_MAX
}
/// <summary>
/// Adapter properties flags from ctl_adapter_properties_flags_t.
/// The graphics_adapter_properties field is a bitmask of these values.
/// </summary>
public enum ctl_adapter_properties_flag_t : uint
{
CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED = 1 << 0,
CTL_ADAPTER_PROPERTIES_FLAG_LDA_PRIMARY = 1 << 1,
CTL_ADAPTER_PROPERTIES_FLAG_LDA_SECONDARY = 1 << 2,
}
public enum ctl_fan_speed_mode_t
{
CTL_FAN_SPEED_MODE_DEFAULT = 0,