A modern C# wrapper for Intel Graphics Control Library (IGCL), providing easy access to Intel GPU features and settings.
- IntPtr-based API surface; no custom handle types to manage
- Automatic cleanup via
IDisposable(SafeHandle-backed) - Strongly typed structs/enums matching IGCL headers
- Helper methods for common adapter/display queries
- Facade DTOs for bool-friendly helper results, with
*Native()accessors when you need raw structs - ClangSharp-generated bindings kept in sync with the SDK
- Tests skip gracefully when hardware is absent
- Split test suites:
IGCLWrapper.NativeTestsexercises only ClangSharp-generated APIs.IGCLWrapper.FacadeTestsexercises the new facade helpers.
- Intel GPU with IGCL support
- Windows 10/11 x64
- .NET 10.0 SDK
- Intel Graphics drivers (25.20.100.6618+)
git clone https://github.com/terrymacdonald/IGCLWrapper.git
cd IGCLWrapper
./prepare_igcl.ps1 # pulls the IGCL SDK
./build_igcl.ps1 # restores, regenerates bindings, builds, tests- Local build artifacts: after
./build_igcl.ps1, referenceIGCLWrapper\bin\Debug\net10.0\IGCLWrapper.dll(orReleaseif you build release) from your project. - Add as a project reference: add
IGCLWrapper/IGCLWrapper.csprojto your solution and reference it. - Requirements at runtime: Windows x64, Intel GPU/driver, and IGCL DLLs available (the wrapper dynamically loads
ControlLibfrom the installed Intel drivers).
using IGCLWrapper;
using var api = IGCLApiHelper.Initialize();
var adapters = api.EnumerateAdapters();
Console.WriteLine($"Found {adapters.Count} Intel GPU(s)");
foreach (var adapter in adapters)
{
var props = adapter.GetProperties();
Console.WriteLine($"\nGPU: {adapter.Name}");
Console.WriteLine($"Device ID: 0x{props.pci_device_id:X}");
foreach (var display in adapter.EnumerateDisplayOutputs())
{
if (!display.IsActive()) continue;
var (width, height) = display.GetResolution();
var refresh = display.GetRefreshRateHz();
Console.WriteLine($" {width}x{height} @ {refresh:F2} Hz");
}
}try
{
using var igcl = IGCLApi.Initialize();
// use the API
}
catch (IGCLException ex)
{
Console.WriteLine($"IGCL Error: {ex.Result} - {ex.Message}");
}
catch (DllNotFoundException)
{
Console.WriteLine("IGCL DLL not found. Install Intel Graphics drivers.");
}Use the facade helpers to avoid manual struct sizing/handle management.
DTO-returning helpers use bool properties; call *Native() variants when you need the raw structs.
Get/Set operations are split into Get*() and Set*() helpers; GetSet*Native() remains for direct IGCL calls.
using IGCLWrapper;
using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
foreach (var display in adapter.EnumerateDisplayOutputs())
{
if (!display.IsActive()) continue;
var (w, h) = display.GetResolution();
var hz = display.GetRefreshRateHz();
Console.WriteLine($"{adapter.Name}: {w}x{h} @ {hz:F2} Hz");
}
}using IGCLWrapper;
using System.Linq;
using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
var result = adapter.GetCombinedDisplay();
if (result.IsSupported && result.NumOutputs > 0)
{
Console.WriteLine($"Adapter {adapter.Name} has a combined display with {result.NumOutputs} outputs.");
}
}using IGCLWrapper;
using System.Linq;
using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
var combined = adapter.GetCombinedDisplay();
if (combined.NumOutputs == 0 || combined.ChildInfos == null)
{
Console.WriteLine($"Adapter {adapter.Name} has no combined display configured.");
continue;
}
Console.WriteLine($"Combined display: {combined.CombinedDesktopWidth}x{combined.CombinedDesktopHeight}");
for (var i = 0; i < combined.NumOutputs; i++)
{
var child = combined.ChildInfos[i];
Console.WriteLine(
$" Output {i}: handle={child.DisplayOutput}, " +
$"FbSrc={child.FbSrc.Left},{child.FbSrc.Top},{child.FbSrc.Right},{child.FbSrc.Bottom}, " +
$"FbPos={child.FbPos.Left},{child.FbPos.Top},{child.FbPos.Right},{child.FbPos.Bottom}, " +
$"Orientation={child.DisplayOrientation}, " +
$"Target={child.TargetMode.Width}x{child.TargetMode.Height}@{child.TargetMode.RefreshRate}");
}
}using IGCLWrapper;
using var api = IGCLApiHelper.Initialize();
foreach (var adapter in api.EnumerateAdapters())
{
var tempHelper = api.GetTemperatureHelper(adapter);
var sensor = tempHelper.EnumTemperatureSensors().FirstOrDefault();
if (sensor != IntPtr.Zero)
{
var tempC = tempHelper.TemperatureGetState(sensor);
Console.WriteLine($"{adapter.Name}: {tempC:F1} C");
}
}using IGCLWrapper;
using var api = IGCLApiHelper.Initialize();
var adapter = api.EnumerateAdapters().First();
var args = new ctl_wait_property_change_args_t
{
Size = (uint)sizeof(ctl_wait_property_change_args_t),
Version = 0,
PropertyType = ctl_property_type_flags_t.CTL_PROPERTY_TYPE_FLAG_DISPLAY,
TimeOutMilliSec = 5_000 // 5 seconds
};
adapter.WaitForPropertyChange(args);
Console.WriteLine("Property change observed or timeout reached.");If you prefer direct P/Invoke access, use IGCLApi and the generated structs/functions.
using IGCLWrapper;
using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
var displays = igcl.EnumerateDisplays(adapter);
foreach (var display in displays)
{
var props = new ctl_display_properties_t { Size = (uint)sizeof(ctl_display_properties_t), Version = 0 };
if (IGCL.ctlGetDisplayProperties((_ctl_display_output_handle_t*)display, &props) != ctl_result_t.CTL_RESULT_SUCCESS)
continue;
var timing = props.Display_Timing_Info;
if (timing.HActive == 0 || timing.VActive == 0) continue;
Console.WriteLine($"{timing.HActive}x{timing.VActive} @ {timing.RefreshRate / 1000.0:F2} Hz");
}
}using IGCLWrapper;
using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
var args = new ctl_combined_display_args_t
{
Size = (uint)sizeof(ctl_combined_display_args_t),
Version = 0,
OpType = ctl_combined_display_optype_t.CTL_COMBINED_DISPLAY_OPTYPE_QUERY_CONFIG
};
var result = IGCL.ctlGetSetCombinedDisplay((_ctl_device_adapter_handle_t*)adapter, &args);
if (result == ctl_result_t.CTL_RESULT_SUCCESS && args.IsSupported && args.NumOutputs > 0)
{
Console.WriteLine("Combined display present.");
}
}using IGCLWrapper;
using var igcl = IGCLApi.Initialize();
foreach (var adapter in igcl.EnumerateAdapters())
{
uint count = 0;
if (IGCL.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, null) != ctl_result_t.CTL_RESULT_SUCCESS || count == 0)
continue;
var sensors = new IntPtr[count];
unsafe
{
fixed (IntPtr* pSensors = sensors)
{
if (IGCL.ctlEnumTemperatureSensors((_ctl_device_adapter_handle_t*)adapter, &count, (_ctl_temp_handle_t**)pSensors) != ctl_result_t.CTL_RESULT_SUCCESS)
continue;
}
double temp = 0;
if (IGCL.ctlTemperatureGetState((_ctl_temp_handle_t*)sensors[0], &temp) == ctl_result_t.CTL_RESULT_SUCCESS)
{
Console.WriteLine($"{temp:F1} C");
}
}
}Tests require Intel GPU hardware and the IGCL DLLs present. They skip gracefully if not available.
./test_igcl.ps1
# or
dotnet test IGCLWrapper.NativeTests/IGCLWrapper.NativeTests.csproj
dotnet test IGCLWrapper.FacadeTests/IGCLWrapper.FacadeTests.csprojWhen Intel releases a new IGCL:
./prepare_igcl.ps1 # update SDK bits
./build_igcl.ps1 # regenerates bindings via ClangSharp and rebuildsIGCLWrapper/- main wrappercs_generated/- ClangSharp output (auto-generated)IGCLApi.cs- high-level APIIGCLExtensions.cs- helpers for common ops
IGCLWrapper.NativeTests/- native test suiteIGCLWrapper.FacadeTests/- facade test suiteSamples/- sample appsdrivers.gpu.control-library/- IGCL SDK payload (populated by prepare script)
- Always dispose
IGCLApi(useusing); SafeHandle + finalizer backstops leaks. - Handles returned from enumerate calls are opaque; pass them back to IGCL or helper methods.
- Facade helpers return DTOs with
boolproperties; use*Native()helper methods to access raw structs. - Struct
Versionfields are bytes in native structs; use(byte)0/1in native code paths.
PRs welcome-please add/keep tests passing and let the generator own cs_generated.